From 7b0b873b1f58011992ba3cc1e95be6f6df7c4e12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 18:36:54 +0000 Subject: [PATCH 001/607] Bump floki from 0.35.1 to 0.35.2 Bumps [floki](https://github.com/philss/floki) from 0.35.1 to 0.35.2. - [Release notes](https://github.com/philss/floki/releases) - [Changelog](https://github.com/philss/floki/blob/main/CHANGELOG.md) - [Commits](https://github.com/philss/floki/compare/v0.35.1...v0.35.2) --- updated-dependencies: - dependency-name: floki dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 2ea41a9490b5..52c7f73b4882 100644 --- a/mix.lock +++ b/mix.lock @@ -60,7 +60,7 @@ "exvcr": {:hex, :exvcr, "0.14.4", "1aa5fe7d3f10b117251c158f8d28b39f7fc73d0a7628b2d0b75bf8cfb1111576", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.16", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4e600568c02ed29d46bc2e2c74927d172ba06658aa8b14705c0207363c44cc94"}, "file_info": {:hex, :file_info, "0.0.4", "2e0e77f211e833f38ead22cb29ce53761d457d80b3ffe0ffe0eb93880b0963b2", [:mix], [{:mimetype_parser, "~> 0.1.2", [hex: :mimetype_parser, repo: "hexpm", optional: false]}], "hexpm", "50e7ad01c2c8b9339010675fe4dc4a113b8d6ca7eddce24d1d74fd0e762781a5"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "floki": {:hex, :floki, "0.35.1", "b21cf592ed38c1207c5ea52120a2e81d6ecba11337a633a3f29ec17a64033178", [:mix], [], "hexpm", "f126e3eb814f131c21befeeeb773d2c4e2331ce05214c1a9844a3edde5c69003"}, + "floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"}, "flow": {:hex, :flow, "1.2.4", "1dd58918287eb286656008777cb32714b5123d3855956f29aa141ebae456922d", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "874adde96368e71870f3510b91e35bc31652291858c86c0e75359cbdd35eb211"}, "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, "gettext": {:hex, :gettext, "0.23.1", "821e619a240e6000db2fc16a574ef68b3bd7fe0167ccc264a81563cc93e67a31", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "19d744a36b809d810d610b57c27b934425859d158ebd56561bc41f7eeb8795db"}, From 240376b9eb592a342a49c4c01d99c90d0587e316 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Mon, 30 Oct 2023 23:30:28 +0300 Subject: [PATCH 002/607] Add TOKEN_INSTANCE_OWNER_MIGRATION_ENABLED env --- .../token_instance_owner_address_migration/supervisor.ex | 8 +++++--- .../token_instance_owner_address_migration/worker.ex | 2 +- config/runtime.exs | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/explorer/lib/explorer/token_instance_owner_address_migration/supervisor.ex b/apps/explorer/lib/explorer/token_instance_owner_address_migration/supervisor.ex index 5bb8532fdf5a..01ca324f5f2d 100644 --- a/apps/explorer/lib/explorer/token_instance_owner_address_migration/supervisor.ex +++ b/apps/explorer/lib/explorer/token_instance_owner_address_migration/supervisor.ex @@ -5,7 +5,7 @@ defmodule Explorer.TokenInstanceOwnerAddressMigration.Supervisor do use Supervisor - alias Explorer.TokenInstanceOwnerAddressMigration.{Helper, Worker} + alias Explorer.TokenInstanceOwnerAddressMigration.Worker def start_link(init_arg) do Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__) @@ -13,9 +13,11 @@ defmodule Explorer.TokenInstanceOwnerAddressMigration.Supervisor do @impl true def init(_init_arg) do - if Helper.unfilled_token_instances_exists?() do + params = Application.get_env(:explorer, Explorer.TokenInstanceOwnerAddressMigration) + + if params[:enabled] do children = [ - {Worker, Application.get_env(:explorer, Explorer.TokenInstanceOwnerAddressMigration)} + {Worker, params} ] Supervisor.init(children, strategy: :one_for_one) diff --git a/apps/explorer/lib/explorer/token_instance_owner_address_migration/worker.ex b/apps/explorer/lib/explorer/token_instance_owner_address_migration/worker.ex index c0ad7c487997..c9cfabc9d2ca 100644 --- a/apps/explorer/lib/explorer/token_instance_owner_address_migration/worker.ex +++ b/apps/explorer/lib/explorer/token_instance_owner_address_migration/worker.ex @@ -14,7 +14,7 @@ defmodule Explorer.TokenInstanceOwnerAddressMigration.Worker do alias Explorer.Repo alias Explorer.TokenInstanceOwnerAddressMigration.Helper - def start_link(concurrency: concurrency, batch_size: batch_size) do + def start_link(concurrency: concurrency, batch_size: batch_size, enabled: _) do GenServer.start_link(__MODULE__, %{concurrency: concurrency, batch_size: batch_size}, name: __MODULE__) end diff --git a/config/runtime.exs b/config/runtime.exs index 6332d618d0c8..3bc5336b441e 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -433,7 +433,8 @@ config :explorer, Explorer.Chain.Cache.MinMissingBlockNumber, config :explorer, Explorer.TokenInstanceOwnerAddressMigration, concurrency: ConfigHelper.parse_integer_env_var("TOKEN_INSTANCE_OWNER_MIGRATION_CONCURRENCY", 5), - batch_size: ConfigHelper.parse_integer_env_var("TOKEN_INSTANCE_OWNER_MIGRATION_BATCH_SIZE", 50) + batch_size: ConfigHelper.parse_integer_env_var("TOKEN_INSTANCE_OWNER_MIGRATION_BATCH_SIZE", 50), + enabled: ConfigHelper.parse_bool_env_var("TOKEN_INSTANCE_OWNER_MIGRATION_ENABLED") config :explorer, Explorer.Chain.Transaction, rootstock_remasc_address: System.get_env("ROOTSTOCK_REMASC_ADDRESS"), From 96743ee022abf94201c98b4277bb2dde1c2c862f Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Tue, 31 Oct 2023 12:32:17 +0600 Subject: [PATCH 003/607] Fix internal transaction error --- CHANGELOG.md | 2 +- apps/indexer/lib/indexer/fetcher/internal_transaction.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d5c3f8edf61..66d9259aa873 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ - [#8708](https://github.com/blockscout/blockscout/pull/8708) - CoinBalanceHistory tab: show also tx with gasPrice & gasUsed > 0 - [#8706](https://github.com/blockscout/blockscout/pull/8706) - Add address name updating on contract re-verification - [#8705](https://github.com/blockscout/blockscout/pull/8705) - Fix sourcify enabled flag -- [#8695](https://github.com/blockscout/blockscout/pull/8695) - Don't override internal transaction error if it's present already +- [#8695](https://github.com/blockscout/blockscout/pull/8695), [#8755](https://github.com/blockscout/blockscout/pull/8755) - Don't override internal transaction error if it's present already - [#8685](https://github.com/blockscout/blockscout/pull/8685) - Fix db pool size exceeds Postgres max connections - [#8678](https://github.com/blockscout/blockscout/pull/8678) - Fix `is_verified` for `/addresses` and `/smart-contracts` diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex index 7fefb5b4400b..e4726d5a3c7a 100644 --- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex @@ -306,7 +306,7 @@ defmodule Indexer.Fetcher.InternalTransaction do |> Map.delete(:created_contract_code) |> Map.delete(:gas_used) |> Map.delete(:output) - |> Map.put_new(:error, failed_parent[:error]) + |> Map.put(:error, internal_transaction_param[:error] || failed_parent[:error]) else internal_transaction_param end From 2d9112cff0072d6446cc5322c4db0960753f57cc Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Tue, 31 Oct 2023 10:13:15 +0300 Subject: [PATCH 004/607] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d5c3f8edf61..548491649a1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Fixes +- [#8752](https://github.com/blockscout/blockscout/pull/8752) - Add `TOKEN_INSTANCE_OWNER_MIGRATION_ENABLED` env - [#8724](https://github.com/blockscout/blockscout/pull/8724) - Fix flaky account notifier test ### Chore From 3c58526b50c108bbdb31fe696c8e71f4e6275499 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 31 Oct 2023 12:16:23 +0300 Subject: [PATCH 005/607] Gnosis safe proxy via singleton --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/chain.ex | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61e19994c174..bf7408a11cfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Fixes +- [#8759](https://github.com/blockscout/blockscout/pull/8759) - Gnosis safe proxy via singleton input - [#8752](https://github.com/blockscout/blockscout/pull/8752) - Add `TOKEN_INSTANCE_OWNER_MIGRATION_ENABLED` env - [#8724](https://github.com/blockscout/blockscout/pull/8724) - Fix flaky account notifier test diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 498de42328c6..6cc7bc80a227 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -5700,7 +5700,7 @@ defmodule Explorer.Chain do |> Enum.find(fn item -> case item do {"inputs", inputs} -> - master_copy_input?(inputs) + master_copy_input?(inputs) || singleton_input?(inputs) _ -> false @@ -5715,6 +5715,13 @@ defmodule Explorer.Chain do end) end + defp singleton_input?(inputs) do + inputs + |> Enum.find(fn input -> + Map.get(input, "name") == "_singleton" + end) + end + def get_implementation_abi(implementation_address_hash_string, options \\ []) def get_implementation_abi(implementation_address_hash_string, options) From 64309ed808cc3146d248369e948efefb56bff6e2 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 1 Nov 2023 12:31:41 +0300 Subject: [PATCH 006/607] Fix for tvl update in market history when row already exists --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/market/market.ex | 70 ++++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf7408a11cfd..4163b44323f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Fixes +- [#8765](https://github.com/blockscout/blockscout/pull/8765) - Fix for tvl update in market history when row already exists - [#8759](https://github.com/blockscout/blockscout/pull/8759) - Gnosis safe proxy via singleton input - [#8752](https://github.com/blockscout/blockscout/pull/8752) - Add `TOKEN_INSTANCE_OWNER_MIGRATION_ENABLED` env - [#8724](https://github.com/blockscout/blockscout/pull/8724) - Fix flaky account notifier test diff --git a/apps/explorer/lib/explorer/market/market.ex b/apps/explorer/lib/explorer/market/market.ex index 571f837e0fe7..5e99a147c387 100644 --- a/apps/explorer/lib/explorer/market/market.ex +++ b/apps/explorer/lib/explorer/market/market.ex @@ -7,6 +7,8 @@ defmodule Explorer.Market do alias Explorer.Market.{MarketHistory, MarketHistoryCache} alias Explorer.{ExchangeRates, Repo} + import Ecto.Query, only: [from: 2] + @doc """ Retrieves the history for the recent specified amount of days. @@ -67,7 +69,73 @@ defmodule Explorer.Market do # Enforce MarketHistory ShareLocks order (see docs: sharelocks.md) |> Enum.sort_by(& &1.date) - Repo.insert_all(MarketHistory, records_without_zeroes, on_conflict: :nothing, conflict_target: [:date]) + Repo.insert_all(MarketHistory, records_without_zeroes, + on_conflict: market_history_on_conflict(), + conflict_target: [:date] + ) + end + + defp market_history_on_conflict do + from( + market_history in MarketHistory, + update: [ + set: [ + opening_price: + fragment( + """ + CASE WHEN (? IS NULL OR ? = 0) AND EXCLUDED.opening_price IS NOT NULL AND EXCLUDED.opening_price > 0 + THEN EXCLUDED.opening_price + ELSE ? + END + """, + market_history.opening_price, + market_history.opening_price, + market_history.opening_price + ), + closing_price: + fragment( + """ + CASE WHEN (? IS NULL OR ? = 0) AND EXCLUDED.closing_price IS NOT NULL AND EXCLUDED.closing_price > 0 + THEN EXCLUDED.closing_price + ELSE ? + END + """, + market_history.closing_price, + market_history.closing_price, + market_history.closing_price + ), + market_cap: + fragment( + """ + CASE WHEN (? IS NULL OR ? = 0) AND EXCLUDED.market_cap IS NOT NULL AND EXCLUDED.market_cap > 0 + THEN EXCLUDED.market_cap + ELSE ? + END + """, + market_history.market_cap, + market_history.market_cap, + market_history.market_cap + ), + tvl: + fragment( + """ + CASE WHEN (? IS NULL OR ? = 0) AND EXCLUDED.tvl IS NOT NULL AND EXCLUDED.tvl > 0 + THEN EXCLUDED.tvl + ELSE ? + END + """, + market_history.tvl, + market_history.tvl, + market_history.tvl + ) + ] + ], + where: + is_nil(market_history.tvl) or market_history.tvl == 0 or is_nil(market_history.market_cap) or + market_history.market_cap == 0 or is_nil(market_history.opening_price) or + market_history.opening_price == 0 or is_nil(market_history.closing_price) or + market_history.closing_price == 0 + ) end @spec get_exchange_rate(String.t()) :: Token.t() | nil From fbbf53cd524bb22f6fa6383a2e9ddb8eed743000 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Tue, 7 Nov 2023 17:01:17 +0600 Subject: [PATCH 007/607] Disable catchup indexer by env --- CHANGELOG.md | 2 ++ apps/indexer/lib/indexer/supervisor.ex | 12 +++++++----- config/runtime.exs | 2 ++ docker-compose/envs/common-blockscout.env | 1 + 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4163b44323f9..eafd2ff4dfbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Features +- [#8795](https://github.com/blockscout/blockscout/pull/8795) - Disable catchup indexer by env + ### Fixes - [#8765](https://github.com/blockscout/blockscout/pull/8765) - Fix for tvl update in market history when row already exists diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index 5fce739019ca..c7afebbbcee0 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -157,11 +157,13 @@ defmodule Indexer.Supervisor do %{block_fetcher: realtime_block_fetcher, subscribe_named_arguments: realtime_subscribe_named_arguments}, [name: BlockRealtime.Supervisor] ]), - {BlockCatchup.Supervisor, - [ - %{block_fetcher: block_fetcher, block_interval: block_interval, memory_monitor: memory_monitor}, - [name: BlockCatchup.Supervisor] - ]}, + configure( + BlockCatchup.Supervisor, + [ + %{block_fetcher: block_fetcher, block_interval: block_interval, memory_monitor: memory_monitor}, + [name: BlockCatchup.Supervisor] + ] + ), {Withdrawal.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments]]} ] |> List.flatten() diff --git a/config/runtime.exs b/config/runtime.exs index 3bc5336b441e..2b3f4d44fcda 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -525,6 +525,8 @@ config :indexer, Indexer.Fetcher.EmptyBlocksSanitizer.Supervisor, config :indexer, Indexer.Block.Realtime.Supervisor, enabled: !ConfigHelper.parse_bool_env_var("DISABLE_REALTIME_INDEXER") +config :indexer, Indexer.Block.Catchup.Supervisor, enabled: !ConfigHelper.parse_bool_env_var("DISABLE_CATCHUP_INDEXER") + config :indexer, Indexer.Fetcher.TokenInstance.Realtime.Supervisor, disabled?: ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_TOKEN_INSTANCE_REALTIME_FETCHER") diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 3b5ae9a7d212..7bf3c41a56ca 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -99,6 +99,7 @@ API_V1_READ_METHODS_DISABLED=false API_V1_WRITE_METHODS_DISABLED=false DISABLE_INDEXER=false DISABLE_REALTIME_INDEXER=false +DISABLE_CATCHUP_INDEXER=false INDEXER_DISABLE_TOKEN_INSTANCE_REALTIME_FETCHER=false INDEXER_DISABLE_TOKEN_INSTANCE_RETRY_FETCHER=false INDEXER_DISABLE_TOKEN_INSTANCE_SANITIZE_FETCHER=false From f89d29b478dc68f82512f81c4828cf0a11249b61 Mon Sep 17 00:00:00 2001 From: varasev <33550681+varasev@users.noreply.github.com> Date: Wed, 8 Nov 2023 09:56:45 +0300 Subject: [PATCH 008/607] Fix Indexer.Transform.Addresses for non-Suave setup (#8784) * Fix Indexer.Transform.Addresses for non-Suave setup * Update changelog * Fix bin/install_chrome_headless.sh * Fix bin/install_chrome_headless.sh * Rename Explorer.Token.MetadataRetrieverTest * Reset GA cache * Fix test in InstanceMetadataRetrieverTest --------- Co-authored-by: POA <33550681+poa@users.noreply.github.com> Co-authored-by: Viktor Baranov --- .github/workflows/config.yml | 28 +++++++++---------- CHANGELOG.md | 1 + .../instance_metadata_retriever_test.exs | 4 +-- .../lib/indexer/transform/addresses.ex | 4 ++- bin/install_chrome_headless.sh | 4 +-- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 497c5005104d..75e35d9df679 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -55,7 +55,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps- @@ -113,7 +113,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -137,7 +137,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -160,7 +160,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -169,7 +169,7 @@ jobs: id: dialyzer-cache with: path: priv/plts - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-mixlockhash_24-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-mixlockhash_25-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-" @@ -200,7 +200,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -226,7 +226,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -255,7 +255,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -303,7 +303,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -349,7 +349,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -406,7 +406,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -460,7 +460,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -525,7 +525,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -589,7 +589,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" diff --git a/CHANGELOG.md b/CHANGELOG.md index 4163b44323f9..0f832e0e733b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Fixes +- [#8784](https://github.com/blockscout/blockscout/pull/8784) - Fix Indexer.Transform.Addresses for non-Suave setup - [#8765](https://github.com/blockscout/blockscout/pull/8765) - Fix for tvl update in market history when row already exists - [#8759](https://github.com/blockscout/blockscout/pull/8759) - Gnosis safe proxy via singleton input - [#8752](https://github.com/blockscout/blockscout/pull/8752) - Add `TOKEN_INSTANCE_OWNER_MIGRATION_ENABLED` env diff --git a/apps/explorer/test/explorer/token/instance_metadata_retriever_test.exs b/apps/explorer/test/explorer/token/instance_metadata_retriever_test.exs index a9255d04c796..d1d48cc65452 100644 --- a/apps/explorer/test/explorer/token/instance_metadata_retriever_test.exs +++ b/apps/explorer/test/explorer/token/instance_metadata_retriever_test.exs @@ -1,4 +1,4 @@ -defmodule Explorer.Token.MetadataRetrieverTest do +defmodule Explorer.Token.InstanceMetadataRetrieverTest do use EthereumJSONRPC.Case alias Indexer.Fetcher.TokenInstance.MetadataRetriever @@ -342,7 +342,7 @@ defmodule Explorer.Token.MetadataRetrieverTest do {:error, "(3) execution reverted: Nonexistent token (0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000114e6f6e6578697374656e7420746f6b656e000000000000000000000000000000)"} - assert {:ok, %{error: "VM execution error"}} == MetadataRetriever.fetch_json(data) + assert {:error, "VM execution error"} == MetadataRetriever.fetch_json(data) end test "Process CIDv0 IPFS links" do diff --git a/apps/indexer/lib/indexer/transform/addresses.ex b/apps/indexer/lib/indexer/transform/addresses.ex index bd31681200da..5e3a0e227999 100644 --- a/apps/indexer/lib/indexer/transform/addresses.ex +++ b/apps/indexer/lib/indexer/transform/addresses.ex @@ -95,7 +95,9 @@ defmodule Indexer.Transform.Addresses do ], [ %{from: :block_number, to: :fetched_coin_balance_block_number}, - %{from: :to_address_hash, to: :hash}, + %{from: :to_address_hash, to: :hash} + ], + [ %{from: :execution_node_hash, to: :hash}, %{from: :wrapped_to_address_hash, to: :hash} ] diff --git a/bin/install_chrome_headless.sh b/bin/install_chrome_headless.sh index 731ee458c8be..63f2d98a2493 100755 --- a/bin/install_chrome_headless.sh +++ b/bin/install_chrome_headless.sh @@ -2,8 +2,8 @@ export DISPLAY=:99.0 sh -e /etc/init.d/xvfb start export CHROMEDRIVER_VERSION=$(curl -s "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json" | jq -r '.channels' | jq -r '.Stable' | jq -r '.version') -curl -L -O "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/${CHROMEDRIVER_VERSION}/linux64/chrome-linux64.zip" -unzip chromedriver_linux64.zip +curl -L -O "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/${CHROMEDRIVER_VERSION}/linux64/chromedriver-linux64.zip" +unzip -j chromedriver-linux64.zip sudo chmod +x chromedriver sudo mv chromedriver /usr/local/bin wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb From b2e458a6931af27cf461a184711b0ea14946d509 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Nov 2023 06:58:30 +0000 Subject: [PATCH 009/607] Bump cldr_utils from 2.24.1 to 2.24.2 Bumps [cldr_utils](https://github.com/elixir-cldr/cldr_utils) from 2.24.1 to 2.24.2. - [Release notes](https://github.com/elixir-cldr/cldr_utils/releases) - [Changelog](https://github.com/elixir-cldr/cldr_utils/blob/master/CHANGELOG.md) - [Commits](https://github.com/elixir-cldr/cldr_utils/compare/v2.24.1...v2.24.2) --- updated-dependencies: - dependency-name: cldr_utils dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 542e0271a23c..0a65fac3f9f5 100644 --- a/mix.lock +++ b/mix.lock @@ -15,7 +15,7 @@ "castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"}, "cbor": {:hex, :cbor, "1.0.1", "39511158e8ea5a57c1fcb9639aaa7efde67129678fee49ebbda780f6f24959b0", [:mix], [], "hexpm", "5431acbe7a7908f17f6a9cd43311002836a34a8ab01876918d8cfb709cd8b6a2"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, - "cldr_utils": {:hex, :cldr_utils, "2.24.1", "5ff8c8c55f96666228827bcf85a23d632022def200566346545d01d15e4c30dc", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "1820300531b5b849d0bc468e5a87cd64f8f2c5191916f548cbe69b2efc203780"}, + "cldr_utils": {:hex, :cldr_utils, "2.24.2", "364fa30be55d328e704629568d431eb74cd2f085752b27f8025520b566352859", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "3362b838836a9f0fa309de09a7127e36e67310e797d556db92f71b548832c7cf"}, "cloak": {:hex, :cloak, "1.1.2", "7e0006c2b0b98d976d4f559080fabefd81f0e0a50a3c4b621f85ceeb563e80bb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "940d5ac4fcd51b252930fd112e319ea5ae6ab540b722f3ca60a85666759b9585"}, "cloak_ecto": {:hex, :cloak_ecto, "1.2.0", "e86a3df3bf0dc8980f70406bcb0af2858bac247d55494d40bc58a152590bd402", [:mix], [{:cloak, "~> 1.1.1", [hex: :cloak, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "8bcc677185c813fe64b786618bd6689b1707b35cd95acaae0834557b15a0c62f"}, "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, From 4bb4552c0cd44ebafa987a53997ed47e1c43d4bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Nov 2023 06:59:13 +0000 Subject: [PATCH 010/607] Bump eslint from 8.52.0 to 8.53.0 in /apps/block_scout_web/assets Bumps [eslint](https://github.com/eslint/eslint) from 8.52.0 to 8.53.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v8.52.0...v8.53.0) --- updated-dependencies: - dependency-name: eslint dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 58 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 859a02067788..182812353e9b 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -78,7 +78,7 @@ "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.8.1", "css-minimizer-webpack-plugin": "^5.0.1", - "eslint": "^8.52.0", + "eslint": "^8.53.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.0", "eslint-plugin-node": "^11.1.0", @@ -2038,9 +2038,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -2067,9 +2067,9 @@ "dev": true }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.21.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", - "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2106,9 +2106,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", - "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -7290,15 +7290,15 @@ } }, "node_modules/eslint": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", - "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.52.0", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -19219,9 +19219,9 @@ "dev": true }, "@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -19242,9 +19242,9 @@ "dev": true }, "globals": { - "version": "13.21.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", - "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -19268,9 +19268,9 @@ } }, "@eslint/js": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", - "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true }, "@ethereumjs/common": { @@ -23228,15 +23228,15 @@ } }, "eslint": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", - "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.52.0", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index b2ad77b9301e..34655e1073c2 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -90,7 +90,7 @@ "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.8.1", "css-minimizer-webpack-plugin": "^5.0.1", - "eslint": "^8.52.0", + "eslint": "^8.53.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.0", "eslint-plugin-node": "^11.1.0", From 9d8d8a28b56883ea92ac03b8ae1fa10ce6ccd95f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Nov 2023 06:59:33 +0000 Subject: [PATCH 011/607] Bump ex_cldr_numbers from 2.32.2 to 2.32.3 Bumps [ex_cldr_numbers](https://github.com/elixir-cldr/cldr_numbers) from 2.32.2 to 2.32.3. - [Release notes](https://github.com/elixir-cldr/cldr_numbers/releases) - [Changelog](https://github.com/elixir-cldr/cldr_numbers/blob/main/CHANGELOG.md) - [Commits](https://github.com/elixir-cldr/cldr_numbers/compare/v2.32.2...v2.32.3) --- updated-dependencies: - dependency-name: ex_cldr_numbers dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mix.lock b/mix.lock index 542e0271a23c..5e57591e369c 100644 --- a/mix.lock +++ b/mix.lock @@ -15,7 +15,7 @@ "castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"}, "cbor": {:hex, :cbor, "1.0.1", "39511158e8ea5a57c1fcb9639aaa7efde67129678fee49ebbda780f6f24959b0", [:mix], [], "hexpm", "5431acbe7a7908f17f6a9cd43311002836a34a8ab01876918d8cfb709cd8b6a2"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, - "cldr_utils": {:hex, :cldr_utils, "2.24.1", "5ff8c8c55f96666228827bcf85a23d632022def200566346545d01d15e4c30dc", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "1820300531b5b849d0bc468e5a87cd64f8f2c5191916f548cbe69b2efc203780"}, + "cldr_utils": {:hex, :cldr_utils, "2.24.2", "364fa30be55d328e704629568d431eb74cd2f085752b27f8025520b566352859", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "3362b838836a9f0fa309de09a7127e36e67310e797d556db92f71b548832c7cf"}, "cloak": {:hex, :cloak, "1.1.2", "7e0006c2b0b98d976d4f559080fabefd81f0e0a50a3c4b621f85ceeb563e80bb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "940d5ac4fcd51b252930fd112e319ea5ae6ab540b722f3ca60a85666759b9585"}, "cloak_ecto": {:hex, :cloak_ecto, "1.2.0", "e86a3df3bf0dc8980f70406bcb0af2858bac247d55494d40bc58a152590bd402", [:mix], [{:cloak, "~> 1.1.1", [hex: :cloak, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "8bcc677185c813fe64b786618bd6689b1707b35cd95acaae0834557b15a0c62f"}, "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, @@ -42,10 +42,10 @@ "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_abi": {:hex, :ex_abi, "0.6.3", "e16f232346badc9f03a459cea7113ad4cff28dd2faf36f5635eaa01d5e903d7c", [:mix], [{:ex_keccak, "~> 0.7.3", [hex: :ex_keccak, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "bd1bcbe1d4fac14b90aa132fa54690b36d26e11e3777b755623bf32e66caef85"}, - "ex_cldr": {:hex, :ex_cldr, "2.37.4", "3e2c04d9c691a75a8b7e808dfcbacedb9cdf3e73f819d1b4174f8f065e5f29c1", [:mix], [{:cldr_utils, "~> 2.21", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "2bcb5de0095324ba645b4e156bf346add156d8b928f0ffd985c61664d981ad3d"}, - "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.15.0", "aadd34e91cfac7ef6b03fe8f47f8c6fa8c5daf3f89b5d9fee64ec545ded839cf", [:mix], [{:ex_cldr, "~> 2.34", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "0521316396c66877a2d636219767560bb2397c583341fcb154ecf9f3000e6ff8"}, + "ex_cldr": {:hex, :ex_cldr, "2.37.5", "9da6d97334035b961d2c2de167dc6af8cd3e09859301a5b8f49f90bd8b034593", [:mix], [{:cldr_utils, "~> 2.21", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "74ad5ddff791112ce4156382e171a5f5d3766af9d5c4675e0571f081fe136479"}, + "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.15.1", "e92ba17c41e7405b7784e0e65f406b5f17cfe313e0e70de9befd653e12854822", [:mix], [{:ex_cldr, "~> 2.34", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "31df8bd37688340f8819bdd770eb17d659652078d34db632b85d4a32864d6a25"}, "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.10.1", "67795eb28f8534a36cbdfeeaea6d3ee587eeac7eafff71968dd046c215d4ec42", [:mix], [{:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "da826148d95c7a1889fd847ae5704c141747bad43a5a44431ae97bced57b0f93"}, - "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.32.2", "5e0e3031d3f54b51fe7078a7a94592987b70b06d631bdc88813b222dc5a8b1bd", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "91257684a9c4d6abdf738f0cc5671837de876e69552e8bd4bc5fa1bfd5817713"}, + "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.32.3", "b631ff94c982ec518e46bf4736000a30a33d6b58facc085d5f240305f512ad4a", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7b626ff1e59a0ec9c3c5db5ce9ca91a6995e2ab56426b71f3cbf67181ea225f5"}, "ex_cldr_units": {:hex, :ex_cldr_units, "3.16.3", "41344ed9f6d5c69baeb4d13dadc80310ca511626f738af2e69ae53dd40fb28a5", [:mix], [{:cldr_utils, "~> 2.24", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.31", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "889dd56084821723ce82c4639d62cfc3513a9ca1ffb9063bcc83e7b3de5bc35b"}, "ex_doc": {:hex, :ex_doc, "0.30.9", "d691453495c47434c0f2052b08dd91cc32bc4e1a218f86884563448ee2502dd2", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "d7aaaf21e95dc5cddabf89063327e96867d00013963eadf2c6ad135506a8bc10"}, "ex_json_schema": {:hex, :ex_json_schema, "0.10.1", "e03b746b6675a750c0bb1a5cc919f61353f7ab8450977e11ceede20e6180c560", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "66a64e60dadad89914d92f89c7e7906c57de75a8b79ac2480d0d53e1b8096fb0"}, From dadb52ec7dabe9da759077393ff738df94fda2bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Nov 2023 07:16:25 +0000 Subject: [PATCH 012/607] Bump ex_abi from 0.6.3 to 0.6.4 Bumps [ex_abi](https://github.com/poanetwork/ex_abi) from 0.6.3 to 0.6.4. - [Release notes](https://github.com/poanetwork/ex_abi/releases) - [Changelog](https://github.com/poanetwork/ex_abi/blob/master/CHANGELOG.md) - [Commits](https://github.com/poanetwork/ex_abi/compare/0.6.3...0.6.4) --- updated-dependencies: - dependency-name: ex_abi dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 5e57591e369c..bdec56fbac9a 100644 --- a/mix.lock +++ b/mix.lock @@ -41,7 +41,7 @@ "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_abi": {:hex, :ex_abi, "0.6.3", "e16f232346badc9f03a459cea7113ad4cff28dd2faf36f5635eaa01d5e903d7c", [:mix], [{:ex_keccak, "~> 0.7.3", [hex: :ex_keccak, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "bd1bcbe1d4fac14b90aa132fa54690b36d26e11e3777b755623bf32e66caef85"}, + "ex_abi": {:hex, :ex_abi, "0.6.4", "f722a38298f176dab511cf94627b2815282669255bc2eb834674f23ca71f5cfb", [:mix], [{:ex_keccak, "~> 0.7.3", [hex: :ex_keccak, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "07eaf39b70dd3beac1286c10368d27091a9a64844830eb26a38f1c8d8b19dfbb"}, "ex_cldr": {:hex, :ex_cldr, "2.37.5", "9da6d97334035b961d2c2de167dc6af8cd3e09859301a5b8f49f90bd8b034593", [:mix], [{:cldr_utils, "~> 2.21", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "74ad5ddff791112ce4156382e171a5f5d3766af9d5c4675e0571f081fe136479"}, "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.15.1", "e92ba17c41e7405b7784e0e65f406b5f17cfe313e0e70de9befd653e12854822", [:mix], [{:ex_cldr, "~> 2.34", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "31df8bd37688340f8819bdd770eb17d659652078d34db632b85d4a32864d6a25"}, "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.10.1", "67795eb28f8534a36cbdfeeaea6d3ee587eeac7eafff71968dd046c215d4ec42", [:mix], [{:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "da826148d95c7a1889fd847ae5704c141747bad43a5a44431ae97bced57b0f93"}, From af39e8955632c914615a3907b1b0cfe90964778a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Nov 2023 07:28:48 +0000 Subject: [PATCH 013/607] Bump ex_cldr_units from 3.16.3 to 3.16.4 Bumps [ex_cldr_units](https://github.com/elixir-cldr/cldr_units) from 3.16.3 to 3.16.4. - [Release notes](https://github.com/elixir-cldr/cldr_units/releases) - [Changelog](https://github.com/elixir-cldr/cldr_units/blob/main/CHANGELOG.md) - [Commits](https://github.com/elixir-cldr/cldr_units/compare/v3.16.3...v3.16.4) --- updated-dependencies: - dependency-name: ex_cldr_units dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 5e57591e369c..866ba117b4f0 100644 --- a/mix.lock +++ b/mix.lock @@ -44,9 +44,9 @@ "ex_abi": {:hex, :ex_abi, "0.6.3", "e16f232346badc9f03a459cea7113ad4cff28dd2faf36f5635eaa01d5e903d7c", [:mix], [{:ex_keccak, "~> 0.7.3", [hex: :ex_keccak, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "bd1bcbe1d4fac14b90aa132fa54690b36d26e11e3777b755623bf32e66caef85"}, "ex_cldr": {:hex, :ex_cldr, "2.37.5", "9da6d97334035b961d2c2de167dc6af8cd3e09859301a5b8f49f90bd8b034593", [:mix], [{:cldr_utils, "~> 2.21", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "74ad5ddff791112ce4156382e171a5f5d3766af9d5c4675e0571f081fe136479"}, "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.15.1", "e92ba17c41e7405b7784e0e65f406b5f17cfe313e0e70de9befd653e12854822", [:mix], [{:ex_cldr, "~> 2.34", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "31df8bd37688340f8819bdd770eb17d659652078d34db632b85d4a32864d6a25"}, - "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.10.1", "67795eb28f8534a36cbdfeeaea6d3ee587eeac7eafff71968dd046c215d4ec42", [:mix], [{:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "da826148d95c7a1889fd847ae5704c141747bad43a5a44431ae97bced57b0f93"}, + "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.10.2", "c8dbb3324ca35cea3679a96f4c774cdf4bdd425786a44c4f52aacb57a0cee446", [:mix], [{:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "0cc2124eccffa5438045c2504dd3365490b64065131f58ecc27f344db1edb4b8"}, "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.32.3", "b631ff94c982ec518e46bf4736000a30a33d6b58facc085d5f240305f512ad4a", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7b626ff1e59a0ec9c3c5db5ce9ca91a6995e2ab56426b71f3cbf67181ea225f5"}, - "ex_cldr_units": {:hex, :ex_cldr_units, "3.16.3", "41344ed9f6d5c69baeb4d13dadc80310ca511626f738af2e69ae53dd40fb28a5", [:mix], [{:cldr_utils, "~> 2.24", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.31", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "889dd56084821723ce82c4639d62cfc3513a9ca1ffb9063bcc83e7b3de5bc35b"}, + "ex_cldr_units": {:hex, :ex_cldr_units, "3.16.4", "fee054e9ebed40ef05cbb405cb0c7e7c9fda201f8f03ec0d1e54e879af413246", [:mix], [{:cldr_utils, "~> 2.24", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.31", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7c15c6357dd555a5bc6c72fdeb243e4706a04065753dbd2f40150f062ca996c7"}, "ex_doc": {:hex, :ex_doc, "0.30.9", "d691453495c47434c0f2052b08dd91cc32bc4e1a218f86884563448ee2502dd2", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "d7aaaf21e95dc5cddabf89063327e96867d00013963eadf2c6ad135506a8bc10"}, "ex_json_schema": {:hex, :ex_json_schema, "0.10.1", "e03b746b6675a750c0bb1a5cc919f61353f7ab8450977e11ceede20e6180c560", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "66a64e60dadad89914d92f89c7e7906c57de75a8b79ac2480d0d53e1b8096fb0"}, "ex_keccak": {:hex, :ex_keccak, "0.7.3", "33298f97159f6b0acd28f6e96ce5ea975a0f4a19f85fe615b4f4579b88b24d06", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6.1", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "4c5e6d9d5f77b64ab48769a0166a9814180d40ced68ed74ce60a5174ab55b3fc"}, From e8ccfbbffea8ed597e3bf59772e82570b9b25419 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 1 Nov 2023 18:24:09 +0300 Subject: [PATCH 014/607] Fix for eth_getbalance API v1 endpoint when requesting latest tag --- CHANGELOG.md | 1 + .../controllers/api/rpc/eth_controller_test.exs | 4 +--- apps/explorer/lib/explorer/etherscan/blocks.ex | 16 ++++++++++------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f9b370d63b2..05efb03eeb0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Fixes - [#8784](https://github.com/blockscout/blockscout/pull/8784) - Fix Indexer.Transform.Addresses for non-Suave setup +- [#8770](https://github.com/blockscout/blockscout/pull/8770) - Fix for eth_getbalance API v1 endpoint when requesting latest tag - [#8765](https://github.com/blockscout/blockscout/pull/8765) - Fix for tvl update in market history when row already exists - [#8759](https://github.com/blockscout/blockscout/pull/8759) - Gnosis safe proxy via singleton input - [#8752](https://github.com/blockscout/blockscout/pull/8752) - Add `TOKEN_INSTANCE_OWNER_MIGRATION_ENABLED` env diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs index e80a8bbc7c92..f441a173b1e2 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs @@ -400,9 +400,7 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do test "with a valid address that has a balance", %{conn: conn, api_params: api_params} do block = insert(:block) - address = insert(:address) - - insert(:fetched_balance, block_number: block.number, address_hash: address.hash, value: 1) + address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: block.number) assert response = conn diff --git a/apps/explorer/lib/explorer/etherscan/blocks.ex b/apps/explorer/lib/explorer/etherscan/blocks.ex index d7238fb7a20f..0249f46743d3 100644 --- a/apps/explorer/lib/explorer/etherscan/blocks.ex +++ b/apps/explorer/lib/explorer/etherscan/blocks.ex @@ -11,7 +11,7 @@ defmodule Explorer.Etherscan.Blocks do ] alias Explorer.{Chain, Repo} - alias Explorer.Chain.{Address.CoinBalance, Block, Hash, Wei} + alias Explorer.Chain.{Address, Address.CoinBalance, Block, Hash, Wei} @doc """ Returns the balance of the given address and block combination. @@ -39,12 +39,16 @@ defmodule Explorer.Etherscan.Blocks do end def get_balance_as_of_block(address, :latest) do - case Chain.max_consensus_block_number() do - {:ok, latest_block_number} -> - get_balance_as_of_block(address, latest_block_number) + latest_coin_balance_query = + from( + a in Address, + select: a.fetched_coin_balance, + where: a.hash == ^address + ) - {:error, :not_found} -> - {:error, :not_found} + case Repo.replica().one(latest_coin_balance_query) do + nil -> {:error, :not_found} + coin_balance -> {:ok, coin_balance} end end From 84a58de713e9ea09a419a72d96651c317bd87660 Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Thu, 9 Nov 2023 10:48:45 +0300 Subject: [PATCH 015/607] =?UTF-8?q?Add=20new=20metadata=20fields=20and=20a?= =?UTF-8?q?dd=20x-api-key=20header=20to=20eth-bytecode-db=20r=E2=80=A6=20(?= =?UTF-8?q?#8750)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add new metadata fields and add x-api-key header to eth-bytecode-db request * Add /api/v2/import/smart-contracts/{address_hash} endpoint --- CHANGELOG.md | 1 + .../lib/block_scout_web/api_router.ex | 1 + .../controllers/api/v2/fallback_controller.ex | 2 +- .../controllers/api/v2/import_controller.ex | 57 +++++++++++++++++- .../lib/explorer/chain/smart_contract.ex | 46 ++++++++++++++- .../eth_bytecode_db_interface.ex | 9 +++ .../lib/explorer/smart_contract/helper.ex | 58 +++++++++++++++++++ .../rust_verifier_interface_behaviour.ex | 41 ++++++++----- .../smart_contract/solidity/verifier.ex | 25 ++++---- .../explorer/smart_contract/vyper/verifier.ex | 35 ++++++----- config/runtime.exs | 3 +- 11 files changed, 227 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05efb03eeb0b..2bfd041e2778 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - [#8795](https://github.com/blockscout/blockscout/pull/8795) - Disable catchup indexer by env +- [#8750](https://github.com/blockscout/blockscout/pull/8750) - Support new eth-bytecode-db request metadata fields ### Fixes diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 0ab24f48cd5a..8d500a2a49cb 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -181,6 +181,7 @@ defmodule BlockScoutWeb.ApiRouter do pipe_through(:api_v2_no_session) post("/token-info", V2.ImportController, :import_token_info) + get("/smart-contracts/:address_hash_param", V2.ImportController, :try_to_search_contract) end scope "/v2", as: :api_v2 do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex index 79cad2ebffed..56e66c7d0a44 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex @@ -130,7 +130,7 @@ defmodule BlockScoutWeb.API.V2.FallbackController do |> render(:message, %{message: @restricted_access}) end - def call(conn, {:already_verified, true}) do + def call(conn, {:already_verified, _}) do Logger.error(fn -> ["#{@verification_failed}: #{@already_verified}"] end) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/import_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/import_controller.ex index bf5ebe6f3280..7c6b59c3bf99 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/import_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/import_controller.ex @@ -3,7 +3,11 @@ defmodule BlockScoutWeb.API.V2.ImportController do alias BlockScoutWeb.API.V2.ApiView alias Explorer.{Chain, Repo} - alias Explorer.Chain.Token + alias Explorer.Chain.{Data, Token} + alias Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand + alias Explorer.SmartContract.EthBytecodeDBInterface + + import Explorer.SmartContract.Helper, only: [prepare_bytecode_for_microservice: 3, contract_creation_input: 1] require Logger @api_true [api?: true] @@ -48,6 +52,47 @@ defmodule BlockScoutWeb.API.V2.ImportController do end end + @doc """ + Function to handle request at: + `/api/v2/smart-contracts/{address_hash_param}` + + Needed to try to import unverified smart contracts via eth-bytecode-db (`/api/v2/bytecodes/sources:search` method). + Protected by `x-api-key` header. + """ + @spec try_to_search_contract(Plug.Conn.t(), map()) :: + {:already_verified, nil | Explorer.Chain.SmartContract.t()} + | {:api_key, nil | binary()} + | {:format, :error} + | {:not_found, {:error, :not_found}} + | {:sensitive_endpoints_api_key, any()} + | Plug.Conn.t() + def try_to_search_contract(conn, %{"address_hash_param" => address_hash_string}) do + with {:sensitive_endpoints_api_key, api_key} when not is_nil(api_key) <- + {:sensitive_endpoints_api_key, Application.get_env(:block_scout_web, :sensitive_endpoints_api_key)}, + {:api_key, ^api_key} <- {:api_key, get_api_key_header(conn)}, + {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:not_found, {:ok, address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)}, + {:already_verified, smart_contract} when is_nil(smart_contract) <- + {:already_verified, Chain.address_hash_to_smart_contract(address_hash, @api_true)} do + creation_tx_input = contract_creation_input(address.hash) + + with {:ok, %{"sourceType" => type} = source} <- + %{} + |> prepare_bytecode_for_microservice(creation_tx_input, Data.to_string(address.contract_code)) + |> EthBytecodeDBInterface.search_contract_in_eth_bytecode_internal_db(), + {:ok, _} <- LookUpSmartContractSourcesOnDemand.process_contract_source(type, source, address.hash) do + conn + |> put_view(ApiView) + |> render(:message, %{message: "Success"}) + else + _ -> + conn + |> put_view(ApiView) + |> render(:message, %{message: "Contract was not imported"}) + end + end + end + defp valid_url?(url) when is_binary(url) do uri = URI.parse(url) uri.scheme != nil && uri.host =~ "." @@ -74,4 +119,14 @@ defmodule BlockScoutWeb.API.V2.ImportController do end defp put_token_string_field(changeset, _token_symbol, _field), do: changeset + + defp get_api_key_header(conn) do + case get_req_header(conn, "x-api-key") do + [api_key] -> + api_key + + _ -> + nil + end + end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index 18590460e672..5391349acf09 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -16,7 +16,7 @@ defmodule Explorer.Chain.SmartContract do alias EthereumJSONRPC.Contract alias Explorer.Counters.AverageBlockTime alias Explorer.{Chain, Repo} - alias Explorer.Chain.{Address, ContractMethod, DecompiledSmartContract, Hash} + alias Explorer.Chain.{Address, ContractMethod, Data, DecompiledSmartContract, Hash, InternalTransaction, Transaction} alias Explorer.Chain.SmartContract.ExternalLibrary alias Explorer.SmartContract.Reader alias Timex.Duration @@ -965,4 +965,48 @@ defmodule Explorer.Chain.SmartContract do Chain.select_repo(options).one(query) end + + @doc """ + Extracts creation bytecode (`init`) and transaction (`tx`) or + internal transaction (`internal_tx`) where the contract was created. + """ + @spec creation_tx_with_bytecode(binary() | Hash.t()) :: + %{init: binary(), tx: Transaction.t()} | %{init: binary(), internal_tx: InternalTransaction.t()} | nil + def creation_tx_with_bytecode(address_hash) do + creation_tx_query = + from( + tx in Transaction, + where: tx.created_contract_address_hash == ^address_hash, + where: tx.status == ^1 + ) + + tx = + creation_tx_query + |> Repo.one() + + if tx do + with %{input: input} <- tx do + %{init: Data.to_string(input), tx: tx} + end + else + creation_int_tx_query = + from( + itx in InternalTransaction, + join: t in assoc(itx, :transaction), + where: itx.created_contract_address_hash == ^address_hash, + where: t.status == ^1 + ) + + internal_tx = creation_int_tx_query |> Repo.one() + + case internal_tx do + %{init: init} -> + init_str = Data.to_string(init) + %{init: init_str, internal_tx: internal_tx} + + _ -> + nil + end + end + end end diff --git a/apps/explorer/lib/explorer/smart_contract/eth_bytecode_db_interface.ex b/apps/explorer/lib/explorer/smart_contract/eth_bytecode_db_interface.ex index b4ffab08328c..82b83c053c7d 100644 --- a/apps/explorer/lib/explorer/smart_contract/eth_bytecode_db_interface.ex +++ b/apps/explorer/lib/explorer/smart_contract/eth_bytecode_db_interface.ex @@ -17,6 +17,15 @@ defmodule Explorer.SmartContract.EthBytecodeDBInterface do end end + @doc """ + Function to search smart contracts in eth-bytecode-db, similar to `search_contract/2` but + this function uses only `/api/v2/bytecodes/sources:search` method + """ + @spec search_contract_in_eth_bytecode_internal_db(map()) :: {:error, any} | {:ok, any} + def search_contract_in_eth_bytecode_internal_db(%{"bytecode" => _, "bytecodeType" => _} = body) do + http_post_request(bytecode_search_sources_url(), body) + end + def process_verifier_response(%{"sourcifySources" => [src | _]}) do {:ok, Map.put(src, "sourcify?", true)} end diff --git a/apps/explorer/lib/explorer/smart_contract/helper.ex b/apps/explorer/lib/explorer/smart_contract/helper.ex index c73ea123b919..544cb7c19e77 100644 --- a/apps/explorer/lib/explorer/smart_contract/helper.ex +++ b/apps/explorer/lib/explorer/smart_contract/helper.ex @@ -4,6 +4,7 @@ defmodule Explorer.SmartContract.Helper do """ alias Explorer.Chain + alias Explorer.Chain.{Hash, SmartContract} alias Phoenix.HTML def queriable_method?(method) do @@ -135,4 +136,61 @@ defmodule Explorer.SmartContract.Helper do nil end end + + @doc """ + Returns a tuple: `{creation_bytecode, deployed_bytecode, metadata}` where `metadata` is a map: + { + "blockNumber": "string", + "chainId": "string", + "contractAddress": "string", + "creationCode": "string", + "deployer": "string", + "runtimeCode": "string", + "transactionHash": "string", + "transactionIndex": "string" + } + + Metadata will be sent to a verifier microservice + """ + @spec fetch_data_for_verification(binary() | Hash.t()) :: {binary() | nil, binary(), map()} + def fetch_data_for_verification(address_hash) do + deployed_bytecode = Chain.smart_contract_bytecode(address_hash) + + metadata = %{ + "contractAddress" => to_string(address_hash), + "runtimeCode" => to_string(deployed_bytecode), + "chainId" => Application.get_env(:block_scout_web, :chain_id) + } + + case SmartContract.creation_tx_with_bytecode(address_hash) do + %{init: init, tx: tx} -> + {init, deployed_bytecode, tx |> tx_to_metadata(init) |> Map.merge(metadata)} + + %{init: init, internal_tx: internal_tx} -> + {init, deployed_bytecode, internal_tx |> internal_tx_to_metadata(init) |> Map.merge(metadata)} + + _ -> + {nil, deployed_bytecode, metadata} + end + end + + defp tx_to_metadata(tx, init) do + %{ + "blockNumber" => to_string(tx.block_number), + "transactionHash" => to_string(tx.hash), + "transactionIndex" => to_string(tx.index), + "deployer" => to_string(tx.from_address_hash), + "creationCode" => to_string(init) + } + end + + defp internal_tx_to_metadata(internal_tx, init) do + %{ + "blockNumber" => to_string(internal_tx.block_number), + "transactionHash" => to_string(internal_tx.transaction_hash), + "transactionIndex" => to_string(internal_tx.transaction_index), + "deployer" => to_string(internal_tx.from_address_hash), + "creationCode" => to_string(init) + } + end end diff --git a/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex b/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex index 799fd7bd0e0a..2466e6e1d572 100644 --- a/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex +++ b/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex @@ -22,9 +22,9 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do "optimizationRuns" => _, "libraries" => _ } = body, - address_hash + metadata ) do - http_post_request(solidity_multiple_files_verification_url(), append_metadata(body, address_hash)) + http_post_request(solidity_multiple_files_verification_url(), append_metadata(body, metadata), true) end def verify_standard_json_input( @@ -34,9 +34,9 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do "compilerVersion" => _, "input" => _ } = body, - address_hash + metadata ) do - http_post_request(solidity_standard_json_verification_url(), append_metadata(body, address_hash)) + http_post_request(solidity_standard_json_verification_url(), append_metadata(body, metadata), true) end def vyper_verify_multipart( @@ -46,9 +46,9 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do "compilerVersion" => _, "sourceFiles" => _ } = body, - address_hash + metadata ) do - http_post_request(vyper_multiple_files_verification_url(), append_metadata(body, address_hash)) + http_post_request(vyper_multiple_files_verification_url(), append_metadata(body, metadata), true) end def vyper_verify_standard_json( @@ -58,15 +58,17 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do "compilerVersion" => _, "input" => _ } = body, - address_hash + metadata ) do - http_post_request(vyper_standard_json_verification_url(), append_metadata(body, address_hash)) + http_post_request(vyper_standard_json_verification_url(), append_metadata(body, metadata), true) end - def http_post_request(url, body) do + def http_post_request(url, body, is_verification_request? \\ false) do headers = [{"Content-Type", "application/json"}] - case HTTPoison.post(url, Jason.encode!(body), headers, recv_timeout: @post_timeout) do + case HTTPoison.post(url, Jason.encode!(body), maybe_put_api_key_header(headers, is_verification_request?), + recv_timeout: @post_timeout + ) do {:ok, %Response{body: body, status_code: _}} -> process_verifier_response(body) @@ -86,6 +88,18 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do end end + defp maybe_put_api_key_header(headers, false), do: headers + + defp maybe_put_api_key_header(headers, true) do + api_key = Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)[:api_key] + + if api_key do + [{"x-api-key", api_key} | headers] + else + headers + end + end + def http_get_request(url) do case HTTPoison.get(url) do {:ok, %Response{body: body, status_code: 200}} -> @@ -163,12 +177,9 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do def enabled?, do: Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)[:enabled] - defp append_metadata(body, address_hash) when is_map(body) do + defp append_metadata(body, metadata) when is_map(body) do body - |> Map.put("metadata", %{ - "chainId" => Application.get_env(:block_scout_web, :chain_id), - "contractAddress" => to_string(address_hash) - }) + |> Map.put("metadata", metadata) end end end diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex b/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex index ee5554a0361d..8eb602ba3559 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex @@ -9,9 +9,12 @@ defmodule Explorer.SmartContract.Solidity.Verifier do """ import Explorer.SmartContract.Helper, - only: [cast_libraries: 1, prepare_bytecode_for_microservice: 3, contract_creation_input: 1] + only: [ + cast_libraries: 1, + fetch_data_for_verification: 1, + prepare_bytecode_for_microservice: 3 + ] - # import Explorer.Chain.SmartContract, only: [:function_description] alias ABI.{FunctionSelector, TypeDecoder} alias Explorer.Chain alias Explorer.Chain.{Data, Hash, SmartContract} @@ -40,9 +43,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do end defp evaluate_authenticity_inner(true, address_hash, params) do - deployed_bytecode = Chain.smart_contract_bytecode(address_hash) - - creation_tx_input = contract_creation_input(address_hash) + {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) %{} |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) @@ -54,7 +55,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do |> Map.put("optimizationRuns", prepare_optimization_runs(params["optimization"], params["optimization_runs"])) |> Map.put("evmVersion", Map.get(params, "evm_version", "default")) |> Map.put("compilerVersion", params["compiler_version"]) - |> RustVerifierInterface.verify_multi_part(address_hash) + |> RustVerifierInterface.verify_multi_part(verifier_metadata) end defp evaluate_authenticity_inner(false, address_hash, params) do @@ -124,14 +125,12 @@ defmodule Explorer.SmartContract.Solidity.Verifier do end def evaluate_authenticity_via_standard_json_input_inner(true, address_hash, params, json_input) do - deployed_bytecode = Chain.smart_contract_bytecode(address_hash) - - creation_tx_input = contract_creation_input(address_hash) + {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) %{"compilerVersion" => params["compiler_version"]} |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) |> Map.put("input", json_input) - |> RustVerifierInterface.verify_standard_json_input(address_hash) + |> RustVerifierInterface.verify_standard_json_input(verifier_metadata) end def evaluate_authenticity_via_standard_json_input_inner(false, address_hash, params, json_input) do @@ -139,9 +138,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do end def evaluate_authenticity_via_multi_part_files(address_hash, params, files) do - deployed_bytecode = Chain.smart_contract_bytecode(address_hash) - - creation_tx_input = contract_creation_input(address_hash) + {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) %{} |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) @@ -150,7 +147,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do |> Map.put("optimizationRuns", prepare_optimization_runs(params["optimization"], params["optimization_runs"])) |> Map.put("evmVersion", Map.get(params, "evm_version", "default")) |> Map.put("compilerVersion", params["compiler_version"]) - |> RustVerifierInterface.verify_multi_part(address_hash) + |> RustVerifierInterface.verify_multi_part(verifier_metadata) end defp verify(address_hash, params, json_input) do diff --git a/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex b/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex index 3d66e1539a01..3ca5ac90994c 100644 --- a/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex @@ -9,10 +9,11 @@ defmodule Explorer.SmartContract.Vyper.Verifier do """ require Logger - alias Explorer.Chain alias Explorer.SmartContract.Vyper.CodeCompiler alias Explorer.SmartContract.RustVerifierInterface - import Explorer.SmartContract.Helper, only: [prepare_bytecode_for_microservice: 3, contract_creation_input: 1] + + import Explorer.SmartContract.Helper, + only: [fetch_data_for_verification: 1, prepare_bytecode_for_microservice: 3, contract_creation_input: 1] def evaluate_authenticity(_, %{"contract_source_code" => ""}), do: {:error, :contract_source_code} @@ -34,7 +35,7 @@ defmodule Explorer.SmartContract.Vyper.Verifier do def evaluate_authenticity(address_hash, params, files) do try do if RustVerifierInterface.enabled?() do - vyper_verify_multipart(params, fetch_bytecode(address_hash), params["evm_version"], files, address_hash) + vyper_verify_multipart(params, params["evm_version"], files, address_hash) end rescue exception -> @@ -50,7 +51,7 @@ defmodule Explorer.SmartContract.Vyper.Verifier do def evaluate_authenticity_standard_json(%{"address_hash" => address_hash} = params) do try do if RustVerifierInterface.enabled?() do - vyper_verify_standard_json(params, fetch_bytecode(address_hash), address_hash) + vyper_verify_standard_json(params, address_hash) end rescue exception -> @@ -66,7 +67,6 @@ defmodule Explorer.SmartContract.Vyper.Verifier do defp evaluate_authenticity_inner(true, address_hash, params) do vyper_verify_multipart( params, - fetch_bytecode(address_hash), params["evm_version"], %{ "#{params["name"]}.vy" => params["contract_source_code"] @@ -79,13 +79,6 @@ defmodule Explorer.SmartContract.Vyper.Verifier do verify(address_hash, params) end - def fetch_bytecode(address_hash) do - deployed_bytecode = Chain.smart_contract_bytecode(address_hash) - creation_tx_input = contract_creation_input(address_hash) - - prepare_bytecode_for_microservice(%{}, creation_tx_input, deployed_bytecode) - end - defp verify(address_hash, params) do contract_source_code = Map.fetch!(params, "contract_source_code") compiler_version = Map.fetch!(params, "compiler_version") @@ -123,19 +116,25 @@ defmodule Explorer.SmartContract.Vyper.Verifier do end end - defp vyper_verify_multipart(params, bytecode_map, evm_version, files, address_hash) do - bytecode_map + defp vyper_verify_multipart(params, evm_version, files, address_hash) do + {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) + + %{} + |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) |> Map.put("evmVersion", evm_version) |> Map.put("sourceFiles", files) |> Map.put("compilerVersion", params["compiler_version"]) |> Map.put("interfaces", params["interfaces"] || %{}) - |> RustVerifierInterface.vyper_verify_multipart(address_hash) + |> RustVerifierInterface.vyper_verify_multipart(verifier_metadata) end - defp vyper_verify_standard_json(params, bytecode_map, address_hash) do - bytecode_map + defp vyper_verify_standard_json(params, address_hash) do + {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) + + %{} + |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) |> Map.put("compilerVersion", params["compiler_version"]) |> Map.put("input", params["input"]) - |> RustVerifierInterface.vyper_verify_standard_json(address_hash) + |> RustVerifierInterface.vyper_verify_standard_json(verifier_metadata) end end diff --git a/config/runtime.exs b/config/runtime.exs index 2b3f4d44fcda..8c8baee4fa63 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -379,7 +379,8 @@ config :explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, service_url: System.get_env("MICROSERVICE_SC_VERIFIER_URL") || "https://eth-bytecode-db.services.blockscout.com/", enabled: enabled?, type: type, - eth_bytecode_db?: enabled? && type == "eth_bytecode_db" + eth_bytecode_db?: enabled? && type == "eth_bytecode_db", + api_key: System.get_env("MICROSERVICE_SC_VERIFIER_API_KEY") config :explorer, Explorer.Visualize.Sol2uml, service_url: System.get_env("MICROSERVICE_VISUALIZE_SOL2UML_URL"), From f9d5377e64d2c6bfb16c7298be448fddc3e0477a Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Thu, 9 Nov 2023 11:09:38 +0300 Subject: [PATCH 016/607] Add endpoint url to the block_scout_web logging (#8609) * Replace :console logger backend with LoggerJSON; Add endpoint url to the block_scout_web logging * Move logger_json dependency * Turn json logs only for prod environment * Disable debug ecto logs for test environment * Return HTTP method to logs --- CHANGELOG.md | 1 + .../lib/block_scout_web/plug/logger.ex | 47 +++++++------------ apps/block_scout_web/mix.exs | 3 +- apps/ethereum_jsonrpc/mix.exs | 3 +- apps/explorer/config/test.exs | 12 +++-- apps/explorer/mix.exs | 3 +- apps/indexer/mix.exs | 3 +- config/config.exs | 10 +++- config/dev.exs | 2 + config/prod.exs | 4 +- config/test.exs | 2 + mix.lock | 1 + 12 files changed, 51 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bfd041e2778..bac2acc94790 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - [#8795](https://github.com/blockscout/blockscout/pull/8795) - Disable catchup indexer by env - [#8750](https://github.com/blockscout/blockscout/pull/8750) - Support new eth-bytecode-db request metadata fields +- [#8609](https://github.com/blockscout/blockscout/pull/8609) - Change logs format to JSON; Add endpoint url to the block_scout_web logging ### Fixes diff --git a/apps/block_scout_web/lib/block_scout_web/plug/logger.ex b/apps/block_scout_web/lib/block_scout_web/plug/logger.ex index 883dd95ef3f8..b61c9cafe4c6 100644 --- a/apps/block_scout_web/lib/block_scout_web/plug/logger.ex +++ b/apps/block_scout_web/lib/block_scout_web/plug/logger.ex @@ -19,53 +19,40 @@ defmodule BlockScoutWeb.Plug.Logger do @impl true def call(conn, opts) do level = Keyword.get(opts, :log, :info) - application = Keyword.get(opts, :application, :block_scout_web) - - log(application, conn, level, opts) start = System.monotonic_time() Conn.register_before_send(conn, fn conn -> + stop = System.monotonic_time() + diff = System.convert_time_unit(stop - start, :native, :microsecond) + status = Integer.to_string(conn.status) + Logger.log( level, fn -> - stop = System.monotonic_time() - diff = System.convert_time_unit(stop - start, :native, :microsecond) - status = Integer.to_string(conn.status) - - [connection_type(conn), ?\s, status, " in ", formatted_diff(diff)] + [connection_type(conn), ?\s, status, " in ", formatted_diff(diff), " on ", conn.method, ?\s, endpoint(conn)] end, - opts + Keyword.merge( + [duration: diff, status: status, unit: "microsecond", endpoint: endpoint(conn), method: conn.method], + opts + ) ) conn end) end - defp log(:api, conn, level, opts) do - endpoint = - if conn.query_string do - "#{conn.request_path}?#{conn.query_string}" - else - conn.request_path - end - - Logger.log(level, endpoint, opts) - end - - defp log(_application, conn, level, opts) do - Logger.log( - level, - fn -> - [conn.method, ?\s, conn.request_path] - end, - opts - ) - end - defp formatted_diff(diff) when diff > 1000, do: [diff |> div(1000) |> Integer.to_string(), "ms"] defp formatted_diff(diff), do: [Integer.to_string(diff), "µs"] defp connection_type(%{state: :set_chunked}), do: "Chunked" defp connection_type(_), do: "Sent" + + defp endpoint(conn) do + if conn.query_string do + "#{conn.request_path}?#{conn.query_string}" + else + conn.request_path + end + end end diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index e0283df5a7a5..17b4a79de407 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -132,7 +132,8 @@ defmodule BlockScoutWeb.Mixfile do {:ex_json_schema, "~> 0.10.1"}, {:ueberauth, "~> 0.7"}, {:ueberauth_auth0, "~> 2.0"}, - {:bureaucrat, "~> 0.2.9", only: :test} + {:bureaucrat, "~> 0.2.9", only: :test}, + {:logger_json, "~> 5.1"} ] end diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index 9159a8c9852b..d4496a620da3 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -85,7 +85,8 @@ defmodule EthereumJsonrpc.MixProject do {:decimal, "~> 2.0"}, {:decorator, "~> 1.4"}, {:hackney, "~> 1.18"}, - {:poolboy, "~> 1.5.2"} + {:poolboy, "~> 1.5.2"}, + {:logger_json, "~> 5.1"} ] end end diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index 8a08ee60acf5..955f9df84267 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -12,7 +12,8 @@ config :explorer, Explorer.Repo, ownership_timeout: :timer.minutes(7), timeout: :timer.seconds(60), queue_target: 1000, - migration_lock: nil + migration_lock: nil, + log: false # Configure API database config :explorer, Explorer.Repo.Replica1, @@ -26,7 +27,8 @@ config :explorer, Explorer.Repo.Replica1, enable_caching_implementation_data_of_proxy: true, avg_block_time_as_ttl_cached_implementation_data_of_proxy: false, fallback_ttl_cached_implementation_data_of_proxy: :timer.seconds(20), - implementation_data_fetching_timeout: :timer.seconds(20) + implementation_data_fetching_timeout: :timer.seconds(20), + log: false # Configure API database config :explorer, Explorer.Repo.Account, @@ -36,7 +38,8 @@ config :explorer, Explorer.Repo.Account, # Default of `5_000` was too low for `BlockFetcher` test ownership_timeout: :timer.minutes(1), timeout: :timer.seconds(60), - queue_target: 1000 + queue_target: 1000, + log: false for repo <- [Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Suave] do config :explorer, repo, @@ -46,7 +49,8 @@ for repo <- [Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, Explorer.Rep # Default of `5_000` was too low for `BlockFetcher` test ownership_timeout: :timer.minutes(1), timeout: :timer.seconds(60), - queue_target: 1000 + queue_target: 1000, + log: false end config :logger, :explorer, diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 1a43393fa626..e976d63ce455 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -117,7 +117,8 @@ defmodule Explorer.Mixfile do {:cbor, "~> 1.0"}, {:cloak_ecto, "~> 1.2.0"}, {:redix, "~> 1.1"}, - {:hammer_backend_redis, "~> 6.1"} + {:hammer_backend_redis, "~> 6.1"}, + {:logger_json, "~> 5.1"} ] end diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs index f388f635319c..6444e6596182 100644 --- a/apps/indexer/mix.exs +++ b/apps/indexer/mix.exs @@ -55,7 +55,8 @@ defmodule Indexer.MixProject do # Tracing {:spandex, "~> 3.0"}, # `:spandex` integration with Datadog - {:spandex_datadog, "~> 1.0"} + {:spandex_datadog, "~> 1.0"}, + {:logger_json, "~> 5.1"} ] end diff --git a/config/config.exs b/config/config.exs index 554d3ae557b1..f09d21a3f065 100644 --- a/config/config.exs +++ b/config/config.exs @@ -38,9 +38,17 @@ config :logger, {LoggerFileBackend, :api}, {LoggerFileBackend, :block_import_timings}, {LoggerFileBackend, :account}, - {LoggerFileBackend, :api_v2} + {LoggerFileBackend, :api_v2}, + LoggerJSON ] +config :logger_json, :backend, + metadata: + ~w(application fetcher request_id first_block_number last_block_number missing_block_range_count missing_block_count + block_number step count error_count shrunk import_id transaction_id duration status unit endpoint method)a, + json_encoder: Jason, + formatter: LoggerJSON.Formatters.BasicLogger + config :logger, :console, # Use same format for all loggers, even though the level should only ever be `:error` for `:error` backend format: "$dateT$time $metadata[$level] $message\n", diff --git a/config/dev.exs b/config/dev.exs index 323212e107f7..27154824a939 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -3,6 +3,8 @@ import Config # DO NOT make it `:debug` or all Ecto logs will be shown for indexer config :logger, :console, level: :info +config :logger_json, :backend, level: :none + config :logger, :ecto, level: :debug, path: Path.absname("logs/dev/ecto.log") diff --git a/config/prod.exs b/config/prod.exs index 1d9be54e3fc6..2e8c9db24d97 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -2,7 +2,9 @@ import Config # Do not print debug messages in production -config :logger, :console, level: :info +config :logger, :console, level: :none + +config :logger_json, :backend, level: :info config :logger, :ecto, level: :info, diff --git a/config/test.exs b/config/test.exs index 92ddca6a93a0..c979cd834877 100644 --- a/config/test.exs +++ b/config/test.exs @@ -4,6 +4,8 @@ import Config config :logger, :console, level: :warn +config :logger_json, :backend, level: :none + config :logger, :ecto, level: :warn, path: Path.absname("logs/test/ecto.log") diff --git a/mix.lock b/mix.lock index 03e579b8ae39..605ac6bec0ec 100644 --- a/mix.lock +++ b/mix.lock @@ -75,6 +75,7 @@ "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm", "fc3499fed7a726995aa659143a248534adc754ebd16ccd437cd93b649a95091f"}, "junit_formatter": {:hex, :junit_formatter, "3.3.1", "c729befb848f1b9571f317d2fefa648e9d4869befc4b2980daca7c1edc468e40", [:mix], [], "hexpm", "761fc5be4b4c15d8ba91a6dafde0b2c2ae6db9da7b8832a55b5a1deb524da72b"}, "logger_file_backend": {:hex, :logger_file_backend, "0.0.13", "df07b14970e9ac1f57362985d76e6f24e3e1ab05c248055b7d223976881977c2", [:mix], [], "hexpm", "71a453a7e6e899ae4549fb147b1c6621f4233f8f48f58ca10a64ec67b6c50018"}, + "logger_json": {:hex, :logger_json, "5.1.2", "7dde5f6dff814aba033f045a3af9408f5459bac72357dc533276b47045371ecf", [:mix], [{:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "ed42047e5c57a60d0fa1450aef36bc016d0f9a5e6c0807ebb0c03d8895fb6ebc"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, From 150533e5b846e3266a15f346c5ab6ba02a90b9c9 Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Thu, 9 Nov 2023 13:28:12 +0300 Subject: [PATCH 017/607] Add possibility to search tokens by address hash (#8768) * Add possibility to search tokens by address hash * Changelog + remove dbg calls * Refactor searching queries set of selected fields * Final polishing --------- Co-authored-by: Viktor Baranov --- CHANGELOG.md | 1 + .../account/api/v1/user_controller_test.exs | 2 +- .../api/v2/search_controller_test.exs | 50 +++ apps/explorer/lib/explorer/chain/search.ex | 303 +++++++++--------- 4 files changed, 206 insertions(+), 150 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bac2acc94790..bbd2315d761a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - [#8795](https://github.com/blockscout/blockscout/pull/8795) - Disable catchup indexer by env +- [#8768](https://github.com/blockscout/blockscout/pull/8768) - Add possibility to search tokens by address hash - [#8750](https://github.com/blockscout/blockscout/pull/8750) - Support new eth-bytecode-db request metadata fields - [#8609](https://github.com/blockscout/blockscout/pull/8609) - Change logs format to JSON; Add endpoint url to the block_scout_web logging diff --git a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs index 2a8639bfdc62..f32b5727e727 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs @@ -554,7 +554,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do response_1 = conn - |> get("/api/account/v2/user/watchlist", response["next_page_params"] |> dbg()) + |> get("/api/account/v2/user/watchlist", response["next_page_params"]) |> json_response(200) check_paginated_response(response, response_1, tags_address) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs index 6804f778f0f7..ee9ee43a0371 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs @@ -3,6 +3,7 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do alias Explorer.Chain.{Address, Block} alias Explorer.Repo + alias Explorer.Tags.AddressTag setup do insert(:block) @@ -171,6 +172,35 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do assert item["is_verified_via_admin_panel"] == token.is_verified_via_admin_panel end + test "search token by hash", %{conn: conn} do + token = insert(:unique_token) + + request = get(conn, "/api/v2/search?q=#{token.contract_address_hash}") + assert response = json_response(request, 200) + + assert Enum.count(response["items"]) == 2 + assert response["next_page_params"] == nil + + item = Enum.at(response["items"], 0) + + assert item["type"] == "token" + assert item["name"] == token.name + assert item["symbol"] == token.symbol + assert item["address"] == Address.checksum(token.contract_address_hash) + assert item["token_url"] =~ Address.checksum(token.contract_address_hash) + assert item["address_url"] =~ Address.checksum(token.contract_address_hash) + assert item["token_type"] == token.type + assert item["is_smart_contract_verified"] == token.contract_address.verified + assert item["exchange_rate"] == (token.fiat_value && to_string(token.fiat_value)) + assert item["total_supply"] == to_string(token.total_supply) + assert item["icon_url"] == token.icon_url + assert item["is_verified_via_admin_panel"] == token.is_verified_via_admin_panel + + item_1 = Enum.at(response["items"], 1) + + assert item_1["type"] == "address" + end + test "search transaction", %{conn: conn} do tx = insert(:transaction) @@ -222,6 +252,26 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do assert item["url"] =~ Address.checksum(tag.address.hash) assert item["is_smart_contract_verified"] == tag.address.verified end + + test "check that simultaneous search of ", %{conn: conn} do + block = insert(:block) + + insert(:smart_contract, name: to_string(block.number)) + insert(:token, name: to_string(block.number)) + + insert(:address_to_tag, + tag: %AddressTag{ + label: "qwerty", + display_name: to_string(block.number) + } + ) + + request = get(conn, "/api/v2/search?q=#{block.number}") + assert response = json_response(request, 200) + + assert Enum.count(response["items"]) == 4 + assert response["next_page_params"] == nil + end end describe "/search/check-redirect" do diff --git a/apps/explorer/lib/explorer/chain/search.ex b/apps/explorer/lib/explorer/chain/search.ex index b2439545a424..64e8c1640645 100644 --- a/apps/explorer/lib/explorer/chain/search.ex +++ b/apps/explorer/lib/explorer/chain/search.ex @@ -4,6 +4,7 @@ defmodule Explorer.Chain.Search do """ import Ecto.Query, only: [ + dynamic: 2, from: 2, limit: 2, order_by: 3, @@ -34,7 +35,7 @@ defmodule Explorer.Chain.Search do case prepare_search_term(string) do {:some, term} -> - tokens_query = search_token_query(term) + tokens_query = search_token_query(string, term) contracts_query = search_contract_query(term) labels_query = search_label_query(term) tx_query = search_tx_query(string) @@ -114,8 +115,8 @@ defmodule Explorer.Chain.Search do case prepare_search_term(search_query) do {:some, term} -> tokens_result = - term - |> search_token_query() + search_query + |> search_token_query(term) |> order_by([token], desc_nulls_last: token.circulating_market_cap, desc_nulls_last: token.fiat_value, @@ -204,6 +205,27 @@ defmodule Explorer.Chain.Search do end defp search_label_query(term) do + label_search_fields = %{ + address_hash: dynamic([att, _, _], att.address_hash), + tx_hash: dynamic([_, _, _], type(^nil, :binary)), + block_hash: dynamic([_, _, _], type(^nil, :binary)), + type: "label", + name: dynamic([_, at, _], at.display_name), + symbol: nil, + holder_count: nil, + inserted_at: dynamic([att, _, _], att.inserted_at), + block_number: 0, + icon_url: nil, + token_type: nil, + timestamp: dynamic([_, _, _], type(^nil, :utc_datetime_usec)), + verified: dynamic([_, _, smart_contract], not is_nil(smart_contract)), + exchange_rate: nil, + total_supply: nil, + circulating_market_cap: nil, + priority: 1, + is_verified_via_admin_panel: nil + } + inner_query = from(tag in AddressTag, where: fragment("to_tsvector('english', ?) @@ to_tsquery(?)", tag.display_name, ^term), @@ -215,88 +237,105 @@ defmodule Explorer.Chain.Search do on: att.tag_id == at.id, left_join: smart_contract in SmartContract, on: att.address_hash == smart_contract.address_hash, - select: %{ - address_hash: att.address_hash, - tx_hash: fragment("CAST(NULL AS bytea)"), - block_hash: fragment("CAST(NULL AS bytea)"), - type: "label", - name: at.display_name, - symbol: ^nil, - holder_count: ^nil, - inserted_at: att.inserted_at, - block_number: 0, - icon_url: nil, - token_type: nil, - timestamp: fragment("NULL::timestamp without time zone"), - verified: not is_nil(smart_contract), - exchange_rate: nil, - total_supply: nil, - circulating_market_cap: nil, - priority: 1, - is_verified_via_admin_panel: nil - } + select: ^label_search_fields ) end - defp search_token_query(term) do - from(token in Token, - left_join: smart_contract in SmartContract, - on: token.contract_address_hash == smart_contract.address_hash, - where: fragment("to_tsvector('english', ? || ' ' || ?) @@ to_tsquery(?)", token.symbol, token.name, ^term), - select: %{ - address_hash: token.contract_address_hash, - tx_hash: fragment("CAST(NULL AS bytea)"), - block_hash: fragment("CAST(NULL AS bytea)"), - type: "token", - name: token.name, - symbol: token.symbol, - holder_count: token.holder_count, - inserted_at: token.inserted_at, - block_number: 0, - icon_url: token.icon_url, - token_type: token.type, - timestamp: fragment("NULL::timestamp without time zone"), - verified: not is_nil(smart_contract), - exchange_rate: token.fiat_value, - total_supply: token.total_supply, - circulating_market_cap: token.circulating_market_cap, - priority: 0, - is_verified_via_admin_panel: token.is_verified_via_admin_panel - } - ) + defp search_token_query(string, term) do + token_search_fields = %{ + address_hash: dynamic([token, _], token.contract_address_hash), + tx_hash: dynamic([_, _], type(^nil, :binary)), + block_hash: dynamic([_, _], type(^nil, :binary)), + type: "token", + name: dynamic([token, _], token.name), + symbol: dynamic([token, _], token.symbol), + holder_count: dynamic([token, _], token.holder_count), + inserted_at: dynamic([token, _], token.inserted_at), + block_number: 0, + icon_url: dynamic([token, _], token.icon_url), + token_type: dynamic([token, _], token.type), + timestamp: dynamic([_, _], type(^nil, :utc_datetime_usec)), + verified: dynamic([_, smart_contract], not is_nil(smart_contract)), + exchange_rate: dynamic([token, _], token.fiat_value), + total_supply: dynamic([token, _], token.total_supply), + circulating_market_cap: dynamic([token, _], token.circulating_market_cap), + priority: 0, + is_verified_via_admin_panel: dynamic([token, _], token.is_verified_via_admin_panel) + } + + case Chain.string_to_address_hash(string) do + {:ok, address_hash} -> + from(token in Token, + left_join: smart_contract in SmartContract, + on: token.contract_address_hash == smart_contract.address_hash, + where: token.contract_address_hash == ^address_hash, + select: ^token_search_fields + ) + + _ -> + from(token in Token, + left_join: smart_contract in SmartContract, + on: token.contract_address_hash == smart_contract.address_hash, + where: fragment("to_tsvector('english', ? || ' ' || ?) @@ to_tsquery(?)", token.symbol, token.name, ^term), + select: ^token_search_fields + ) + end end defp search_contract_query(term) do + contract_search_fields = %{ + address_hash: dynamic([smart_contract, _], smart_contract.address_hash), + tx_hash: dynamic([_, _], type(^nil, :binary)), + block_hash: dynamic([_, _], type(^nil, :binary)), + type: "contract", + name: dynamic([smart_contract, _], smart_contract.name), + symbol: nil, + holder_count: nil, + inserted_at: dynamic([_, address], address.inserted_at), + block_number: 0, + icon_url: nil, + token_type: nil, + timestamp: dynamic([_, _], type(^nil, :utc_datetime_usec)), + verified: true, + exchange_rate: nil, + total_supply: nil, + circulating_market_cap: nil, + priority: 0, + is_verified_via_admin_panel: nil + } + from(smart_contract in SmartContract, left_join: address in Address, on: smart_contract.address_hash == address.hash, where: fragment("to_tsvector('english', ?) @@ to_tsquery(?)", smart_contract.name, ^term), - select: %{ - address_hash: smart_contract.address_hash, - tx_hash: fragment("CAST(NULL AS bytea)"), - block_hash: fragment("CAST(NULL AS bytea)"), - type: "contract", - name: smart_contract.name, - symbol: ^nil, - holder_count: ^nil, - inserted_at: address.inserted_at, - block_number: 0, - icon_url: nil, - token_type: nil, - timestamp: fragment("NULL::timestamp without time zone"), - verified: true, - exchange_rate: nil, - total_supply: nil, - circulating_market_cap: nil, - priority: 0, - is_verified_via_admin_panel: nil - } + select: ^contract_search_fields ) end defp search_address_query(term) do case Chain.string_to_address_hash(term) do {:ok, address_hash} -> + address_search_fields = %{ + address_hash: dynamic([address, _], address.hash), + block_hash: dynamic([_, _], type(^nil, :binary)), + tx_hash: dynamic([_, _], type(^nil, :binary)), + type: "address", + name: dynamic([_, address_name], address_name.name), + symbol: nil, + holder_count: nil, + inserted_at: dynamic([address, _], address.inserted_at), + block_number: 0, + icon_url: nil, + token_type: nil, + timestamp: dynamic([_, _], type(^nil, :utc_datetime_usec)), + verified: dynamic([address, _], address.verified), + exchange_rate: nil, + total_supply: nil, + circulating_market_cap: nil, + priority: 0, + is_verified_via_admin_panel: nil + } + from(address in Address, left_join: address_name in subquery( @@ -308,26 +347,7 @@ defmodule Explorer.Chain.Search do ), on: address.hash == address_name.address_hash, where: address.hash == ^address_hash, - select: %{ - address_hash: address.hash, - tx_hash: fragment("CAST(NULL AS bytea)"), - block_hash: fragment("CAST(NULL AS bytea)"), - type: "address", - name: address_name.name, - symbol: ^nil, - holder_count: ^nil, - inserted_at: address.inserted_at, - block_number: 0, - icon_url: nil, - token_type: nil, - timestamp: fragment("NULL::timestamp without time zone"), - verified: address.verified, - exchange_rate: nil, - total_supply: nil, - circulating_market_cap: nil, - priority: 0, - is_verified_via_admin_panel: nil - } + select: ^address_search_fields ) _ -> @@ -338,30 +358,32 @@ defmodule Explorer.Chain.Search do 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 + } + from(transaction in Transaction, left_join: block in Block, on: transaction.block_hash == block.hash, where: transaction.hash == ^tx_hash, - select: %{ - address_hash: fragment("CAST(NULL AS bytea)"), - tx_hash: transaction.hash, - block_hash: fragment("CAST(NULL AS bytea)"), - type: "transaction", - name: ^nil, - symbol: ^nil, - holder_count: ^nil, - inserted_at: transaction.inserted_at, - block_number: 0, - icon_url: nil, - token_type: nil, - timestamp: block.timestamp, - verified: nil, - exchange_rate: nil, - total_supply: nil, - circulating_market_cap: nil, - priority: 0, - is_verified_via_admin_panel: nil - } + select: ^transaction_search_fields ) _ -> @@ -370,30 +392,32 @@ defmodule Explorer.Chain.Search do end defp search_block_query(term) do + block_search_fields = %{ + address_hash: dynamic([_], type(^nil, :binary)), + tx_hash: dynamic([_], type(^nil, :binary)), + block_hash: dynamic([block], block.hash), + type: "block", + name: nil, + symbol: nil, + holder_count: nil, + inserted_at: dynamic([block], block.inserted_at), + block_number: dynamic([block], block.number), + 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 + } + case Chain.string_to_block_hash(term) do {:ok, block_hash} -> from(block in Block, where: block.hash == ^block_hash, - select: %{ - address_hash: fragment("CAST(NULL AS bytea)"), - tx_hash: fragment("CAST(NULL AS bytea)"), - block_hash: block.hash, - type: "block", - name: ^nil, - symbol: ^nil, - holder_count: ^nil, - inserted_at: block.inserted_at, - block_number: block.number, - icon_url: nil, - token_type: nil, - timestamp: block.timestamp, - verified: nil, - exchange_rate: nil, - total_supply: nil, - circulating_market_cap: nil, - priority: 0, - is_verified_via_admin_panel: nil - } + select: ^block_search_fields ) _ -> @@ -401,26 +425,7 @@ defmodule Explorer.Chain.Search do {block_number, ""} -> from(block in Block, where: block.number == ^block_number, - select: %{ - address_hash: fragment("CAST(NULL AS bytea)"), - tx_hash: fragment("CAST(NULL AS bytea)"), - block_hash: block.hash, - type: "block", - name: ^nil, - symbol: ^nil, - holder_count: ^nil, - inserted_at: block.inserted_at, - block_number: block.number, - icon_url: nil, - token_type: nil, - timestamp: block.timestamp, - verified: nil, - exchange_rate: nil, - total_supply: nil, - circulating_market_cap: nil, - priority: 0, - is_verified_via_admin_panel: nil - } + select: ^block_search_fields ) _ -> From 13e9ccec3804d4d618cfc0402fdbe715afc85165 Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Thu, 9 Nov 2023 15:19:27 +0300 Subject: [PATCH 018/607] Enable API v2 by default (#8802) * Enable API v2 by default * Changelog --- .github/workflows/config.yml | 1 - CHANGELOG.md | 1 + config/runtime.exs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 75e35d9df679..0b7346dc7b11 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -637,5 +637,4 @@ jobs: ADMIN_PANEL_ENABLED: "true" ACCOUNT_ENABLED: "true" ACCOUNT_REDIS_URL: "redis://localhost:6379" - API_V2_ENABLED: "true" SOURCIFY_INTEGRATION_ENABLED: "true" diff --git a/CHANGELOG.md b/CHANGELOG.md index bbd2315d761a..7422a7055f45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ ### Chore +- [#8802](https://github.com/blockscout/blockscout/pull/8802) - Enable API v2 by default - [#8728](https://github.com/blockscout/blockscout/pull/8728) - Remove repos_list (default value for ecto repos) from Explorer.ReleaseTasks
diff --git a/config/runtime.exs b/config/runtime.exs index 8c8baee4fa63..1d1a7341e72b 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -132,7 +132,7 @@ config :block_scout_web, :chart, config :block_scout_web, BlockScoutWeb.Chain.Address.CoinBalance, coin_balance_history_days: ConfigHelper.parse_integer_env_var("COIN_BALANCE_HISTORY_DAYS", 10) -config :block_scout_web, BlockScoutWeb.API.V2, enabled: ConfigHelper.parse_bool_env_var("API_V2_ENABLED") +config :block_scout_web, BlockScoutWeb.API.V2, enabled: ConfigHelper.parse_bool_env_var("API_V2_ENABLED", "true") # Configures Ueberauth's Auth0 auth provider config :ueberauth, Ueberauth.Strategy.Auth0.OAuth, From aec92611784ea05ffa138b26edf1893ee8023e8a Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Tue, 1 Aug 2023 17:46:42 +0300 Subject: [PATCH 019/607] Add rootstock specific fields (#8076) * Add rootstock fields to block structere * Add rootstock fetcher --- .../views/api/v2/block_view.ex | 12 ++ apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex | 6 +- .../lib/ethereum_jsonrpc/block.ex | 65 +++++-- .../lib/ethereum_jsonrpc/blocks.ex | 7 +- .../test/ethereum_jsonrpc/block_test.exs | 7 +- apps/explorer/lib/explorer/chain/block.ex | 34 +++- ...4094744_add_rootstock_fields_to_blocks.exs | 13 ++ .../lib/indexer/fetcher/rootstock_data.ex | 168 ++++++++++++++++++ apps/indexer/lib/indexer/supervisor.ex | 2 + .../indexer/fetcher/rootstock_data_test.exs | 134 ++++++++++++++ .../fetcher/rootstock_data_supervisor_case.ex | 17 ++ config/runtime.exs | 9 + docker-compose/envs/common-blockscout.env | 5 + 13 files changed, 462 insertions(+), 17 deletions(-) create mode 100644 apps/explorer/priv/repo/migrations/20230724094744_add_rootstock_fields_to_blocks.exs create mode 100644 apps/indexer/lib/indexer/fetcher/rootstock_data.ex create mode 100644 apps/indexer/test/indexer/fetcher/rootstock_data_test.exs create mode 100644 apps/indexer/test/support/indexer/fetcher/rootstock_data_supervisor_case.ex diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex index c4793dbacedf..3d6e3f57f004 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex @@ -61,6 +61,7 @@ defmodule BlockScoutWeb.API.V2.BlockView do "tx_fees" => tx_fees, "withdrawals_count" => count_withdrawals(block) } + |> add_rootstock_fields(block, single_block?) end def prepare_rewards(rewards, block, single_block?) do @@ -115,4 +116,15 @@ defmodule BlockScoutWeb.API.V2.BlockView do def count_withdrawals(%Block{withdrawals: withdrawals}) when is_list(withdrawals), do: Enum.count(withdrawals) def count_withdrawals(_), do: nil + + defp add_rootstock_fields(prepared_block, _block, false), do: prepared_block + + defp add_rootstock_fields(prepared_block, block, true) do + prepared_block + |> Map.put("minimum_gas_price", block.minimum_gas_price) + |> Map.put("bitcoin_merged_mining_header", block.bitcoin_merged_mining_header) + |> Map.put("bitcoin_merged_mining_coinbase_transaction", block.bitcoin_merged_mining_coinbase_transaction) + |> Map.put("bitcoin_merged_mining_merkle_proof", block.bitcoin_merged_mining_merkle_proof) + |> Map.put("hash_for_merged_mining", block.hash_for_merged_mining) + end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex index 3b67a4dd7f5a..abad4cdfb3cd 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex @@ -277,12 +277,12 @@ defmodule EthereumJSONRPC do @doc """ Fetches blocks by block number list. """ - @spec fetch_blocks_by_numbers([block_number()], json_rpc_named_arguments) :: + @spec fetch_blocks_by_numbers([block_number()], json_rpc_named_arguments, boolean()) :: {:ok, Blocks.t()} | {:error, reason :: term} - def fetch_blocks_by_numbers(block_numbers, json_rpc_named_arguments) do + def fetch_blocks_by_numbers(block_numbers, json_rpc_named_arguments, with_transactions? \\ true) do block_numbers |> Enum.map(fn number -> %{number: number} end) - |> fetch_blocks_by_params(&Block.ByNumber.request/1, json_rpc_named_arguments) + |> fetch_blocks_by_params(&Block.ByNumber.request(&1, with_transactions?), json_rpc_named_arguments) end @doc """ diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex index 5737aa909651..ea841f483d06 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex @@ -30,7 +30,12 @@ defmodule EthereumJSONRPC.Block do transactions_root: EthereumJSONRPC.hash(), uncles: [EthereumJSONRPC.hash()], base_fee_per_gas: non_neg_integer(), - withdrawals_root: EthereumJSONRPC.hash() + withdrawals_root: EthereumJSONRPC.hash(), + minimum_gas_price: non_neg_integer(), + bitcoin_merged_mining_header: EthereumJSONRPC.data(), + bitcoin_merged_mining_coinbase_transaction: EthereumJSONRPC.data(), + bitcoin_merged_mining_merkle_proof: EthereumJSONRPC.data(), + hash_for_merged_mining: EthereumJSONRPC.data() } @typedoc """ @@ -69,6 +74,11 @@ defmodule EthereumJSONRPC.Block do `t:EthereumJSONRPC.hash/0`. * `"baseFeePerGas"` - `t:EthereumJSONRPC.quantity/0` of wei to denote amount of fee burned per unit gas used. Introduced in [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) * `"withdrawalsRoot"` - `t:EthereumJSONRPC.hash/0` of the root of the withdrawals. + * `"minimumGasPrice"` - `t:EthereumJSONRPC.quantity/0` of the minimum gas price for this block. + * `"bitcoinMergedMiningHeader"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining header. + * `"bitcoinMergedMiningCoinbaseTransaction"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining coinbase transaction. + * `"bitcoinMergedMiningMerkleProof"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining merkle proof. + * `"hashForMergedMining"` - `t:EthereumJSONRPC.data/0` of the hash for merged mining. """ @type t :: %{String.t() => EthereumJSONRPC.data() | EthereumJSONRPC.hash() | EthereumJSONRPC.quantity() | nil} @@ -120,7 +130,12 @@ defmodule EthereumJSONRPC.Block do ...> "totalDifficulty" => 340282366920938463463374607431465668165, ...> "transactions" => [], ...> "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - ...> "uncles" => [] + ...> "uncles" => [], + ...> "minimumGasPrice" => 345786, + ...> "bitcoinMergedMiningHeader" => "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9", + ...> "bitcoinMergedMiningCoinbaseTransaction" => "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000", + ...> "bitcoinMergedMiningMerkleProof" => "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7", + ...> "hashForMergedMining" => "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40", ...> } ...> ) %{ @@ -143,7 +158,12 @@ defmodule EthereumJSONRPC.Block do total_difficulty: 340282366920938463463374607431465668165, transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", uncles: [], - withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + bitcoin_merged_mining_coinbase_transaction: "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000", + bitcoin_merged_mining_header: "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9", + bitcoin_merged_mining_merkle_proof: "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7", + hash_for_merged_mining: "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40", + minimum_gas_price: 345786 } [Geth] `elixir` can be converted to params @@ -192,7 +212,12 @@ defmodule EthereumJSONRPC.Block do total_difficulty: 1039309006117, transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", uncles: [], - withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + bitcoin_merged_mining_coinbase_transaction: nil, + bitcoin_merged_mining_header: nil, + bitcoin_merged_mining_merkle_proof: nil, + hash_for_merged_mining: nil, + minimum_gas_price: nil } """ @@ -241,7 +266,12 @@ defmodule EthereumJSONRPC.Block do uncles: uncles, base_fee_per_gas: base_fee_per_gas, withdrawals_root: - Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), + minimum_gas_price: Map.get(elixir, "minimumGasPrice"), + bitcoin_merged_mining_header: Map.get(elixir, "bitcoinMergedMiningHeader"), + bitcoin_merged_mining_coinbase_transaction: Map.get(elixir, "bitcoinMergedMiningCoinbaseTransaction"), + bitcoin_merged_mining_merkle_proof: Map.get(elixir, "bitcoinMergedMiningMerkleProof"), + hash_for_merged_mining: Map.get(elixir, "hashForMergedMining") } end @@ -287,7 +317,12 @@ defmodule EthereumJSONRPC.Block do uncles: uncles, base_fee_per_gas: base_fee_per_gas, withdrawals_root: - Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), + minimum_gas_price: Map.get(elixir, "minimumGasPrice"), + bitcoin_merged_mining_header: Map.get(elixir, "bitcoinMergedMiningHeader"), + bitcoin_merged_mining_coinbase_transaction: Map.get(elixir, "bitcoinMergedMiningCoinbaseTransaction"), + bitcoin_merged_mining_merkle_proof: Map.get(elixir, "bitcoinMergedMiningMerkleProof"), + hash_for_merged_mining: Map.get(elixir, "hashForMergedMining") } end @@ -333,7 +368,12 @@ defmodule EthereumJSONRPC.Block do transactions_root: transactions_root, uncles: uncles, withdrawals_root: - Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), + minimum_gas_price: Map.get(elixir, "minimumGasPrice"), + bitcoin_merged_mining_header: Map.get(elixir, "bitcoinMergedMiningHeader"), + bitcoin_merged_mining_coinbase_transaction: Map.get(elixir, "bitcoinMergedMiningCoinbaseTransaction"), + bitcoin_merged_mining_merkle_proof: Map.get(elixir, "bitcoinMergedMiningMerkleProof"), + hash_for_merged_mining: Map.get(elixir, "hashForMergedMining") } end @@ -378,7 +418,12 @@ defmodule EthereumJSONRPC.Block do transactions_root: transactions_root, uncles: uncles, withdrawals_root: - Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), + minimum_gas_price: Map.get(elixir, "minimumGasPrice"), + bitcoin_merged_mining_header: Map.get(elixir, "bitcoinMergedMiningHeader"), + bitcoin_merged_mining_coinbase_transaction: Map.get(elixir, "bitcoinMergedMiningCoinbaseTransaction"), + bitcoin_merged_mining_merkle_proof: Map.get(elixir, "bitcoinMergedMiningMerkleProof"), + hash_for_merged_mining: Map.get(elixir, "hashForMergedMining") } end @@ -685,7 +730,7 @@ defmodule EthereumJSONRPC.Block do end defp entry_to_elixir({key, quantity}, _block) - when key in ~w(difficulty gasLimit gasUsed minimumGasPrice baseFeePerGas number size cumulativeDifficulty totalDifficulty paidFees) and + when key in ~w(difficulty gasLimit gasUsed minimumGasPrice baseFeePerGas number size cumulativeDifficulty totalDifficulty paidFees minimumGasPrice) and not is_nil(quantity) do {key, quantity_to_integer(quantity)} end @@ -700,7 +745,7 @@ defmodule EthereumJSONRPC.Block do # hash format defp entry_to_elixir({key, _} = entry, _block) when key in ~w(author extraData hash logsBloom miner mixHash nonce parentHash receiptsRoot sealFields sha3Uncles - signature stateRoot step transactionsRoot uncles withdrawalsRoot), + signature stateRoot step transactionsRoot uncles withdrawalsRoot bitcoinMergedMiningHeader bitcoinMergedMiningCoinbaseTransaction bitcoinMergedMiningMerkleProof hashForMergedMining), do: entry defp entry_to_elixir({"timestamp" = key, timestamp}, _block) do diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex index a501a16bc817..4a79b51687f3 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex @@ -117,7 +117,12 @@ defmodule EthereumJSONRPC.Blocks do total_difficulty: 131072, transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", uncles: ["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273311"], - withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + bitcoin_merged_mining_coinbase_transaction: nil, + bitcoin_merged_mining_header: nil, + bitcoin_merged_mining_merkle_proof: nil, + hash_for_merged_mining: nil, + minimum_gas_price: nil } ] diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs index 4f59274e8533..5f0e59c91cd8 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs @@ -53,7 +53,12 @@ defmodule EthereumJSONRPC.BlockTest do total_difficulty: nil, transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", uncles: [], - withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + bitcoin_merged_mining_coinbase_transaction: nil, + bitcoin_merged_mining_header: nil, + bitcoin_merged_mining_merkle_proof: nil, + hash_for_merged_mining: nil, + minimum_gas_price: nil } end end diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index a1ab2d19e6a8..6cef72ae5e07 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -10,7 +10,7 @@ defmodule Explorer.Chain.Block do alias Explorer.Chain.{Address, Block, Gas, Hash, PendingBlockOperation, Transaction, Wei, Withdrawal} alias Explorer.Chain.Block.{Reward, SecondDegreeRelation} - @optional_attrs ~w(size refetch_needed total_difficulty difficulty base_fee_per_gas)a + @optional_attrs ~w(size refetch_needed total_difficulty difficulty base_fee_per_gas minimum_gas_price bitcoin_merged_mining_header bitcoin_merged_mining_coinbase_transaction bitcoin_merged_mining_merkle_proof hash_for_merged_mining)a @required_attrs ~w(consensus gas_limit gas_used hash miner_hash nonce number parent_hash timestamp)a @@ -65,7 +65,12 @@ defmodule Explorer.Chain.Block do transactions: %Ecto.Association.NotLoaded{} | [Transaction.t()], refetch_needed: boolean(), base_fee_per_gas: Wei.t(), - is_empty: boolean() + is_empty: boolean(), + minimum_gas_price: Decimal.t(), + bitcoin_merged_mining_header: binary(), + bitcoin_merged_mining_coinbase_transaction: binary(), + bitcoin_merged_mining_merkle_proof: binary(), + hash_for_merged_mining: binary() } @primary_key {:hash, Hash.Full, autogenerate: false} @@ -82,6 +87,11 @@ defmodule Explorer.Chain.Block do field(:refetch_needed, :boolean) field(:base_fee_per_gas, Wei) field(:is_empty, :boolean) + field(:minimum_gas_price, :decimal) + field(:bitcoin_merged_mining_header, :binary) + field(:bitcoin_merged_mining_coinbase_transaction, :binary) + field(:bitcoin_merged_mining_merkle_proof, :binary) + field(:hash_for_merged_mining, :binary) timestamps() @@ -159,4 +169,24 @@ defmodule Explorer.Chain.Block do end def block_type_filter(query, "Uncle"), do: where(query, [block], block.consensus == false) + + @doc """ + Returns query that fetches up to `limit` of consensus blocks + that are missing rootstock data ordered by number desc. + """ + @spec blocks_without_rootstock_data_query(non_neg_integer()) :: Ecto.Query.t() + def blocks_without_rootstock_data_query(limit) do + from( + block in __MODULE__, + where: + is_nil(block.minimum_gas_price) or + is_nil(block.bitcoin_merged_mining_header) or + is_nil(block.bitcoin_merged_mining_coinbase_transaction) or + is_nil(block.bitcoin_merged_mining_merkle_proof) or + is_nil(block.hash_for_merged_mining), + where: block.consensus == true, + limit: ^limit, + order_by: [desc: block.number] + ) + end end diff --git a/apps/explorer/priv/repo/migrations/20230724094744_add_rootstock_fields_to_blocks.exs b/apps/explorer/priv/repo/migrations/20230724094744_add_rootstock_fields_to_blocks.exs new file mode 100644 index 000000000000..a5ebc7c614b5 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20230724094744_add_rootstock_fields_to_blocks.exs @@ -0,0 +1,13 @@ +defmodule Explorer.Repo.Migrations.AddRootstockFieldsToBlocks do + use Ecto.Migration + + def change do + alter table(:blocks) do + add(:minimum_gas_price, :decimal) + add(:bitcoin_merged_mining_header, :bytea) + add(:bitcoin_merged_mining_coinbase_transaction, :bytea) + add(:bitcoin_merged_mining_merkle_proof, :bytea) + add(:hash_for_merged_mining, :bytea) + end + end +end diff --git a/apps/indexer/lib/indexer/fetcher/rootstock_data.ex b/apps/indexer/lib/indexer/fetcher/rootstock_data.ex new file mode 100644 index 000000000000..fc23769be4d1 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/rootstock_data.ex @@ -0,0 +1,168 @@ +defmodule Indexer.Fetcher.RootstockData do + @moduledoc """ + Refetch `minimum_gas_price`, `bitcoin_merged_mining_header`, `bitcoin_merged_mining_coinbase_transaction`, + `bitcoin_merged_mining_merkle_proof`, `hash_for_merged_mining` fields for blocks that were indexed before app update. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + alias EthereumJSONRPC.Blocks + alias Explorer.Chain.Block + alias Explorer.Repo + + @interval :timer.seconds(3) + @batch_size 10 + @concurrency 5 + @db_batch_size 300 + + defstruct blocks_to_fetch: [], + interval: @interval, + json_rpc_named_arguments: [], + batch_size: @batch_size, + max_concurrency: @concurrency, + db_batch_size: @db_batch_size + + def child_spec([init_arguments]) do + child_spec([init_arguments, []]) + end + + def child_spec([_init_arguments, _gen_server_options] = start_link_arguments) do + default = %{ + id: __MODULE__, + start: {__MODULE__, :start_link, start_link_arguments} + } + + Supervisor.child_spec(default, restart: :transient) + end + + def start_link(arguments, gen_server_options \\ []) do + GenServer.start_link(__MODULE__, arguments, gen_server_options) + end + + @impl GenServer + def init(opts) when is_list(opts) do + Logger.metadata(fetcher: :rootstock_data) + + json_rpc_named_arguments = opts[:json_rpc_named_arguments] + + unless json_rpc_named_arguments do + raise ArgumentError, + ":json_rpc_named_arguments must be provided to `#{__MODULE__}.init to allow for json_rpc calls when running." + end + + state = %__MODULE__{ + blocks_to_fetch: nil, + interval: Application.get_env(:indexer, __MODULE__)[:interval] || @interval, + json_rpc_named_arguments: json_rpc_named_arguments, + batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @batch_size, + max_concurrency: Application.get_env(:indexer, __MODULE__)[:max_concurrency] || @concurrency, + db_batch_size: Application.get_env(:indexer, __MODULE__)[:db_batch_size] || @db_batch_size + } + + Process.send_after(self(), :fetch_rootstock_data, state.interval) + + {:ok, state, {:continue, :fetch_blocks}} + end + + @impl GenServer + def handle_continue(:fetch_blocks, state), do: fetch_blocks(state) + + @impl GenServer + def handle_info(:fetch_blocks, state), do: fetch_blocks(state) + + @impl GenServer + def handle_info( + :fetch_rootstock_data, + %__MODULE__{ + blocks_to_fetch: blocks_to_fetch, + interval: interval, + json_rpc_named_arguments: json_rpc_named_arguments, + batch_size: batch_size, + max_concurrency: concurrency + } = state + ) do + if Enum.empty?(blocks_to_fetch) do + send(self(), :fetch_blocks) + {:noreply, state} + else + new_blocks_to_fetch = + blocks_to_fetch + |> Stream.chunk_every(batch_size) + |> Task.async_stream( + &{EthereumJSONRPC.fetch_blocks_by_numbers( + Enum.map(&1, fn b -> b.number end), + json_rpc_named_arguments, + false + ), &1}, + max_concurrency: concurrency, + timeout: :infinity, + zip_input_on_exit: true + ) + |> Enum.reduce([], &fetch_reducer/2) + + Process.send_after(self(), :fetch_rootstock_data, interval) + + {:noreply, %__MODULE__{state | blocks_to_fetch: new_blocks_to_fetch}} + end + end + + def handle_info({ref, _result}, state) do + Process.demonitor(ref, [:flush]) + {:noreply, state} + end + + def handle_info( + {:DOWN, _ref, :process, _pid, reason}, + state + ) do + if reason === :normal do + {:noreply, state} + else + Logger.error(fn -> "Rootstock data fetcher task exited due to #{inspect(reason)}." end) + {:noreply, state} + end + end + + defp fetch_blocks(%__MODULE__{db_batch_size: db_batch_size} = state) do + blocks_to_fetch = db_batch_size |> Block.blocks_without_rootstock_data_query() |> Repo.all() + + if Enum.empty?(blocks_to_fetch) do + Logger.info("Rootstock data from old blocks are fetched.") + + {:stop, :normal, state} + else + [%Block{number: max_number} | _] = blocks_to_fetch + + Logger.info( + "Rootstock data will now be fetched for #{Enum.count(blocks_to_fetch)} blocks starting from #{max_number}." + ) + + {:noreply, %__MODULE__{state | blocks_to_fetch: blocks_to_fetch}} + end + end + + defp fetch_reducer({:ok, {{:ok, %Blocks{blocks_params: block_params}}, blocks}}, acc) do + blocks_map = Map.new(blocks, fn b -> {b.number, b} end) + + for block_param <- block_params, + block = blocks_map[block_param.number], + block_param.hash == to_string(block.hash) do + block |> Block.changeset(block_param) |> Repo.update() + end + + acc + end + + defp fetch_reducer({:ok, {{:error, reason}, blocks}}, acc) do + Logger.error("failed to fetch: " <> inspect(reason) <> ". Retrying.") + [blocks | acc] |> List.flatten() + end + + defp fetch_reducer({:exit, {blocks, reason}}, acc) do + Logger.error("failed to fetch: " <> inspect(reason) <> ". Retrying.") + [blocks | acc] |> List.flatten() + end +end diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index c7afebbbcee0..97603fd0dfe5 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -28,6 +28,7 @@ defmodule Indexer.Supervisor do PendingTransaction, PolygonEdge, ReplacedTransaction, + RootstockData, Token, TokenBalance, TokenTotalSupplyUpdater, @@ -151,6 +152,7 @@ defmodule Indexer.Supervisor do [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, {PendingOpsCleaner, [[], []]}, {PendingBlockOperationsSanitizer, [[]]}, + {RootstockData.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments]]}, # Block fetchers configure(BlockRealtime.Supervisor, [ diff --git a/apps/indexer/test/indexer/fetcher/rootstock_data_test.exs b/apps/indexer/test/indexer/fetcher/rootstock_data_test.exs new file mode 100644 index 000000000000..b3011a283421 --- /dev/null +++ b/apps/indexer/test/indexer/fetcher/rootstock_data_test.exs @@ -0,0 +1,134 @@ +defmodule Indexer.Fetcher.RootstockDataTest do + use EthereumJSONRPC.Case + use Explorer.DataCase + + import Mox + import EthereumJSONRPC, only: [integer_to_quantity: 1] + + alias Indexer.Fetcher.RootstockData + + setup :verify_on_exit! + setup :set_mox_global + + test "do not start when all old blocks are fetched", %{json_rpc_named_arguments: json_rpc_named_arguments} do + RootstockData.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + + :timer.sleep(300) + + assert [{Indexer.Fetcher.RootstockData, :undefined, :worker, [Indexer.Fetcher.RootstockData]} | _] = + RootstockData.Supervisor |> Supervisor.which_children() + end + + test "stops when all old blocks are fetched", %{json_rpc_named_arguments: json_rpc_named_arguments} do + block_a = insert(:block) + block_b = insert(:block) + + block_a_number_string = integer_to_quantity(block_a.number) + block_b_number_string = integer_to_quantity(block_b.number) + + EthereumJSONRPC.Mox + |> stub(:json_rpc, fn requests, _options -> + {:ok, + Enum.map(requests, fn + %{id: id, method: "eth_getBlockByNumber", params: [^block_a_number_string, false]} -> + %{ + id: id, + result: %{ + "author" => "0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c", + "difficulty" => "0x6bc767dd80781", + "extraData" => "0x5050594520737061726b706f6f6c2d6574682d7477", + "gasLimit" => "0x7a121d", + "gasUsed" => "0x79cbe9", + "hash" => to_string(block_a.hash), + "logsBloom" => + "0x044d42d008801488400e1809190200a80d06105bc0c4100b047895c0d518327048496108388040140010b8208006288102e206160e21052322440924002090c1c808a0817405ab238086d028211014058e949401012403210314896702d06880c815c3060a0f0809987c81044488292cc11d57882c912a808ca10471c84460460040000c0001012804022000a42106591881d34407420ba401e1c08a8d00a000a34c11821a80222818a4102152c8a0c044032080c6462644223104d618e0e544072008120104408205c60510542264808488220403000106281a0290404220112c10b080145028c8000300b18a2c8280701c882e702210b00410834840108084", + "miner" => to_string(block_a.miner), + "mixHash" => "0xda53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010", + "nonce" => "0x0946e5f01fce12bc", + "number" => block_a_number_string, + "parentHash" => to_string(block_a.parent_hash), + "receiptsRoot" => "0xa7d2b82bd8526de11736c18bd5cc8cfe2692106c4364526f3310ad56d78669c4", + "sealFields" => [ + "0xa0da53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010", + "0x880946e5f01fce12bc" + ], + "sha3Uncles" => "0x483a8a21a5825ad270f358b3ea56e060bbb8b3082d9a92ec8fa17a5c7e6fc1b6", + "size" => "0x544c", + "stateRoot" => "0x85daa9cd528004c1609d4cb3520fd958e85983bb4183124a4a9f7137fd39c691", + "timestamp" => "0x5c8bc76e", + "totalDifficulty" => "0x201a42c35142ae94458", + "transactions" => [], + "transactionsRoot" => "0xcd6c12fa43cd4e92ad5c0bf232b30488bbcbfe273c5b4af0366fced0767d54db", + "uncles" => [], + "withdrawals" => [], + "minimumGasPrice" => "0x0", + "bitcoinMergedMiningHeader" => + "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9", + "bitcoinMergedMiningCoinbaseTransaction" => + "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000", + "bitcoinMergedMiningMerkleProof" => + "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7", + "hashForMergedMining" => "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40" + } + } + + %{id: id, method: "eth_getBlockByNumber", params: [^block_b_number_string, false]} -> + %{ + id: id, + result: %{ + "author" => "0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c", + "difficulty" => "0x6bc767dd80781", + "extraData" => "0x5050594520737061726b706f6f6c2d6574682d7477", + "gasLimit" => "0x7a121d", + "gasUsed" => "0x79cbe9", + "hash" => to_string(block_b.hash), + "logsBloom" => + "0x044d42d008801488400e1809190200a80d06105bc0c4100b047895c0d518327048496108388040140010b8208006288102e206160e21052322440924002090c1c808a0817405ab238086d028211014058e949401012403210314896702d06880c815c3060a0f0809987c81044488292cc11d57882c912a808ca10471c84460460040000c0001012804022000a42106591881d34407420ba401e1c08a8d00a000a34c11821a80222818a4102152c8a0c044032080c6462644223104d618e0e544072008120104408205c60510542264808488220403000106281a0290404220112c10b080145028c8000300b18a2c8280701c882e702210b00410834840108084", + "miner" => to_string(block_b.miner), + "mixHash" => "0xda53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010", + "nonce" => "0x0946e5f01fce12bc", + "number" => block_b_number_string, + "parentHash" => to_string(block_b.parent_hash), + "receiptsRoot" => "0xa7d2b82bd8526de11736c18bd5cc8cfe2692106c4364526f3310ad56d78669c4", + "sealFields" => [ + "0xa0da53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010", + "0x880946e5f01fce12bc" + ], + "sha3Uncles" => "0x483a8a21a5825ad270f358b3ea56e060bbb8b3082d9a92ec8fa17a5c7e6fc1b6", + "size" => "0x544c", + "stateRoot" => "0x85daa9cd528004c1609d4cb3520fd958e85983bb4183124a4a9f7137fd39c691", + "timestamp" => "0x5c8bc76e", + "totalDifficulty" => "0x201a42c35142ae94458", + "transactions" => [], + "transactionsRoot" => "0xcd6c12fa43cd4e92ad5c0bf232b30488bbcbfe273c5b4af0366fced0767d54db", + "uncles" => [], + "withdrawals" => [], + "minimumGasPrice" => "0x1", + "bitcoinMergedMiningHeader" => + "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9", + "bitcoinMergedMiningCoinbaseTransaction" => + "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000", + "bitcoinMergedMiningMerkleProof" => + "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7", + "hashForMergedMining" => "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40" + } + } + end)} + end) + + pid = RootstockData.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + + assert [{Indexer.Fetcher.RootstockData, worker_pid, :worker, [Indexer.Fetcher.RootstockData]} | _] = + RootstockData.Supervisor |> Supervisor.which_children() + + assert is_pid(worker_pid) + + :timer.sleep(300) + + assert [{Indexer.Fetcher.RootstockData, :undefined, :worker, [Indexer.Fetcher.RootstockData]} | _] = + RootstockData.Supervisor |> Supervisor.which_children() + + # Terminates the process so it finishes all Ecto processes. + GenServer.stop(pid) + end +end diff --git a/apps/indexer/test/support/indexer/fetcher/rootstock_data_supervisor_case.ex b/apps/indexer/test/support/indexer/fetcher/rootstock_data_supervisor_case.ex new file mode 100644 index 000000000000..d58f3c5f10cd --- /dev/null +++ b/apps/indexer/test/support/indexer/fetcher/rootstock_data_supervisor_case.ex @@ -0,0 +1,17 @@ +defmodule Indexer.Fetcher.RootstockData.Supervisor.Case do + alias Indexer.Fetcher.RootstockData + + def start_supervised!(fetcher_arguments \\ []) when is_list(fetcher_arguments) do + merged_fetcher_arguments = + Keyword.merge( + fetcher_arguments, + interval: 1, + batch_size: 1, + max_concurrency: 1 + ) + + [merged_fetcher_arguments] + |> RootstockData.Supervisor.child_spec() + |> ExUnit.Callbacks.start_supervised!() + end +end diff --git a/config/runtime.exs b/config/runtime.exs index 1d1a7341e72b..a7eaeaa0a36f 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -630,6 +630,15 @@ config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch.Supervisor, System.get_env("CHAIN_TYPE", "ethereum") == "polygon_zkevm" && ConfigHelper.parse_bool_env_var("INDEXER_ZKEVM_BATCHES_ENABLED") + config :indexer, Indexer.Fetcher.RootstockData.Supervisor, + disabled?: ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_ROOTSTOCK_DATA_FETCHER") + +config :indexer, Indexer.Fetcher.RootstockData, + interval: ConfigHelper.parse_time_env_var("INDEXER_ROOTSTOCK_DATA_FETCHER_INTERVAL", "3s"), + batch_size: ConfigHelper.parse_integer_env_var("INDEXER_ROOTSTOCK_DATA_FETCHER_BATCH_SIZE", 10), + max_concurrency: ConfigHelper.parse_integer_env_var("INDEXER_ROOTSTOCK_DATA_FETCHER_CONCURRENCY", 5), + db_batch_size: ConfigHelper.parse_integer_env_var("INDEXER_ROOTSTOCK_DATA_FETCHER_DB_BATCH_SIZE", 300) + Code.require_file("#{config_env()}.exs", "config/runtime") for config <- "../apps/*/config/runtime/#{config_env()}.exs" |> Path.expand(__DIR__) |> Path.wildcard() do diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 7bf3c41a56ca..8ea2e9265dbe 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -155,6 +155,11 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false # ROOTSTOCK_BRIDGE_ADDRESS= # ROOTSTOCK_LOCKED_BTC_CACHE_PERIOD= # ROOTSTOCK_LOCKING_CAP= +# INDEXER_DISABLE_ROOTSTOCK_DATA_FETCHER= +# INDEXER_ROOTSTOCK_DATA_FETCHER_INTERVAL= +# INDEXER_ROOTSTOCK_DATA_FETCHER_BATCH_SIZE= +# INDEXER_ROOTSTOCK_DATA_FETCHER_CONCURRENCY= +# INDEXER_ROOTSTOCK_DATA_FETCHER_DB_BATCH_SIZE= # TOKEN_ID_MIGRATION_FIRST_BLOCK= # TOKEN_ID_MIGRATION_CONCURRENCY= # TOKEN_ID_MIGRATION_BATCH_SIZE= From 940799b5f9b5b1d36a1def45203c5b65c8bd785a Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Wed, 2 Aug 2023 13:23:51 +0300 Subject: [PATCH 020/607] Fix cycle of rootstock fetcher [no ci] --- apps/indexer/lib/indexer/fetcher/rootstock_data.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/indexer/lib/indexer/fetcher/rootstock_data.ex b/apps/indexer/lib/indexer/fetcher/rootstock_data.ex index fc23769be4d1..dc6b0761be64 100644 --- a/apps/indexer/lib/indexer/fetcher/rootstock_data.ex +++ b/apps/indexer/lib/indexer/fetcher/rootstock_data.ex @@ -126,7 +126,7 @@ defmodule Indexer.Fetcher.RootstockData do end end - defp fetch_blocks(%__MODULE__{db_batch_size: db_batch_size} = state) do + defp fetch_blocks(%__MODULE__{db_batch_size: db_batch_size, interval: interval} = state) do blocks_to_fetch = db_batch_size |> Block.blocks_without_rootstock_data_query() |> Repo.all() if Enum.empty?(blocks_to_fetch) do @@ -140,6 +140,8 @@ defmodule Indexer.Fetcher.RootstockData do "Rootstock data will now be fetched for #{Enum.count(blocks_to_fetch)} blocks starting from #{max_number}." ) + Process.send_after(self(), :fetch_rootstock_data, interval) + {:noreply, %__MODULE__{state | blocks_to_fetch: blocks_to_fetch}} end end From 48bc9a16bd2179930526fa2f985db1cb5bcc7afb Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Tue, 7 Nov 2023 15:03:28 +0300 Subject: [PATCH 021/607] Take into account `CHAIN_TYPE` for RSK --- CHANGELOG.md | 1 + .../views/api/v2/block_view.ex | 24 +- .../lib/ethereum_jsonrpc/block.ex | 313 ++++++++++-------- .../lib/ethereum_jsonrpc/blocks.ex | 19 +- .../test/ethereum_jsonrpc/block_test.exs | 66 ++-- apps/explorer/config/config.exs | 1 + apps/explorer/lib/explorer/chain/block.ex | 54 ++- ...4094744_add_rootstock_fields_to_blocks.exs | 0 .../indexer/fetcher/rootstock_data_test.exs | 226 ++++++------- config/config_helper.exs | 3 + config/runtime.exs | 24 +- 11 files changed, 410 insertions(+), 321 deletions(-) rename apps/explorer/priv/{repo => rsk}/migrations/20230724094744_add_rootstock_fields_to_blocks.exs (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7422a7055f45..541fd50a7359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ ### Chore - [#8802](https://github.com/blockscout/blockscout/pull/8802) - Enable API v2 by default +- [#8742](https://github.com/blockscout/blockscout/pull/8742) - Merge rsk branch into the master branch - [#8728](https://github.com/blockscout/blockscout/pull/8728) - Remove repos_list (default value for ecto repos) from Explorer.ReleaseTasks
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex index 3d6e3f57f004..bc6b62d7c712 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex @@ -61,7 +61,7 @@ defmodule BlockScoutWeb.API.V2.BlockView do "tx_fees" => tx_fees, "withdrawals_count" => count_withdrawals(block) } - |> add_rootstock_fields(block, single_block?) + |> chain_type_fields(block, single_block?) end def prepare_rewards(rewards, block, single_block?) do @@ -117,14 +117,18 @@ defmodule BlockScoutWeb.API.V2.BlockView do def count_withdrawals(%Block{withdrawals: withdrawals}) when is_list(withdrawals), do: Enum.count(withdrawals) def count_withdrawals(_), do: nil - defp add_rootstock_fields(prepared_block, _block, false), do: prepared_block - - defp add_rootstock_fields(prepared_block, block, true) do - prepared_block - |> Map.put("minimum_gas_price", block.minimum_gas_price) - |> Map.put("bitcoin_merged_mining_header", block.bitcoin_merged_mining_header) - |> Map.put("bitcoin_merged_mining_coinbase_transaction", block.bitcoin_merged_mining_coinbase_transaction) - |> Map.put("bitcoin_merged_mining_merkle_proof", block.bitcoin_merged_mining_merkle_proof) - |> Map.put("hash_for_merged_mining", block.hash_for_merged_mining) + defp chain_type_fields(result, block, single_block?) do + case single_block? && Application.get_env(:explorer, :chain_type) do + "rsk" -> + result + |> Map.put("minimum_gas_price", block.minimum_gas_price) + |> Map.put("bitcoin_merged_mining_header", block.bitcoin_merged_mining_header) + |> Map.put("bitcoin_merged_mining_coinbase_transaction", block.bitcoin_merged_mining_coinbase_transaction) + |> Map.put("bitcoin_merged_mining_merkle_proof", block.bitcoin_merged_mining_merkle_proof) + |> Map.put("hash_for_merged_mining", block.hash_for_merged_mining) + + _ -> + result + end end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex index ea841f483d06..88f0213f98aa 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex @@ -8,8 +8,23 @@ defmodule EthereumJSONRPC.Block do alias EthereumJSONRPC.{Transactions, Uncles, Withdrawals} + if Application.compile_env(:explorer, :chain_type) == "rsk" do + @rootstock_fields quote( + do: [ + bitcoin_merged_mining_header: EthereumJSONRPC.data(), + bitcoin_merged_mining_coinbase_transaction: EthereumJSONRPC.data(), + bitcoin_merged_mining_merkle_proof: EthereumJSONRPC.data(), + hash_for_merged_mining: EthereumJSONRPC.data(), + minimum_gas_price: non_neg_integer() + ] + ) + else + @rootstock_fields quote(do: []) + end + @type elixir :: %{String.t() => non_neg_integer | DateTime.t() | String.t() | nil} @type params :: %{ + unquote_splicing(@rootstock_fields), difficulty: pos_integer(), extra_data: EthereumJSONRPC.hash(), gas_limit: non_neg_integer(), @@ -30,12 +45,7 @@ defmodule EthereumJSONRPC.Block do transactions_root: EthereumJSONRPC.hash(), uncles: [EthereumJSONRPC.hash()], base_fee_per_gas: non_neg_integer(), - withdrawals_root: EthereumJSONRPC.hash(), - minimum_gas_price: non_neg_integer(), - bitcoin_merged_mining_header: EthereumJSONRPC.data(), - bitcoin_merged_mining_coinbase_transaction: EthereumJSONRPC.data(), - bitcoin_merged_mining_merkle_proof: EthereumJSONRPC.data(), - hash_for_merged_mining: EthereumJSONRPC.data() + withdrawals_root: EthereumJSONRPC.hash() } @typedoc """ @@ -74,11 +84,15 @@ defmodule EthereumJSONRPC.Block do `t:EthereumJSONRPC.hash/0`. * `"baseFeePerGas"` - `t:EthereumJSONRPC.quantity/0` of wei to denote amount of fee burned per unit gas used. Introduced in [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) * `"withdrawalsRoot"` - `t:EthereumJSONRPC.hash/0` of the root of the withdrawals. - * `"minimumGasPrice"` - `t:EthereumJSONRPC.quantity/0` of the minimum gas price for this block. - * `"bitcoinMergedMiningHeader"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining header. - * `"bitcoinMergedMiningCoinbaseTransaction"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining coinbase transaction. - * `"bitcoinMergedMiningMerkleProof"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining merkle proof. - * `"hashForMergedMining"` - `t:EthereumJSONRPC.data/0` of the hash for merged mining. + #{if Application.compile_env(:explorer, :chain_type) == "rsk" do + """ + * `"minimumGasPrice"` - `t:EthereumJSONRPC.quantity/0` of the minimum gas price for this block. + * `"bitcoinMergedMiningHeader"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining header. + * `"bitcoinMergedMiningCoinbaseTransaction"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining coinbase transaction. + * `"bitcoinMergedMiningMerkleProof"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining merkle proof. + * `"hashForMergedMining"` - `t:EthereumJSONRPC.data/0` of the hash for merged mining. + """ + end} """ @type t :: %{String.t() => EthereumJSONRPC.data() | EthereumJSONRPC.hash() | EthereumJSONRPC.quantity() | nil} @@ -129,13 +143,18 @@ defmodule EthereumJSONRPC.Block do ...> "timestamp" => Timex.parse!("2017-12-15T21:03:30Z", "{ISO:Extended:Z}"), ...> "totalDifficulty" => 340282366920938463463374607431465668165, ...> "transactions" => [], - ...> "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - ...> "uncles" => [], - ...> "minimumGasPrice" => 345786, - ...> "bitcoinMergedMiningHeader" => "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9", - ...> "bitcoinMergedMiningCoinbaseTransaction" => "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000", - ...> "bitcoinMergedMiningMerkleProof" => "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7", - ...> "hashForMergedMining" => "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40", + ...> "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",\ + #{if Application.compile_env(:explorer, :chain_type) == "rsk" do + """ + + ...> "minimumGasPrice" => 345786, + ...> "bitcoinMergedMiningHeader" => "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9", + ...> "bitcoinMergedMiningCoinbaseTransaction" => "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000", + ...> "bitcoinMergedMiningMerkleProof" => "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7", + ...> "hashForMergedMining" => "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40",\ + """ + end} + ...> "uncles" => [] ...> } ...> ) %{ @@ -157,13 +176,18 @@ defmodule EthereumJSONRPC.Block do timestamp: Timex.parse!("2017-12-15T21:03:30Z", "{ISO:Extended:Z}"), total_difficulty: 340282366920938463463374607431465668165, transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - uncles: [], - withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - bitcoin_merged_mining_coinbase_transaction: "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000", - bitcoin_merged_mining_header: "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9", - bitcoin_merged_mining_merkle_proof: "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7", - hash_for_merged_mining: "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40", - minimum_gas_price: 345786 + uncles: [],\ + #{if Application.compile_env(:explorer, :chain_type) == "rsk" do + """ + + bitcoin_merged_mining_coinbase_transaction: "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000", + bitcoin_merged_mining_header: "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9", + bitcoin_merged_mining_merkle_proof: "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7", + hash_for_merged_mining: "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40", + minimum_gas_price: 345786,\ + """ + end} + withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" } [Geth] `elixir` can be converted to params @@ -211,39 +235,49 @@ defmodule EthereumJSONRPC.Block do timestamp: Timex.parse!("2015-07-30T15:32:07Z", "{ISO:Extended:Z}"), total_difficulty: 1039309006117, transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - uncles: [], - withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - bitcoin_merged_mining_coinbase_transaction: nil, - bitcoin_merged_mining_header: nil, - bitcoin_merged_mining_merkle_proof: nil, - hash_for_merged_mining: nil, - minimum_gas_price: nil + uncles: [],\ + #{if Application.compile_env(:explorer, :chain_type) == "rsk" do + """ + + bitcoin_merged_mining_coinbase_transaction: nil, + bitcoin_merged_mining_header: nil, + bitcoin_merged_mining_merkle_proof: nil, + hash_for_merged_mining: nil, + minimum_gas_price: nil,\ + """ + end} + withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" } - """ @spec elixir_to_params(elixir) :: params - def elixir_to_params( - %{ - "difficulty" => difficulty, - "extraData" => extra_data, - "gasLimit" => gas_limit, - "gasUsed" => gas_used, - "hash" => hash, - "logsBloom" => logs_bloom, - "miner" => miner_hash, - "number" => number, - "parentHash" => parent_hash, - "receiptsRoot" => receipts_root, - "sha3Uncles" => sha3_uncles, - "size" => size, - "stateRoot" => state_root, - "timestamp" => timestamp, - "totalDifficulty" => total_difficulty, - "transactionsRoot" => transactions_root, - "uncles" => uncles, - "baseFeePerGas" => base_fee_per_gas - } = elixir - ) do + def elixir_to_params(elixir) do + elixir + |> do_elixir_to_params() + |> chain_type_fields(elixir) + end + + defp do_elixir_to_params( + %{ + "difficulty" => difficulty, + "extraData" => extra_data, + "gasLimit" => gas_limit, + "gasUsed" => gas_used, + "hash" => hash, + "logsBloom" => logs_bloom, + "miner" => miner_hash, + "number" => number, + "parentHash" => parent_hash, + "receiptsRoot" => receipts_root, + "sha3Uncles" => sha3_uncles, + "size" => size, + "stateRoot" => state_root, + "timestamp" => timestamp, + "totalDifficulty" => total_difficulty, + "transactionsRoot" => transactions_root, + "uncles" => uncles, + "baseFeePerGas" => base_fee_per_gas + } = elixir + ) do %{ difficulty: difficulty, extra_data: extra_data, @@ -266,36 +300,31 @@ defmodule EthereumJSONRPC.Block do uncles: uncles, base_fee_per_gas: base_fee_per_gas, withdrawals_root: - Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), - minimum_gas_price: Map.get(elixir, "minimumGasPrice"), - bitcoin_merged_mining_header: Map.get(elixir, "bitcoinMergedMiningHeader"), - bitcoin_merged_mining_coinbase_transaction: Map.get(elixir, "bitcoinMergedMiningCoinbaseTransaction"), - bitcoin_merged_mining_merkle_proof: Map.get(elixir, "bitcoinMergedMiningMerkleProof"), - hash_for_merged_mining: Map.get(elixir, "hashForMergedMining") + Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") } end - def elixir_to_params( - %{ - "difficulty" => difficulty, - "extraData" => extra_data, - "gasLimit" => gas_limit, - "gasUsed" => gas_used, - "hash" => hash, - "logsBloom" => logs_bloom, - "miner" => miner_hash, - "number" => number, - "parentHash" => parent_hash, - "receiptsRoot" => receipts_root, - "sha3Uncles" => sha3_uncles, - "size" => size, - "stateRoot" => state_root, - "timestamp" => timestamp, - "transactionsRoot" => transactions_root, - "uncles" => uncles, - "baseFeePerGas" => base_fee_per_gas - } = elixir - ) do + defp do_elixir_to_params( + %{ + "difficulty" => difficulty, + "extraData" => extra_data, + "gasLimit" => gas_limit, + "gasUsed" => gas_used, + "hash" => hash, + "logsBloom" => logs_bloom, + "miner" => miner_hash, + "number" => number, + "parentHash" => parent_hash, + "receiptsRoot" => receipts_root, + "sha3Uncles" => sha3_uncles, + "size" => size, + "stateRoot" => state_root, + "timestamp" => timestamp, + "transactionsRoot" => transactions_root, + "uncles" => uncles, + "baseFeePerGas" => base_fee_per_gas + } = elixir + ) do %{ difficulty: difficulty, extra_data: extra_data, @@ -317,36 +346,31 @@ defmodule EthereumJSONRPC.Block do uncles: uncles, base_fee_per_gas: base_fee_per_gas, withdrawals_root: - Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), - minimum_gas_price: Map.get(elixir, "minimumGasPrice"), - bitcoin_merged_mining_header: Map.get(elixir, "bitcoinMergedMiningHeader"), - bitcoin_merged_mining_coinbase_transaction: Map.get(elixir, "bitcoinMergedMiningCoinbaseTransaction"), - bitcoin_merged_mining_merkle_proof: Map.get(elixir, "bitcoinMergedMiningMerkleProof"), - hash_for_merged_mining: Map.get(elixir, "hashForMergedMining") + Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") } end - def elixir_to_params( - %{ - "difficulty" => difficulty, - "extraData" => extra_data, - "gasLimit" => gas_limit, - "gasUsed" => gas_used, - "hash" => hash, - "logsBloom" => logs_bloom, - "miner" => miner_hash, - "number" => number, - "parentHash" => parent_hash, - "receiptsRoot" => receipts_root, - "sha3Uncles" => sha3_uncles, - "size" => size, - "stateRoot" => state_root, - "timestamp" => timestamp, - "totalDifficulty" => total_difficulty, - "transactionsRoot" => transactions_root, - "uncles" => uncles - } = elixir - ) do + defp do_elixir_to_params( + %{ + "difficulty" => difficulty, + "extraData" => extra_data, + "gasLimit" => gas_limit, + "gasUsed" => gas_used, + "hash" => hash, + "logsBloom" => logs_bloom, + "miner" => miner_hash, + "number" => number, + "parentHash" => parent_hash, + "receiptsRoot" => receipts_root, + "sha3Uncles" => sha3_uncles, + "size" => size, + "stateRoot" => state_root, + "timestamp" => timestamp, + "totalDifficulty" => total_difficulty, + "transactionsRoot" => transactions_root, + "uncles" => uncles + } = elixir + ) do %{ difficulty: difficulty, extra_data: extra_data, @@ -368,36 +392,31 @@ defmodule EthereumJSONRPC.Block do transactions_root: transactions_root, uncles: uncles, withdrawals_root: - Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), - minimum_gas_price: Map.get(elixir, "minimumGasPrice"), - bitcoin_merged_mining_header: Map.get(elixir, "bitcoinMergedMiningHeader"), - bitcoin_merged_mining_coinbase_transaction: Map.get(elixir, "bitcoinMergedMiningCoinbaseTransaction"), - bitcoin_merged_mining_merkle_proof: Map.get(elixir, "bitcoinMergedMiningMerkleProof"), - hash_for_merged_mining: Map.get(elixir, "hashForMergedMining") + Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") } end # Geth: a response from eth_getblockbyhash for uncle blocks is without `totalDifficulty` param - def elixir_to_params( - %{ - "difficulty" => difficulty, - "extraData" => extra_data, - "gasLimit" => gas_limit, - "gasUsed" => gas_used, - "hash" => hash, - "logsBloom" => logs_bloom, - "miner" => miner_hash, - "number" => number, - "parentHash" => parent_hash, - "receiptsRoot" => receipts_root, - "sha3Uncles" => sha3_uncles, - "size" => size, - "stateRoot" => state_root, - "timestamp" => timestamp, - "transactionsRoot" => transactions_root, - "uncles" => uncles - } = elixir - ) do + defp do_elixir_to_params( + %{ + "difficulty" => difficulty, + "extraData" => extra_data, + "gasLimit" => gas_limit, + "gasUsed" => gas_used, + "hash" => hash, + "logsBloom" => logs_bloom, + "miner" => miner_hash, + "number" => number, + "parentHash" => parent_hash, + "receiptsRoot" => receipts_root, + "sha3Uncles" => sha3_uncles, + "size" => size, + "stateRoot" => state_root, + "timestamp" => timestamp, + "transactionsRoot" => transactions_root, + "uncles" => uncles + } = elixir + ) do %{ difficulty: difficulty, extra_data: extra_data, @@ -418,15 +437,27 @@ defmodule EthereumJSONRPC.Block do transactions_root: transactions_root, uncles: uncles, withdrawals_root: - Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), - minimum_gas_price: Map.get(elixir, "minimumGasPrice"), - bitcoin_merged_mining_header: Map.get(elixir, "bitcoinMergedMiningHeader"), - bitcoin_merged_mining_coinbase_transaction: Map.get(elixir, "bitcoinMergedMiningCoinbaseTransaction"), - bitcoin_merged_mining_merkle_proof: Map.get(elixir, "bitcoinMergedMiningMerkleProof"), - hash_for_merged_mining: Map.get(elixir, "hashForMergedMining") + Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") } end + defp chain_type_fields(params, elixir) do + case Application.get_env(:explorer, :chain_type) do + "rsk" -> + params + |> Map.merge(%{ + minimum_gas_price: Map.get(elixir, "minimumGasPrice"), + bitcoin_merged_mining_header: Map.get(elixir, "bitcoinMergedMiningHeader"), + bitcoin_merged_mining_coinbase_transaction: Map.get(elixir, "bitcoinMergedMiningCoinbaseTransaction"), + bitcoin_merged_mining_merkle_proof: Map.get(elixir, "bitcoinMergedMiningMerkleProof"), + hash_for_merged_mining: Map.get(elixir, "hashForMergedMining") + }) + + _ -> + params + end + end + @doc """ Get `t:EthereumJSONRPC.Transactions.elixir/0` from `t:elixir/0` diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex index 4a79b51687f3..d9a697c1acbd 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex @@ -116,13 +116,18 @@ defmodule EthereumJSONRPC.Blocks do timestamp: Timex.parse!("1970-01-01T00:00:00Z", "{ISO:Extended:Z}"), total_difficulty: 131072, transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - uncles: ["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273311"], - withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - bitcoin_merged_mining_coinbase_transaction: nil, - bitcoin_merged_mining_header: nil, - bitcoin_merged_mining_merkle_proof: nil, - hash_for_merged_mining: nil, - minimum_gas_price: nil + uncles: ["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273311"],\ + #{if Application.compile_env(:explorer, :chain_type) == "rsk" do + """ + + bitcoin_merged_mining_coinbase_transaction: nil, + bitcoin_merged_mining_header: nil, + bitcoin_merged_mining_merkle_proof: nil, + hash_for_merged_mining: nil, + minimum_gas_price: nil,\ + """ + end} + withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" } ] diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs index 5f0e59c91cd8..f8d4ecd00172 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs @@ -32,34 +32,44 @@ defmodule EthereumJSONRPC.BlockTest do "uncles" => [] }) - assert result == %{ - difficulty: 17_561_410_778, - extra_data: "0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32", - gas_limit: 5000, - gas_used: 0, - hash: "0x4d9423080290a650eaf6db19c87c76dff83d1b4ab64aefe6e5c5aa2d1f4b6623", - logs_bloom: - "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - mix_hash: "0xbbb93d610b2b0296a59f18474ac3d6086a9902aa7ca4b9a306692f7c3d496fdf", - miner_hash: "0xbb7b8287f3f0a933474a79eae42cbca977791171", - nonce: 5_539_500_215_739_777_653, - number: 59, - parent_hash: "0xcd5b5c4cecd7f18a13fe974255badffd58e737dc67596d56bc01f063dd282e9e", - receipts_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - sha3_uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - size: 542, - state_root: "0x6fd0a5d82ca77d9f38c3ebbde11b11d304a5fcf3854f291df64395ab38ed43ba", - timestamp: Timex.parse!("2015-07-30T15:32:07Z", "{ISO:Extended:Z}"), - total_difficulty: nil, - transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - uncles: [], - withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - bitcoin_merged_mining_coinbase_transaction: nil, - bitcoin_merged_mining_header: nil, - bitcoin_merged_mining_merkle_proof: nil, - hash_for_merged_mining: nil, - minimum_gas_price: nil - } + assert result == + %{ + difficulty: 17_561_410_778, + extra_data: "0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32", + gas_limit: 5000, + gas_used: 0, + hash: "0x4d9423080290a650eaf6db19c87c76dff83d1b4ab64aefe6e5c5aa2d1f4b6623", + logs_bloom: + "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + mix_hash: "0xbbb93d610b2b0296a59f18474ac3d6086a9902aa7ca4b9a306692f7c3d496fdf", + miner_hash: "0xbb7b8287f3f0a933474a79eae42cbca977791171", + nonce: 5_539_500_215_739_777_653, + number: 59, + parent_hash: "0xcd5b5c4cecd7f18a13fe974255badffd58e737dc67596d56bc01f063dd282e9e", + receipts_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + sha3_uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + size: 542, + state_root: "0x6fd0a5d82ca77d9f38c3ebbde11b11d304a5fcf3854f291df64395ab38ed43ba", + timestamp: Timex.parse!("2015-07-30T15:32:07Z", "{ISO:Extended:Z}"), + total_difficulty: nil, + transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + uncles: [], + withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + } + |> (&if(Application.get_env(:explorer, :chain_type) == "rsk", + do: + Map.merge( + &1, + %{ + bitcoin_merged_mining_coinbase_transaction: nil, + bitcoin_merged_mining_header: nil, + bitcoin_merged_mining_merkle_proof: nil, + hash_for_merged_mining: nil, + minimum_gas_price: nil + } + ), + else: &1 + )).() end end diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index feb7ca93e8b3..e9d1eb627e9c 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -11,6 +11,7 @@ import Config # General application configuration config :explorer, + chain_type: ConfigHelper.chain_type(), ecto_repos: ConfigHelper.repos(), token_functions_reader_max_retries: 3, # for not fully indexed blockchains diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index 6cef72ae5e07..b2fa9d97f2a4 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -10,7 +10,15 @@ defmodule Explorer.Chain.Block do alias Explorer.Chain.{Address, Block, Gas, Hash, PendingBlockOperation, Transaction, Wei, Withdrawal} alias Explorer.Chain.Block.{Reward, SecondDegreeRelation} - @optional_attrs ~w(size refetch_needed total_difficulty difficulty base_fee_per_gas minimum_gas_price bitcoin_merged_mining_header bitcoin_merged_mining_coinbase_transaction bitcoin_merged_mining_merkle_proof hash_for_merged_mining)a + @optional_attrs ~w(size refetch_needed total_difficulty difficulty base_fee_per_gas)a + |> (&(case Application.compile_env(:explorer, :chain_type) == "rsk" do + "rsk" -> + &1 ++ + ~w(minimum_gas_price bitcoin_merged_mining_header bitcoin_merged_mining_coinbase_transaction bitcoin_merged_mining_merkle_proof hash_for_merged_mining)a + + _ -> + &1 + end)).() @required_attrs ~w(consensus gas_limit gas_used hash miner_hash nonce number parent_hash timestamp)a @@ -26,6 +34,20 @@ defmodule Explorer.Chain.Block do """ @type block_number :: non_neg_integer() + if Application.compile_env(:explorer, :chain_type) == "rsk" do + @rootstock_fields quote( + do: [ + bitcoin_merged_mining_header: binary(), + bitcoin_merged_mining_coinbase_transaction: binary(), + bitcoin_merged_mining_merkle_proof: binary(), + hash_for_merged_mining: binary(), + minimum_gas_price: Decimal.t() + ] + ) + else + @rootstock_fields quote(do: []) + end + @typedoc """ * `consensus` * `true` - this is a block on the longest consensus agreed upon chain. @@ -47,8 +69,18 @@ defmodule Explorer.Chain.Block do * `total_difficulty` - the total `difficulty` of the chain until this block. * `transactions` - the `t:Explorer.Chain.Transaction.t/0` in this block. * `base_fee_per_gas` - Minimum fee required per unit of gas. Fee adjusts based on network congestion. + #{if Application.compile_env(:explorer, :chain_type) == "rsk" do + """ + * `bitcoin_merged_mining_header` - Bitcoin merged mining header on Rootstock chains. + * `bitcoin_merged_mining_coinbase_transaction` - Bitcoin merged mining coinbase transaction on Rootstock chains. + * `bitcoin_merged_mining_merkle_proof` - Bitcoin merged mining merkle proof on Rootstock chains. + * `hash_for_merged_mining` - Hash for merged mining on Rootstock chains. + * `minimum_gas_price` - Minimum block gas price on Rootstock chains. + """ + end} """ @type t :: %__MODULE__{ + unquote_splicing(@rootstock_fields), consensus: boolean(), difficulty: difficulty(), gas_limit: Gas.t(), @@ -65,12 +97,7 @@ defmodule Explorer.Chain.Block do transactions: %Ecto.Association.NotLoaded{} | [Transaction.t()], refetch_needed: boolean(), base_fee_per_gas: Wei.t(), - is_empty: boolean(), - minimum_gas_price: Decimal.t(), - bitcoin_merged_mining_header: binary(), - bitcoin_merged_mining_coinbase_transaction: binary(), - bitcoin_merged_mining_merkle_proof: binary(), - hash_for_merged_mining: binary() + is_empty: boolean() } @primary_key {:hash, Hash.Full, autogenerate: false} @@ -87,11 +114,14 @@ defmodule Explorer.Chain.Block do field(:refetch_needed, :boolean) field(:base_fee_per_gas, Wei) field(:is_empty, :boolean) - field(:minimum_gas_price, :decimal) - field(:bitcoin_merged_mining_header, :binary) - field(:bitcoin_merged_mining_coinbase_transaction, :binary) - field(:bitcoin_merged_mining_merkle_proof, :binary) - field(:hash_for_merged_mining, :binary) + + if Application.compile_env(:explorer, :chain_type) == "rsk" do + field(:bitcoin_merged_mining_header, :binary) + field(:bitcoin_merged_mining_coinbase_transaction, :binary) + field(:bitcoin_merged_mining_merkle_proof, :binary) + field(:hash_for_merged_mining, :binary) + field(:minimum_gas_price, :decimal) + end timestamps() diff --git a/apps/explorer/priv/repo/migrations/20230724094744_add_rootstock_fields_to_blocks.exs b/apps/explorer/priv/rsk/migrations/20230724094744_add_rootstock_fields_to_blocks.exs similarity index 100% rename from apps/explorer/priv/repo/migrations/20230724094744_add_rootstock_fields_to_blocks.exs rename to apps/explorer/priv/rsk/migrations/20230724094744_add_rootstock_fields_to_blocks.exs diff --git a/apps/indexer/test/indexer/fetcher/rootstock_data_test.exs b/apps/indexer/test/indexer/fetcher/rootstock_data_test.exs index b3011a283421..d7954ce886b8 100644 --- a/apps/indexer/test/indexer/fetcher/rootstock_data_test.exs +++ b/apps/indexer/test/indexer/fetcher/rootstock_data_test.exs @@ -10,125 +10,127 @@ defmodule Indexer.Fetcher.RootstockDataTest do setup :verify_on_exit! setup :set_mox_global - test "do not start when all old blocks are fetched", %{json_rpc_named_arguments: json_rpc_named_arguments} do - RootstockData.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) - - :timer.sleep(300) - - assert [{Indexer.Fetcher.RootstockData, :undefined, :worker, [Indexer.Fetcher.RootstockData]} | _] = - RootstockData.Supervisor |> Supervisor.which_children() - end - - test "stops when all old blocks are fetched", %{json_rpc_named_arguments: json_rpc_named_arguments} do - block_a = insert(:block) - block_b = insert(:block) - - block_a_number_string = integer_to_quantity(block_a.number) - block_b_number_string = integer_to_quantity(block_b.number) - - EthereumJSONRPC.Mox - |> stub(:json_rpc, fn requests, _options -> - {:ok, - Enum.map(requests, fn - %{id: id, method: "eth_getBlockByNumber", params: [^block_a_number_string, false]} -> - %{ - id: id, - result: %{ - "author" => "0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c", - "difficulty" => "0x6bc767dd80781", - "extraData" => "0x5050594520737061726b706f6f6c2d6574682d7477", - "gasLimit" => "0x7a121d", - "gasUsed" => "0x79cbe9", - "hash" => to_string(block_a.hash), - "logsBloom" => - "0x044d42d008801488400e1809190200a80d06105bc0c4100b047895c0d518327048496108388040140010b8208006288102e206160e21052322440924002090c1c808a0817405ab238086d028211014058e949401012403210314896702d06880c815c3060a0f0809987c81044488292cc11d57882c912a808ca10471c84460460040000c0001012804022000a42106591881d34407420ba401e1c08a8d00a000a34c11821a80222818a4102152c8a0c044032080c6462644223104d618e0e544072008120104408205c60510542264808488220403000106281a0290404220112c10b080145028c8000300b18a2c8280701c882e702210b00410834840108084", - "miner" => to_string(block_a.miner), - "mixHash" => "0xda53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010", - "nonce" => "0x0946e5f01fce12bc", - "number" => block_a_number_string, - "parentHash" => to_string(block_a.parent_hash), - "receiptsRoot" => "0xa7d2b82bd8526de11736c18bd5cc8cfe2692106c4364526f3310ad56d78669c4", - "sealFields" => [ - "0xa0da53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010", - "0x880946e5f01fce12bc" - ], - "sha3Uncles" => "0x483a8a21a5825ad270f358b3ea56e060bbb8b3082d9a92ec8fa17a5c7e6fc1b6", - "size" => "0x544c", - "stateRoot" => "0x85daa9cd528004c1609d4cb3520fd958e85983bb4183124a4a9f7137fd39c691", - "timestamp" => "0x5c8bc76e", - "totalDifficulty" => "0x201a42c35142ae94458", - "transactions" => [], - "transactionsRoot" => "0xcd6c12fa43cd4e92ad5c0bf232b30488bbcbfe273c5b4af0366fced0767d54db", - "uncles" => [], - "withdrawals" => [], - "minimumGasPrice" => "0x0", - "bitcoinMergedMiningHeader" => - "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9", - "bitcoinMergedMiningCoinbaseTransaction" => - "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000", - "bitcoinMergedMiningMerkleProof" => - "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7", - "hashForMergedMining" => "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40" + if Application.compile_env(:explorer, :chain_type) == "rsk" do + test "do not start when all old blocks are fetched", %{json_rpc_named_arguments: json_rpc_named_arguments} do + RootstockData.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + + :timer.sleep(300) + + assert [{Indexer.Fetcher.RootstockData, :undefined, :worker, [Indexer.Fetcher.RootstockData]} | _] = + RootstockData.Supervisor |> Supervisor.which_children() + end + + test "stops when all old blocks are fetched", %{json_rpc_named_arguments: json_rpc_named_arguments} do + block_a = insert(:block) + block_b = insert(:block) + + block_a_number_string = integer_to_quantity(block_a.number) + block_b_number_string = integer_to_quantity(block_b.number) + + EthereumJSONRPC.Mox + |> stub(:json_rpc, fn requests, _options -> + {:ok, + Enum.map(requests, fn + %{id: id, method: "eth_getBlockByNumber", params: [^block_a_number_string, false]} -> + %{ + id: id, + result: %{ + "author" => "0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c", + "difficulty" => "0x6bc767dd80781", + "extraData" => "0x5050594520737061726b706f6f6c2d6574682d7477", + "gasLimit" => "0x7a121d", + "gasUsed" => "0x79cbe9", + "hash" => to_string(block_a.hash), + "logsBloom" => + "0x044d42d008801488400e1809190200a80d06105bc0c4100b047895c0d518327048496108388040140010b8208006288102e206160e21052322440924002090c1c808a0817405ab238086d028211014058e949401012403210314896702d06880c815c3060a0f0809987c81044488292cc11d57882c912a808ca10471c84460460040000c0001012804022000a42106591881d34407420ba401e1c08a8d00a000a34c11821a80222818a4102152c8a0c044032080c6462644223104d618e0e544072008120104408205c60510542264808488220403000106281a0290404220112c10b080145028c8000300b18a2c8280701c882e702210b00410834840108084", + "miner" => to_string(block_a.miner), + "mixHash" => "0xda53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010", + "nonce" => "0x0946e5f01fce12bc", + "number" => block_a_number_string, + "parentHash" => to_string(block_a.parent_hash), + "receiptsRoot" => "0xa7d2b82bd8526de11736c18bd5cc8cfe2692106c4364526f3310ad56d78669c4", + "sealFields" => [ + "0xa0da53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010", + "0x880946e5f01fce12bc" + ], + "sha3Uncles" => "0x483a8a21a5825ad270f358b3ea56e060bbb8b3082d9a92ec8fa17a5c7e6fc1b6", + "size" => "0x544c", + "stateRoot" => "0x85daa9cd528004c1609d4cb3520fd958e85983bb4183124a4a9f7137fd39c691", + "timestamp" => "0x5c8bc76e", + "totalDifficulty" => "0x201a42c35142ae94458", + "transactions" => [], + "transactionsRoot" => "0xcd6c12fa43cd4e92ad5c0bf232b30488bbcbfe273c5b4af0366fced0767d54db", + "uncles" => [], + "withdrawals" => [], + "minimumGasPrice" => "0x0", + "bitcoinMergedMiningHeader" => + "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9", + "bitcoinMergedMiningCoinbaseTransaction" => + "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000", + "bitcoinMergedMiningMerkleProof" => + "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7", + "hashForMergedMining" => "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40" + } } - } - - %{id: id, method: "eth_getBlockByNumber", params: [^block_b_number_string, false]} -> - %{ - id: id, - result: %{ - "author" => "0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c", - "difficulty" => "0x6bc767dd80781", - "extraData" => "0x5050594520737061726b706f6f6c2d6574682d7477", - "gasLimit" => "0x7a121d", - "gasUsed" => "0x79cbe9", - "hash" => to_string(block_b.hash), - "logsBloom" => - "0x044d42d008801488400e1809190200a80d06105bc0c4100b047895c0d518327048496108388040140010b8208006288102e206160e21052322440924002090c1c808a0817405ab238086d028211014058e949401012403210314896702d06880c815c3060a0f0809987c81044488292cc11d57882c912a808ca10471c84460460040000c0001012804022000a42106591881d34407420ba401e1c08a8d00a000a34c11821a80222818a4102152c8a0c044032080c6462644223104d618e0e544072008120104408205c60510542264808488220403000106281a0290404220112c10b080145028c8000300b18a2c8280701c882e702210b00410834840108084", - "miner" => to_string(block_b.miner), - "mixHash" => "0xda53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010", - "nonce" => "0x0946e5f01fce12bc", - "number" => block_b_number_string, - "parentHash" => to_string(block_b.parent_hash), - "receiptsRoot" => "0xa7d2b82bd8526de11736c18bd5cc8cfe2692106c4364526f3310ad56d78669c4", - "sealFields" => [ - "0xa0da53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010", - "0x880946e5f01fce12bc" - ], - "sha3Uncles" => "0x483a8a21a5825ad270f358b3ea56e060bbb8b3082d9a92ec8fa17a5c7e6fc1b6", - "size" => "0x544c", - "stateRoot" => "0x85daa9cd528004c1609d4cb3520fd958e85983bb4183124a4a9f7137fd39c691", - "timestamp" => "0x5c8bc76e", - "totalDifficulty" => "0x201a42c35142ae94458", - "transactions" => [], - "transactionsRoot" => "0xcd6c12fa43cd4e92ad5c0bf232b30488bbcbfe273c5b4af0366fced0767d54db", - "uncles" => [], - "withdrawals" => [], - "minimumGasPrice" => "0x1", - "bitcoinMergedMiningHeader" => - "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9", - "bitcoinMergedMiningCoinbaseTransaction" => - "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000", - "bitcoinMergedMiningMerkleProof" => - "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7", - "hashForMergedMining" => "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40" + + %{id: id, method: "eth_getBlockByNumber", params: [^block_b_number_string, false]} -> + %{ + id: id, + result: %{ + "author" => "0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c", + "difficulty" => "0x6bc767dd80781", + "extraData" => "0x5050594520737061726b706f6f6c2d6574682d7477", + "gasLimit" => "0x7a121d", + "gasUsed" => "0x79cbe9", + "hash" => to_string(block_b.hash), + "logsBloom" => + "0x044d42d008801488400e1809190200a80d06105bc0c4100b047895c0d518327048496108388040140010b8208006288102e206160e21052322440924002090c1c808a0817405ab238086d028211014058e949401012403210314896702d06880c815c3060a0f0809987c81044488292cc11d57882c912a808ca10471c84460460040000c0001012804022000a42106591881d34407420ba401e1c08a8d00a000a34c11821a80222818a4102152c8a0c044032080c6462644223104d618e0e544072008120104408205c60510542264808488220403000106281a0290404220112c10b080145028c8000300b18a2c8280701c882e702210b00410834840108084", + "miner" => to_string(block_b.miner), + "mixHash" => "0xda53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010", + "nonce" => "0x0946e5f01fce12bc", + "number" => block_b_number_string, + "parentHash" => to_string(block_b.parent_hash), + "receiptsRoot" => "0xa7d2b82bd8526de11736c18bd5cc8cfe2692106c4364526f3310ad56d78669c4", + "sealFields" => [ + "0xa0da53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010", + "0x880946e5f01fce12bc" + ], + "sha3Uncles" => "0x483a8a21a5825ad270f358b3ea56e060bbb8b3082d9a92ec8fa17a5c7e6fc1b6", + "size" => "0x544c", + "stateRoot" => "0x85daa9cd528004c1609d4cb3520fd958e85983bb4183124a4a9f7137fd39c691", + "timestamp" => "0x5c8bc76e", + "totalDifficulty" => "0x201a42c35142ae94458", + "transactions" => [], + "transactionsRoot" => "0xcd6c12fa43cd4e92ad5c0bf232b30488bbcbfe273c5b4af0366fced0767d54db", + "uncles" => [], + "withdrawals" => [], + "minimumGasPrice" => "0x1", + "bitcoinMergedMiningHeader" => + "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9", + "bitcoinMergedMiningCoinbaseTransaction" => + "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000", + "bitcoinMergedMiningMerkleProof" => + "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7", + "hashForMergedMining" => "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40" + } } - } - end)} - end) + end)} + end) - pid = RootstockData.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + pid = RootstockData.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) - assert [{Indexer.Fetcher.RootstockData, worker_pid, :worker, [Indexer.Fetcher.RootstockData]} | _] = - RootstockData.Supervisor |> Supervisor.which_children() + assert [{Indexer.Fetcher.RootstockData, worker_pid, :worker, [Indexer.Fetcher.RootstockData]} | _] = + RootstockData.Supervisor |> Supervisor.which_children() - assert is_pid(worker_pid) + assert is_pid(worker_pid) - :timer.sleep(300) + :timer.sleep(300) - assert [{Indexer.Fetcher.RootstockData, :undefined, :worker, [Indexer.Fetcher.RootstockData]} | _] = - RootstockData.Supervisor |> Supervisor.which_children() + assert [{Indexer.Fetcher.RootstockData, :undefined, :worker, [Indexer.Fetcher.RootstockData]} | _] = + RootstockData.Supervisor |> Supervisor.which_children() - # Terminates the process so it finishes all Ecto processes. - GenServer.stop(pid) + # Terminates the process so it finishes all Ecto processes. + GenServer.stop(pid) + end end end diff --git a/config/config_helper.exs b/config/config_helper.exs index af4e8de6aefe..8086279fc44e 100644 --- a/config/config_helper.exs +++ b/config/config_helper.exs @@ -179,4 +179,7 @@ defmodule ConfigHelper do rescue err -> raise "Invalid JSON in environment variable #{env_var}: #{inspect(err)}" end + + @spec chain_type() :: String.t() + def chain_type, do: System.get_env("CHAIN_TYPE") || "ethereum" end diff --git a/config/runtime.exs b/config/runtime.exs index a7eaeaa0a36f..81459925ee18 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -4,8 +4,6 @@ import Config |> Path.join() |> Code.eval_file() -chain_type = System.get_env("CHAIN_TYPE") || "ethereum" - ###################### ### BlockScout Web ### ###################### @@ -162,7 +160,7 @@ config :ethereum_jsonrpc, EthereumJSONRPC.HTTP, config :ethereum_jsonrpc, EthereumJSONRPC.Geth, debug_trace_transaction_timeout: System.get_env("ETHEREUM_JSONRPC_DEBUG_TRACE_TRANSACTION_TIMEOUT", "5s"), tracer: - if(chain_type == "polygon_edge", + if(ConfigHelper.chain_type() == "polygon_edge", do: "polygon_edge", else: System.get_env("INDEXER_INTERNAL_TRANSACTIONS_TRACER_TYPE", "call_tracer") ) @@ -185,7 +183,6 @@ checksum_function = System.get_env("CHECKSUM_FUNCTION") exchange_rates_coin = System.get_env("EXCHANGE_RATES_COIN") config :explorer, - chain_type: chain_type, coin: System.get_env("COIN") || exchange_rates_coin || "ETH", coin_name: System.get_env("COIN_NAME") || exchange_rates_coin || "ETH", allowed_solidity_evm_versions: @@ -590,15 +587,19 @@ config :indexer, Indexer.Fetcher.Withdrawal.Supervisor, config :indexer, Indexer.Fetcher.Withdrawal, first_block: System.get_env("WITHDRAWALS_FIRST_BLOCK") -config :indexer, Indexer.Fetcher.PolygonEdge.Supervisor, disabled?: !(chain_type == "polygon_edge") +config :indexer, Indexer.Fetcher.PolygonEdge.Supervisor, disabled?: !(ConfigHelper.chain_type() == "polygon_edge") -config :indexer, Indexer.Fetcher.PolygonEdge.Deposit.Supervisor, disabled?: !(chain_type == "polygon_edge") +config :indexer, Indexer.Fetcher.PolygonEdge.Deposit.Supervisor, + disabled?: !(ConfigHelper.chain_type() == "polygon_edge") -config :indexer, Indexer.Fetcher.PolygonEdge.DepositExecute.Supervisor, disabled?: !(chain_type == "polygon_edge") +config :indexer, Indexer.Fetcher.PolygonEdge.DepositExecute.Supervisor, + disabled?: !(ConfigHelper.chain_type() == "polygon_edge") -config :indexer, Indexer.Fetcher.PolygonEdge.Withdrawal.Supervisor, disabled?: !(chain_type == "polygon_edge") +config :indexer, Indexer.Fetcher.PolygonEdge.Withdrawal.Supervisor, + disabled?: !(ConfigHelper.chain_type() == "polygon_edge") -config :indexer, Indexer.Fetcher.PolygonEdge.WithdrawalExit.Supervisor, disabled?: !(chain_type == "polygon_edge") +config :indexer, Indexer.Fetcher.PolygonEdge.WithdrawalExit.Supervisor, + disabled?: !(ConfigHelper.chain_type() == "polygon_edge") config :indexer, Indexer.Fetcher.PolygonEdge, polygon_edge_l1_rpc: System.get_env("INDEXER_POLYGON_EDGE_L1_RPC"), @@ -630,8 +631,9 @@ config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch.Supervisor, System.get_env("CHAIN_TYPE", "ethereum") == "polygon_zkevm" && ConfigHelper.parse_bool_env_var("INDEXER_ZKEVM_BATCHES_ENABLED") - config :indexer, Indexer.Fetcher.RootstockData.Supervisor, - disabled?: ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_ROOTSTOCK_DATA_FETCHER") +config :indexer, Indexer.Fetcher.RootstockData.Supervisor, + disabled?: + ConfigHelper.chain_type() != "rsk" || ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_ROOTSTOCK_DATA_FETCHER") config :indexer, Indexer.Fetcher.RootstockData, interval: ConfigHelper.parse_time_env_var("INDEXER_ROOTSTOCK_DATA_FETCHER_INTERVAL", "3s"), From ae15ea5fdb24bc3850a9fbe02624652ebdec9fd2 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 9 Nov 2023 21:11:40 +0300 Subject: [PATCH 022/607] CI for ETH Sepolia --- .../publish-docker-image-for-eth-sepolia.yml | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 .github/workflows/publish-docker-image-for-eth-sepolia.yml diff --git a/.github/workflows/publish-docker-image-for-eth-sepolia.yml b/.github/workflows/publish-docker-image-for-eth-sepolia.yml new file mode 100644 index 000000000000..d53b987b0a1c --- /dev/null +++ b/.github/workflows/publish-docker-image-for-eth-sepolia.yml @@ -0,0 +1,54 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Publish Docker image for specific chain branches + +on: + push: + branches: + - production-eth-sepolia-stg +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 5.3.1 + DOCKER_CHAIN_NAME: eth-sepolia + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: blockscout/blockscout + + - name: Add SHORT_SHA env property with commit short sha + run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:latest, blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} \ No newline at end of file From ca1bd8314dfa6058083f57ff7840fcce3626d57a Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Thu, 9 Nov 2023 21:37:01 +0300 Subject: [PATCH 023/607] API v2: address nft (#8634) * Add /api/v2/addresses/{address_hash}/nft and /api/v2/{address_hash}/nft/collections * Add filter to /api/v2/tokens/{address_hash}/instances?holder_address_hash={holder_address} * Changelog * Revert log disabling * Fix warn * Add @spec and @doc * Process review comments --- CHANGELOG.md | 1 + .../lib/block_scout_web/api_router.ex | 2 + .../lib/block_scout_web/chain.ex | 26 +- .../controllers/address_token_controller.ex | 2 +- .../controllers/api/v2/address_controller.ex | 141 ++-- .../controllers/api/v2/token_controller.ex | 45 +- .../lib/block_scout_web/paging_helper.ex | 18 +- .../views/api/v2/address_view.ex | 64 +- .../views/api/v2/token_view.ex | 56 +- .../api/v2/address_controller_test.exs | 607 ++++++++++++++++++ .../api/v2/token_controller_test.exs | 118 ++++ apps/explorer/config/test.exs | 6 +- apps/explorer/lib/explorer/chain.ex | 4 + .../chain/address/current_token_balance.ex | 8 +- .../lib/explorer/chain/token/instance.ex | 309 ++++++++- apps/explorer/lib/explorer/helper.ex | 22 + apps/explorer/test/support/factory.ex | 45 +- 17 files changed, 1396 insertions(+), 78 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7422a7055f45..998e0c642c10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [#8795](https://github.com/blockscout/blockscout/pull/8795) - Disable catchup indexer by env - [#8768](https://github.com/blockscout/blockscout/pull/8768) - Add possibility to search tokens by address hash - [#8750](https://github.com/blockscout/blockscout/pull/8750) - Support new eth-bytecode-db request metadata fields +- [#8634](https://github.com/blockscout/blockscout/pull/8634) - API v2: NFT for address - [#8609](https://github.com/blockscout/blockscout/pull/8609) - Change logs format to JSON; Add endpoint url to the block_scout_web logging ### Fixes diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 8d500a2a49cb..dee973786cfe 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -240,6 +240,8 @@ defmodule BlockScoutWeb.ApiRouter do get("/:address_hash_param/coin-balance-history", V2.AddressController, :coin_balance_history) get("/:address_hash_param/coin-balance-history-by-day", V2.AddressController, :coin_balance_history_by_day) get("/:address_hash_param/withdrawals", V2.AddressController, :withdrawals) + get("/:address_hash_param/nft", V2.AddressController, :nft_list) + get("/:address_hash_param/nft/collections", V2.AddressController, :nft_collections) end scope "/tokens" do diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index dc3bcbe83780..c7a317956057 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -101,16 +101,13 @@ defmodule BlockScoutWeb.Chain do end end - @spec next_page_params(any, any, any, any) :: nil | map - def next_page_params(next_page, list, params, is_ctb_with_fiat_value \\ false) + @spec next_page_params(any, list(), map(), (any -> map())) :: nil | map + def next_page_params(next_page, list, params, paging_function \\ &paging_params/1) def next_page_params([], _list, _params, _), do: nil - def next_page_params(_, list, params, is_ctb_with_fiat_value) do - paging_params = - if is_ctb_with_fiat_value, - do: paging_params_with_fiat_value(List.last(list)), - else: paging_params(List.last(list)) + def next_page_params(_, list, params, paging_function) do + paging_params = paging_function.(List.last(list)) next_page_params = Map.merge(params, paging_params) current_items_count_string = Map.get(next_page_params, "items_count") @@ -382,6 +379,18 @@ defmodule BlockScoutWeb.Chain do [paging_options: %{@default_paging_options | key: {id}}] end + def paging_options(%{ + "token_contract_address_hash" => token_contract_address_hash, + "token_id" => token_id, + "token_type" => token_type + }) do + [paging_options: %{@default_paging_options | key: {token_contract_address_hash, token_id, token_type}}] + end + + def paging_options(%{"token_contract_address_hash" => token_contract_address_hash, "token_type" => token_type}) do + [paging_options: %{@default_paging_options | key: {token_contract_address_hash, token_type}}] + end + def paging_options(_params), do: [paging_options: @default_paging_options] def put_key_value_to_paging_options([paging_options: paging_options], key, value) do @@ -593,7 +602,8 @@ defmodule BlockScoutWeb.Chain do %{"id" => msg_id} end - defp paging_params_with_fiat_value(%CurrentTokenBalance{id: id, value: value} = ctb) do + @spec paging_params_with_fiat_value(CurrentTokenBalance.t()) :: %{binary() => any} + def paging_params_with_fiat_value(%CurrentTokenBalance{id: id, value: value} = ctb) do %{"fiat_value" => ctb.fiat_value, "value" => value, "id" => id} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex index 3fa058758e6d..8433e96be186 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex @@ -22,7 +22,7 @@ defmodule BlockScoutWeb.AddressTokenController do {tokens, next_page} = split_list_by_page(token_balances_plus_one) next_page_path = - case next_page_params(next_page, tokens, params, true) do + case next_page_params(next_page, tokens, params, &BlockScoutWeb.Chain.paging_params_with_fiat_value/1) do nil -> nil diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex index 5512f086f133..1ca5db5dcdc2 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -12,12 +12,13 @@ defmodule BlockScoutWeb.API.V2.AddressController do ] import BlockScoutWeb.PagingHelper, - only: [delete_parameters_from_next_page_params: 1, token_transfers_types_options: 1] + only: [delete_parameters_from_next_page_params: 1, token_transfers_types_options: 1, nft_token_types_options: 1] alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.API.V2.{BlockView, TransactionView, WithdrawalView} alias Explorer.{Chain, Market} alias Explorer.Chain.Address.Counters + alias Explorer.Chain.Token.Instance alias Indexer.Fetcher.{CoinBalanceOnDemand, TokenBalanceOnDemand} @transaction_necessity_by_association [ @@ -54,14 +55,18 @@ defmodule BlockScoutWeb.API.V2.AddressController do api?: true ] + @nft_necessity_by_association [ + necessity_by_association: %{ + :token => :optional + } + ] + @api_true [api?: true] action_fallback(BlockScoutWeb.API.V2.FallbackController) def address(conn, %{"address_hash_param" => address_hash_string} = params) do - with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, - {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - {:not_found, {:ok, address}} <- {:not_found, Chain.hash_to_address(address_hash, @address_options)} do + with {:ok, _address_hash, address} <- validate_address(address_hash_string, params, @address_options) do CoinBalanceOnDemand.trigger_fetch(address) conn @@ -71,9 +76,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do end def counters(conn, %{"address_hash_param" => address_hash_string} = params) do - with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, - {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - {:not_found, {:ok, address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do + with {:ok, _address_hash, address} <- validate_address(address_hash_string, params) do {validation_count} = Counters.address_counters(address, @api_true) transactions_from_db = address.transactions_count || 0 @@ -90,9 +93,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do end def token_balances(conn, %{"address_hash_param" => address_hash_string} = params) do - with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, - {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do + with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do token_balances = address_hash |> Chain.fetch_last_token_balances(@api_true) @@ -108,9 +109,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do end def transactions(conn, %{"address_hash_param" => address_hash_string} = params) do - with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, - {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do + with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do options = @transaction_necessity_by_association |> Keyword.merge(paging_options(params)) @@ -132,12 +131,8 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn, %{"address_hash_param" => address_hash_string, "token" => token_address_hash_string} = params ) do - with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, - {:format, {:ok, token_address_hash}} <- {:format, Chain.string_to_address_hash(token_address_hash_string)}, - {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - {:ok, false} <- AccessHelper.restricted_access?(token_address_hash_string, params), - {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)}, - {:not_found, {:ok, _}} <- {:not_found, Chain.token_from_address_hash(token_address_hash, @api_true)} do + with {:ok, address_hash, _address} <- validate_address(address_hash_string, params), + {:ok, token_address_hash, _token_address} <- validate_address(token_address_hash_string, params) do paging_options = paging_options(params) options = @@ -176,9 +171,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do end def token_transfers(conn, %{"address_hash_param" => address_hash_string} = params) do - with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, - {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do + with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do paging_options = paging_options(params) options = @@ -207,9 +200,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do end def internal_transactions(conn, %{"address_hash_param" => address_hash_string} = params) do - with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, - {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do + with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do full_options = [ necessity_by_association: %{ @@ -242,9 +233,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do end def logs(conn, %{"address_hash_param" => address_hash_string, "topic" => topic} = params) do - with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, - {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do + with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do prepared_topic = String.trim(topic) formatted_topic = if String.starts_with?(prepared_topic, "0x"), do: prepared_topic, else: "0x" <> prepared_topic @@ -265,9 +254,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do end def logs(conn, %{"address_hash_param" => address_hash_string} = params) do - with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, - {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do + with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do options = params |> paging_options() |> Keyword.merge(@api_true) results_plus_one = Chain.address_to_logs(address_hash, false, options) @@ -284,9 +271,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do end def blocks_validated(conn, %{"address_hash_param" => address_hash_string} = params) do - with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, - {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do + with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do full_options = [ necessity_by_association: %{ @@ -330,9 +315,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do end def coin_balance_history_by_day(conn, %{"address_hash_param" => address_hash_string} = params) do - with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, - {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do + with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do balances_by_day = address_hash |> Chain.address_to_balances_by_day(@api_true) @@ -344,9 +327,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do end def tokens(conn, %{"address_hash_param" => address_hash_string} = params) do - with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, - {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do + with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do results_plus_one = address_hash |> Chain.fetch_paginated_last_token_balances( @@ -362,7 +343,13 @@ defmodule BlockScoutWeb.API.V2.AddressController do {tokens, next_page} = split_list_by_page(results_plus_one) - next_page_params = next_page |> next_page_params(tokens, delete_parameters_from_next_page_params(params), true) + next_page_params = + next_page + |> next_page_params( + tokens, + delete_parameters_from_next_page_params(params), + &BlockScoutWeb.Chain.paging_params_with_fiat_value/1 + ) conn |> put_status(200) @@ -371,9 +358,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do end def withdrawals(conn, %{"address_hash_param" => address_hash_string} = params) do - with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, - {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do + with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do options = @api_true |> Keyword.merge(paging_options(params)) withdrawals_plus_one = address_hash |> Chain.address_hash_to_withdrawals(options) {withdrawals, next_page} = split_list_by_page(withdrawals_plus_one) @@ -411,9 +396,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do end def tabs_counters(conn, %{"address_hash_param" => address_hash_string} = params) do - with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, - {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - {:not_found, {:ok, _address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)} do + with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do {validations, transactions, token_transfers, token_balances, logs, withdrawals, internal_txs} = Counters.address_limited_counters(address_hash, @api_true) @@ -430,4 +413,68 @@ defmodule BlockScoutWeb.API.V2.AddressController do }) end end + + def nft_list(conn, %{"address_hash_param" => address_hash_string} = params) do + with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do + results_plus_one = + Instance.nft_list( + address_hash, + params + |> paging_options() + |> Keyword.merge(nft_token_types_options(params)) + |> Keyword.merge(@api_true) + |> Keyword.merge(@nft_necessity_by_association) + ) + + {nfts, next_page} = split_list_by_page(results_plus_one) + + next_page_params = + next_page + |> next_page_params( + nfts, + delete_parameters_from_next_page_params(params), + &Instance.nft_list_next_page_params/1 + ) + + conn + |> put_status(200) + |> render(:nft_list, %{token_instances: nfts, next_page_params: next_page_params}) + end + end + + def nft_collections(conn, %{"address_hash_param" => address_hash_string} = params) do + with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do + results_plus_one = + Instance.nft_collections( + address_hash, + params + |> paging_options() + |> Keyword.merge(nft_token_types_options(params)) + |> Keyword.merge(@api_true) + |> Keyword.merge(@nft_necessity_by_association) + ) + + {collections, next_page} = split_list_by_page(results_plus_one) + + next_page_params = + next_page + |> next_page_params( + collections, + delete_parameters_from_next_page_params(params), + &Instance.nft_collections_next_page_params/1 + ) + + conn + |> put_status(200) + |> render(:nft_collections, %{collections: collections, next_page_params: next_page_params}) + end + end + + defp validate_address(address_hash_string, params, options \\ @api_true) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), + {:not_found, {:ok, address}} <- {:not_found, Chain.hash_to_address(address_hash, options, false)} do + {:ok, address_hash, address} + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex index 1ec2a32a3e0b..14852429ab61 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex @@ -2,8 +2,9 @@ defmodule BlockScoutWeb.API.V2.TokenController do use BlockScoutWeb, :controller alias BlockScoutWeb.AccessHelper - alias BlockScoutWeb.API.V2.TransactionView - alias Explorer.Chain + alias BlockScoutWeb.API.V2.{AddressView, TransactionView} + alias Explorer.{Chain, Repo} + alias Explorer.Chain.{Address, Token.Instance} alias Indexer.Fetcher.TokenTotalSupplyOnDemand import BlockScoutWeb.Chain, @@ -87,6 +88,43 @@ defmodule BlockScoutWeb.API.V2.TokenController do end end + def instances( + conn, + %{"address_hash_param" => address_hash_string, "holder_address_hash" => holder_address_hash_string} = params + ) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), + {:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)}, + {:not_found, false} <- {:not_found, Chain.is_erc_20_token?(token)}, + {:format, {:ok, holder_address_hash}} <- {:format, Chain.string_to_address_hash(holder_address_hash_string)}, + {:ok, false} <- AccessHelper.restricted_access?(holder_address_hash_string, params) do + holder_address = Repo.get_by(Address, hash: holder_address_hash) + + results_plus_one = + Instance.token_instances_by_holder_address_hash( + token, + holder_address_hash, + params + |> unique_tokens_paging_options() + |> Keyword.merge(@api_true) + ) + + {token_instances, next_page} = split_list_by_page(results_plus_one) + + next_page_params = + next_page |> unique_tokens_next_page(token_instances, delete_parameters_from_next_page_params(params)) + + conn + |> put_status(200) + |> put_view(AddressView) + |> render(:nft_list, %{ + token_instances: token_instances |> put_owner(holder_address), + next_page_params: next_page_params, + token: token + }) + end + end + def instances(conn, %{"address_hash_param" => address_hash_string} = params) do with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), @@ -218,4 +256,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do |> put_status(200) |> render(:tokens, %{tokens: tokens, next_page_params: next_page_params}) end + + defp put_owner(token_instances, holder_address), + do: Enum.map(token_instances, fn token_instance -> %Instance{token_instance | owner: holder_address} end) end diff --git a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex index 860a8f7d4a15..c9a3472c850f 100644 --- a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex @@ -10,6 +10,7 @@ defmodule BlockScoutWeb.PagingHelper do @allowed_filter_labels ["validated", "pending"] @allowed_type_labels ["coin_transfer", "contract_call", "contract_creation", "token_transfer", "token_creation"] @allowed_token_transfer_type_labels ["ERC-20", "ERC-721", "ERC-1155"] + @allowed_nft_token_type_labels ["ERC-721", "ERC-1155"] def paging_options(%{"block_number" => block_number_string, "index" => index_string}, [:validated | _]) do with {block_number, ""} <- Integer.parse(block_number_string), @@ -33,14 +34,29 @@ defmodule BlockScoutWeb.PagingHelper do def paging_options(_params, _filter), do: [paging_options: @default_paging_options] + @spec token_transfers_types_options(map()) :: [{:token_type, list}] def token_transfers_types_options(%{"type" => filters}) do [ - token_type: filters |> String.upcase() |> parse_filter(@allowed_token_transfer_type_labels) + token_type: filters_to_list(filters, @allowed_token_transfer_type_labels) ] end def token_transfers_types_options(_), do: [token_type: []] + @doc """ + Parse 'type' query parameter from request option map + """ + @spec nft_token_types_options(map()) :: [{:token_type, list}] + def nft_token_types_options(%{"type" => filters}) do + [ + token_type: filters_to_list(filters, @allowed_nft_token_type_labels) + ] + end + + def nft_token_types_options(_), do: [token_type: []] + + defp filters_to_list(filters, allowed), do: filters |> String.upcase() |> parse_filter(allowed) + # sobelow_skip ["DOS.StringToAtom"] def filter_options(%{"filter" => filter}, fallback) do filter = filter |> parse_filter(@allowed_filter_labels) |> Enum.map(&String.to_atom/1) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex index 9cdf88abb2f9..33a3cc601a7f 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex @@ -9,6 +9,7 @@ defmodule BlockScoutWeb.API.V2.AddressView do alias Explorer.{Chain, Market} alias Explorer.Chain.Address.Counters alias Explorer.Chain.{Address, SmartContract} + alias Explorer.Chain.Token.Instance @api_true [api?: true] @@ -54,6 +55,18 @@ defmodule BlockScoutWeb.API.V2.AddressView do } end + def render("nft_list.json", %{token_instances: token_instances, token: token, next_page_params: next_page_params}) do + %{"items" => Enum.map(token_instances, &prepare_nft(&1, token, true)), "next_page_params" => next_page_params} + end + + def render("nft_list.json", %{token_instances: token_instances, next_page_params: next_page_params}) do + %{"items" => Enum.map(token_instances, &prepare_nft(&1)), "next_page_params" => next_page_params} + end + + def render("nft_collections.json", %{collections: nft_collections, next_page_params: next_page_params}) do + %{"items" => Enum.map(nft_collections, &prepare_nft_collection(&1)), "next_page_params" => next_page_params} + end + def prepare_address({address, nonce}) do nil |> Helper.address_with_info(address, address.hash, true) @@ -123,7 +136,8 @@ defmodule BlockScoutWeb.API.V2.AddressView do fetch_and_render_token_instance( token_balance.token_id, token_balance.token, - token_balance.address_hash + token_balance.address_hash, + token_balance ) ) } @@ -156,7 +170,44 @@ defmodule BlockScoutWeb.API.V2.AddressView do end end - def fetch_and_render_token_instance(token_id, token, address_hash) do + defp prepare_nft(nft) do + prepare_nft(nft, nft.token, false) + end + + defp prepare_nft(nft, token, need_uniqueness?) do + Map.merge( + %{"token_type" => token.type, "value" => value(token.type, nft)}, + TokenView.prepare_token_instance(nft, token, need_uniqueness?) + ) + end + + defp prepare_nft_collection(collection) do + %{ + "token" => TokenView.render("token.json", token: collection.token), + "amount" => string_or_null(collection.distinct_token_instances_count || collection.value), + "token_instances" => + Enum.map(collection.preloaded_token_instances, fn instance -> + prepare_nft_for_collection(collection.token.type, instance) + end) + } + end + + defp prepare_nft_for_collection(token_type, instance) do + Map.merge( + %{"token_type" => token_type, "value" => value(token_type, instance)}, + TokenView.prepare_token_instance(instance, nil, false) + ) + end + + defp value("ERC-721", _), do: "1" + defp value(_, nft), do: nft.current_token_balance && to_string(nft.current_token_balance.value) + + defp string_or_null(nil), do: nil + defp string_or_null(other), do: to_string(other) + + # TODO think about this approach mb refactor or mark deprecated for example. + # Suggested solution: batch preload + def fetch_and_render_token_instance(token_id, token, address_hash, token_balance) do token_instance = case Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address( token_id, @@ -164,10 +215,13 @@ defmodule BlockScoutWeb.API.V2.AddressView do @api_true ) do # `%{hash: address_hash}` will match with `address_with_info(_, address_hash)` clause in `BlockScoutWeb.API.V2.Helper` - {:ok, token_instance} -> %{token_instance | owner: %{hash: address_hash}} - {:error, :not_found} -> %{token_id: token_id, metadata: nil, owner: %{hash: address_hash}} + {:ok, token_instance} -> %Instance{token_instance | owner: %{hash: address_hash}} + {:error, :not_found} -> %Instance{token_id: token_id, metadata: nil, owner: %{hash: address_hash}} end - TokenView.render("token_instance.json", %{token_instance: token_instance, token: token}) + TokenView.render("token_instance.json", %{ + token_instance: %Instance{token_instance | current_token_balance: token_balance}, + token: token + }) end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex index f78a3340d31b..681c5fa3418e 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex @@ -1,8 +1,13 @@ defmodule BlockScoutWeb.API.V2.TokenView do + use BlockScoutWeb, :view + alias BlockScoutWeb.API.V2.Helper alias BlockScoutWeb.NFTHelper + alias Ecto.Association.NotLoaded alias Explorer.Chain alias Explorer.Chain.Address + alias Explorer.Chain.Address.CurrentTokenBalance + alias Explorer.Chain.Token.Instance @api_true [api?: true] @@ -21,6 +26,10 @@ defmodule BlockScoutWeb.API.V2.TokenView do } end + def render("token.json", %{token: nil}) do + nil + end + def render("token.json", %{token: token}) do %{ "address" => Address.checksum(token.contract_address_hash), @@ -78,16 +87,16 @@ defmodule BlockScoutWeb.API.V2.TokenView do } end - def prepare_token_instance(instance, token) do - is_unique = - not (token.type == "ERC-1155") or - Chain.token_id_1155_is_unique?(token.contract_address_hash, instance.token_id, @api_true) + @doc """ + Internal json rendering function + """ + def prepare_token_instance(instance, token, need_uniqueness_and_owner? \\ true) do + is_unique = is_unique?(need_uniqueness_and_owner?, instance, token) %{ "id" => instance.token_id, "metadata" => instance.metadata, - "owner" => - if(is_unique, do: instance.owner && Helper.address_with_info(nil, instance.owner, instance.owner.hash, false)), + "owner" => token_instance_owner(is_unique, instance), "token" => render("token.json", %{token: token}), "external_app_url" => NFTHelper.external_url(instance), "animation_url" => instance.metadata && NFTHelper.retrieve_image(instance.metadata["animation_url"]), @@ -96,6 +105,41 @@ defmodule BlockScoutWeb.API.V2.TokenView do } end + defp token_instance_owner(false, _instance), do: nil + defp token_instance_owner(nil, _instance), do: nil + + defp token_instance_owner(_is_unique, %Instance{owner: %NotLoaded{}} = instance), + do: Helper.address_with_info(nil, nil, instance.owner_address_hash, false) + + defp token_instance_owner(_is_unique, %Instance{owner: nil} = instance), + do: Helper.address_with_info(nil, nil, instance.owner_address_hash, false) + + defp token_instance_owner(_is_unique, instance), + do: instance.owner && Helper.address_with_info(nil, instance.owner, instance.owner.hash, false) + + defp is_unique?(false, _instance, _token), do: nil + + defp is_unique?( + not_ignore?, + %Instance{current_token_balance: %CurrentTokenBalance{value: %Decimal{} = value}} = instance, + token + ) do + if Decimal.compare(value, 1) == :gt do + false + else + is_unique?(not_ignore?, %Instance{instance | current_token_balance: nil}, token) + end + end + + defp is_unique?(_not_ignore?, %Instance{current_token_balance: %CurrentTokenBalance{value: value}}, _token) + when value > 1, + do: false + + defp is_unique?(_, instance, token), + do: + not (token.type == "ERC-1155") or + Chain.token_id_1155_is_unique?(token.contract_address_hash, instance.token_id, @api_true) + defp prepare_holders_count(nil), do: nil defp prepare_holders_count(count) when count < 0, do: prepare_holders_count(0) defp prepare_holders_count(count), do: to_string(count) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index 962be40cafc0..ebe923dff6d5 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -12,6 +12,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do InternalTransaction, Log, Token, + Token.Instance, TokenTransfer, Transaction, Withdrawal @@ -1914,6 +1915,519 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do end end + describe "/addresses/{address_hash}/nft" do + setup do + {:ok, endpoint: &"/api/v2/addresses/#{&1}/nft"} + end + + test "get 404 on non existing address", %{conn: conn, endpoint: endpoint} do + address = build(:address) + + request = get(conn, endpoint.(address.hash)) + + assert %{"message" => "Not found"} = json_response(request, 404) + end + + test "get 422 on invalid address", %{conn: conn, endpoint: endpoint} do + request = get(conn, endpoint.("0x")) + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "get paginated ERC-721 nft", %{conn: conn, endpoint: endpoint} do + address = insert(:address) + + insert_list(51, :token_instance) + + token_instances = + for _ <- 0..50 do + erc_721_token = insert(:token, type: "ERC-721") + + insert(:token_instance, + owner_address_hash: address.hash, + token_contract_address_hash: erc_721_token.contract_address_hash + ) + |> Repo.preload([:token]) + end + # works because one token_id per token, despite ordering in DB: [asc: ti.token_contract_address_hash, desc: ti.token_id] + |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id}, :desc) + + request = get(conn, endpoint.(address.hash)) + assert response = json_response(request, 200) + + request_2nd_page = get(conn, endpoint.(address.hash), response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, token_instances) + end + + test "get paginated ERC-1155 nft", %{conn: conn, endpoint: endpoint} do + address = insert(:address) + + insert_list(51, :address_current_token_balance_with_token_id) + + token_instances = + for _ <- 0..50 do + token = insert(:token, type: "ERC-1155") + + ti = + insert(:token_instance, + token_contract_address_hash: token.contract_address_hash + ) + |> Repo.preload([:token]) + + current_token_balance = + insert(:address_current_token_balance_with_token_id_and_fixed_token_type, + address: address, + token_type: "ERC-1155", + token_id: ti.token_id, + token_contract_address_hash: token.contract_address_hash + ) + + %Instance{ti | current_token_balance: current_token_balance} + end + |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id}, :desc) + + request = get(conn, endpoint.(address.hash)) + assert response = json_response(request, 200) + + request_2nd_page = get(conn, endpoint.(address.hash), response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, token_instances) + end + + test "test filters", %{conn: conn, endpoint: endpoint} do + address = insert(:address) + + insert_list(51, :token_instance) + + token_instances_721 = + for _ <- 0..50 do + erc_721_token = insert(:token, type: "ERC-721") + + insert(:token_instance, + owner_address_hash: address.hash, + token_contract_address_hash: erc_721_token.contract_address_hash + ) + |> Repo.preload([:token]) + end + |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id}, :desc) + + insert_list(51, :address_current_token_balance_with_token_id) + + token_instances_1155 = + for _ <- 0..50 do + token = insert(:token, type: "ERC-1155") + + ti = + insert(:token_instance, + token_contract_address_hash: token.contract_address_hash + ) + |> Repo.preload([:token]) + + current_token_balance = + insert(:address_current_token_balance_with_token_id_and_fixed_token_type, + address: address, + token_type: "ERC-1155", + token_id: ti.token_id, + token_contract_address_hash: token.contract_address_hash + ) + + %Instance{ti | current_token_balance: current_token_balance} + end + |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id}, :desc) + + filter = %{"type" => "ERC-721"} + request = get(conn, endpoint.(address.hash), filter) + assert response = json_response(request, 200) + + request_2nd_page = get(conn, endpoint.(address.hash), Map.merge(response["next_page_params"], filter)) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, token_instances_721) + + filter = %{"type" => "ERC-1155"} + request = get(conn, endpoint.(address.hash), filter) + assert response = json_response(request, 200) + + request_2nd_page = get(conn, endpoint.(address.hash), Map.merge(response["next_page_params"], filter)) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, token_instances_1155) + end + + test "return all token instances", %{conn: conn, endpoint: endpoint} do + address = insert(:address) + + insert_list(51, :token_instance) + + token_instances_721 = + for _ <- 0..50 do + erc_721_token = insert(:token, type: "ERC-721") + + insert(:token_instance, + owner_address_hash: address.hash, + token_contract_address_hash: erc_721_token.contract_address_hash + ) + |> Repo.preload([:token]) + end + |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id}, :desc) + + insert_list(51, :address_current_token_balance_with_token_id) + + token_instances_1155 = + for _ <- 0..50 do + token = insert(:token, type: "ERC-1155") + + ti = + insert(:token_instance, + token_contract_address_hash: token.contract_address_hash + ) + |> Repo.preload([:token]) + + current_token_balance = + insert(:address_current_token_balance_with_token_id_and_fixed_token_type, + address: address, + token_type: "ERC-1155", + token_id: ti.token_id, + token_contract_address_hash: token.contract_address_hash + ) + + %Instance{ti | current_token_balance: current_token_balance} + end + |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id}, :desc) + + request = get(conn, endpoint.(address.hash)) + assert response = json_response(request, 200) + + request_2nd_page = get(conn, endpoint.(address.hash), response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + request_3rd_page = get(conn, endpoint.(address.hash), response_2nd_page["next_page_params"]) + assert response_3rd_page = json_response(request_3rd_page, 200) + + assert response["next_page_params"] != nil + assert response_2nd_page["next_page_params"] != nil + assert response_3rd_page["next_page_params"] == nil + + assert Enum.count(response["items"]) == 50 + assert Enum.count(response_2nd_page["items"]) == 50 + assert Enum.count(response_3rd_page["items"]) == 2 + + compare_item(Enum.at(token_instances_721, 50), Enum.at(response["items"], 0)) + compare_item(Enum.at(token_instances_721, 1), Enum.at(response["items"], 49)) + + compare_item(Enum.at(token_instances_721, 0), Enum.at(response_2nd_page["items"], 0)) + compare_item(Enum.at(token_instances_1155, 50), Enum.at(response_2nd_page["items"], 1)) + compare_item(Enum.at(token_instances_1155, 2), Enum.at(response_2nd_page["items"], 49)) + + compare_item(Enum.at(token_instances_1155, 1), Enum.at(response_3rd_page["items"], 0)) + compare_item(Enum.at(token_instances_1155, 0), Enum.at(response_3rd_page["items"], 1)) + end + end + + describe "/addresses/{address_hash}/nft/collections" do + setup do + {:ok, endpoint: &"/api/v2/addresses/#{&1}/nft/collections"} + end + + test "get 404 on non existing address", %{conn: conn, endpoint: endpoint} do + address = build(:address) + + request = get(conn, endpoint.(address.hash)) + + assert %{"message" => "Not found"} = json_response(request, 404) + end + + test "get 422 on invalid address", %{conn: conn, endpoint: endpoint} do + request = get(conn, endpoint.("0x")) + + assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) + end + + test "get paginated erc-721 collection", %{conn: conn, endpoint: endpoint} do + address = insert(:address) + + insert_list(51, :address_current_token_balance_with_token_id) + insert_list(51, :token_instance) + + ctbs = + for _ <- 0..50 do + token = insert(:token, type: "ERC-721") + amount = Enum.random(16..50) + + current_token_balance = + insert(:address_current_token_balance, + address: address, + token_type: "ERC-721", + token_id: nil, + token_contract_address_hash: token.contract_address_hash, + value: amount + ) + |> Repo.preload([:token]) + + token_instances = + for _ <- 0..(amount - 1) do + ti = + insert(:token_instance, + token_contract_address_hash: token.contract_address_hash, + owner_address_hash: address.hash + ) + |> Repo.preload([:token]) + + %Instance{ti | current_token_balance: current_token_balance} + end + |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id}, :desc) + + {current_token_balance, token_instances} + end + |> Enum.sort_by(&elem(&1, 0).token_contract_address_hash, :desc) + + request = get(conn, endpoint.(address.hash)) + assert response = json_response(request, 200) + + request_2nd_page = get(conn, endpoint.(address.hash), response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, ctbs) + end + + test "get paginated erc-1155 collection", %{conn: conn, endpoint: endpoint} do + address = insert(:address) + + insert_list(51, :address_current_token_balance_with_token_id) + insert_list(51, :token_instance) + + collections = + for _ <- 0..50 do + token = insert(:token, type: "ERC-1155") + amount = Enum.random(16..50) + + token_instances = + for _ <- 0..(amount - 1) do + ti = + insert(:token_instance, + token_contract_address_hash: token.contract_address_hash, + owner_address_hash: address.hash + ) + |> Repo.preload([:token]) + + current_token_balance = + insert(:address_current_token_balance, + address: address, + token_type: "ERC-1155", + token_id: ti.token_id, + token_contract_address_hash: token.contract_address_hash, + value: Enum.random(1..100_000) + ) + |> Repo.preload([:token]) + + %Instance{ti | current_token_balance: current_token_balance} + end + |> Enum.sort_by(& &1.token_id, :desc) + + {token, amount, token_instances} + end + |> Enum.sort_by(&elem(&1, 0).contract_address_hash, :desc) + + request = get(conn, endpoint.(address.hash)) + assert response = json_response(request, 200) + + request_2nd_page = get(conn, endpoint.(address.hash), response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, collections) + end + + test "test filters", %{conn: conn, endpoint: endpoint} do + address = insert(:address) + + insert_list(51, :address_current_token_balance_with_token_id) + insert_list(51, :token_instance) + + ctbs = + for _ <- 0..50 do + token = insert(:token, type: "ERC-721") + amount = Enum.random(16..50) + + current_token_balance = + insert(:address_current_token_balance, + address: address, + token_type: "ERC-721", + token_id: nil, + token_contract_address_hash: token.contract_address_hash, + value: amount + ) + |> Repo.preload([:token]) + + token_instances = + for _ <- 0..(amount - 1) do + ti = + insert(:token_instance, + token_contract_address_hash: token.contract_address_hash, + owner_address_hash: address.hash + ) + |> Repo.preload([:token]) + + %Instance{ti | current_token_balance: current_token_balance} + end + |> Enum.sort_by(& &1.token_id, :desc) + + {current_token_balance, token_instances} + end + |> Enum.sort_by(&elem(&1, 0).token_contract_address_hash, :desc) + + collections = + for _ <- 0..50 do + token = insert(:token, type: "ERC-1155") + amount = Enum.random(16..50) + + token_instances = + for _ <- 0..(amount - 1) do + ti = + insert(:token_instance, + token_contract_address_hash: token.contract_address_hash, + owner_address_hash: address.hash + ) + |> Repo.preload([:token]) + + current_token_balance = + insert(:address_current_token_balance, + address: address, + token_type: "ERC-1155", + token_id: ti.token_id, + token_contract_address_hash: token.contract_address_hash, + value: Enum.random(1..100_000) + ) + |> Repo.preload([:token]) + + %Instance{ti | current_token_balance: current_token_balance} + end + |> Enum.sort_by(& &1.token_id, :desc) + + {token, amount, token_instances} + end + |> Enum.sort_by(&elem(&1, 0).contract_address_hash, :desc) + + filter = %{"type" => "ERC-721"} + request = get(conn, endpoint.(address.hash), filter) + assert response = json_response(request, 200) + + request_2nd_page = get(conn, endpoint.(address.hash), Map.merge(response["next_page_params"], filter)) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, ctbs) + + filter = %{"type" => "ERC-1155"} + request = get(conn, endpoint.(address.hash), filter) + assert response = json_response(request, 200) + + request_2nd_page = get(conn, endpoint.(address.hash), Map.merge(response["next_page_params"], filter)) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, collections) + end + + test "return all collections", %{conn: conn, endpoint: endpoint} do + address = insert(:address) + + insert_list(51, :address_current_token_balance_with_token_id) + insert_list(51, :token_instance) + + collections_721 = + for _ <- 0..50 do + token = insert(:token, type: "ERC-721") + amount = Enum.random(16..50) + + current_token_balance = + insert(:address_current_token_balance, + address: address, + token_type: "ERC-721", + token_id: nil, + token_contract_address_hash: token.contract_address_hash, + value: amount + ) + |> Repo.preload([:token]) + + token_instances = + for _ <- 0..(amount - 1) do + ti = + insert(:token_instance, + token_contract_address_hash: token.contract_address_hash, + owner_address_hash: address.hash + ) + |> Repo.preload([:token]) + + %Instance{ti | current_token_balance: current_token_balance} + end + |> Enum.sort_by(& &1.token_id, :desc) + + {current_token_balance, token_instances} + end + |> Enum.sort_by(&elem(&1, 0).token_contract_address_hash, :desc) + + collections_1155 = + for _ <- 0..50 do + token = insert(:token, type: "ERC-1155") + amount = Enum.random(16..50) + + token_instances = + for _ <- 0..(amount - 1) do + ti = + insert(:token_instance, + token_contract_address_hash: token.contract_address_hash, + owner_address_hash: address.hash + ) + |> Repo.preload([:token]) + + current_token_balance = + insert(:address_current_token_balance, + address: address, + token_type: "ERC-1155", + token_id: ti.token_id, + token_contract_address_hash: token.contract_address_hash, + value: Enum.random(1..100_000) + ) + |> Repo.preload([:token]) + + %Instance{ti | current_token_balance: current_token_balance} + end + |> Enum.sort_by(& &1.token_id, :desc) + + {token, amount, token_instances} + end + |> Enum.sort_by(&elem(&1, 0).contract_address_hash, :desc) + + request = get(conn, endpoint.(address.hash)) + assert response = json_response(request, 200) + + request_2nd_page = get(conn, endpoint.(address.hash), response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + request_3rd_page = get(conn, endpoint.(address.hash), response_2nd_page["next_page_params"]) + assert response_3rd_page = json_response(request_3rd_page, 200) + + assert response["next_page_params"] != nil + assert response_2nd_page["next_page_params"] != nil + assert response_3rd_page["next_page_params"] == nil + + assert Enum.count(response["items"]) == 50 + assert Enum.count(response_2nd_page["items"]) == 50 + assert Enum.count(response_3rd_page["items"]) == 2 + + compare_item(Enum.at(collections_721, 50), Enum.at(response["items"], 0)) + compare_item(Enum.at(collections_721, 1), Enum.at(response["items"], 49)) + + compare_item(Enum.at(collections_721, 0), Enum.at(response_2nd_page["items"], 0)) + compare_item(Enum.at(collections_1155, 50), Enum.at(response_2nd_page["items"], 1)) + compare_item(Enum.at(collections_1155, 2), Enum.at(response_2nd_page["items"], 49)) + + compare_item(Enum.at(collections_1155, 1), Enum.at(response_3rd_page["items"], 0)) + compare_item(Enum.at(collections_1155, 0), Enum.at(response_3rd_page["items"], 1)) + end + end + defp compare_item(%Address{} = address, json) do assert Address.checksum(address.hash) == json["hash"] assert to_string(address.nonce + 1) == json["tx_count"] @@ -1989,6 +2503,99 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do assert withdrawal.index == json["index"] end + defp compare_item(%Instance{token: %Token{} = token} = instance, json) do + token_type = token.type + value = to_string(value(token.type, instance)) + id = to_string(instance.token_id) + metadata = instance.metadata + token_address_hash = Address.checksum(token.contract_address_hash) + app_url = instance.metadata["external_url"] + animation_url = instance.metadata["animation_url"] + image_url = instance.metadata["image_url"] + token_name = token.name + + assert %{ + "token_type" => ^token_type, + "value" => ^value, + "id" => ^id, + "metadata" => ^metadata, + "owner" => nil, + "token" => %{"address" => ^token_address_hash, "name" => ^token_name, "type" => ^token_type}, + "external_app_url" => ^app_url, + "animation_url" => ^animation_url, + "image_url" => ^image_url, + "is_unique" => nil + } = json + end + + defp compare_item({%CurrentTokenBalance{token: token} = ctb, token_instances}, json) do + token_type = token.type + token_address_hash = Address.checksum(token.contract_address_hash) + token_name = token.name + amount = to_string(ctb.distinct_token_instances_count || ctb.value) + + assert Enum.count(json["token_instances"]) == 15 + + token_instances + |> Enum.take(15) + |> Enum.with_index() + |> Enum.each(fn {instance, index} -> + compare_token_instance_in_collection(instance, Enum.at(json["token_instances"], index)) + end) + + assert %{ + "token" => %{"address" => ^token_address_hash, "name" => ^token_name, "type" => ^token_type}, + "amount" => ^amount + } = json + end + + defp compare_item({token, amount, token_instances}, json) do + token_type = token.type + token_address_hash = Address.checksum(token.contract_address_hash) + token_name = token.name + amount = to_string(amount) + + assert Enum.count(json["token_instances"]) == 15 + + token_instances + |> Enum.take(15) + |> Enum.with_index() + |> Enum.each(fn {instance, index} -> + compare_token_instance_in_collection(instance, Enum.at(json["token_instances"], index)) + end) + + assert %{ + "token" => %{"address" => ^token_address_hash, "name" => ^token_name, "type" => ^token_type}, + "amount" => ^amount + } = json + end + + defp compare_token_instance_in_collection(%Instance{token: %Token{} = token} = instance, json) do + token_type = token.type + value = to_string(value(token.type, instance)) + id = to_string(instance.token_id) + metadata = instance.metadata + app_url = instance.metadata["external_url"] + animation_url = instance.metadata["animation_url"] + image_url = instance.metadata["image_url"] + + assert %{ + "token_type" => ^token_type, + "value" => ^value, + "id" => ^id, + "metadata" => ^metadata, + "owner" => nil, + "token" => nil, + "external_app_url" => ^app_url, + "animation_url" => ^animation_url, + "image_url" => ^image_url, + "is_unique" => nil + } = json + end + + defp value("ERC-721", _), do: 1 + defp value(_, nft), do: nft.current_token_balance.value + defp check_paginated_response(first_page_resp, second_page_resp, list) do assert Enum.count(first_page_resp["items"]) == 50 assert first_page_resp["next_page_params"] != nil diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs index 05b7009487c4..d3506dbe5191 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs @@ -865,6 +865,87 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do check_paginated_response(response, response_2nd_page, instances) end + + test "get instances list by holder erc-721", %{conn: conn} do + token = insert(:token, type: "ERC-721") + + insert_list(51, :token_instance, token_contract_address_hash: token.contract_address_hash) + + address = insert(:address, contract_code: Enum.random([nil, "0x010101"])) + + insert_list(51, :token_instance) + + token_instances = + for _ <- 0..50 do + insert(:token_instance, + owner_address_hash: address.hash, + token_contract_address_hash: token.contract_address_hash + ) + |> Repo.preload([:token, :owner]) + end + + filter = %{"holder_address_hash" => to_string(address.hash)} + + request = get(conn, "/api/v2/tokens/#{token.contract_address_hash}/instances", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/tokens/#{token.contract_address_hash}/instances", + Map.merge(response["next_page_params"], filter) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, token_instances) + end + + test "get instances list by holder erc-1155", %{conn: conn} do + token = insert(:token, type: "ERC-1155") + + insert_list(51, :token_instance, token_contract_address_hash: token.contract_address_hash) + + address = insert(:address, contract_code: Enum.random([nil, "0x010101"])) + + insert_list(51, :token_instance) + + token_instances = + for _ <- 0..50 do + ti = + insert(:token_instance, + token_contract_address_hash: token.contract_address_hash + ) + |> Repo.preload([:token]) + + current_token_balance = + insert(:address_current_token_balance_with_token_id_and_fixed_token_type, + address: address, + token_type: "ERC-1155", + token_id: ti.token_id, + token_contract_address_hash: token.contract_address_hash, + value: Enum.random(1..2) + ) + + %Instance{ti | current_token_balance: current_token_balance, owner: address} + end + + filter = %{"holder_address_hash" => to_string(address.hash)} + + request = get(conn, "/api/v2/tokens/#{token.contract_address_hash}/instances", filter) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/tokens/#{token.contract_address_hash}/instances", + Map.merge(response["next_page_params"], filter) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, token_instances) + end end describe "/tokens/{address_hash}/instances/{token_id}" do @@ -1240,6 +1321,40 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do compare_item(Repo.preload(ctb, [{:token, :contract_address}]).token, json["token"]) end + def compare_item(%Instance{token: %Token{} = token} = instance, json) do + token_type = token.type + value = to_string(value(token.type, instance)) + id = to_string(instance.token_id) + metadata = instance.metadata + token_address_hash = Address.checksum(token.contract_address_hash) + app_url = instance.metadata["external_url"] + animation_url = instance.metadata["animation_url"] + image_url = instance.metadata["image_url"] + token_name = token.name + owner_address_hash = Address.checksum(instance.owner.hash) + is_contract = !is_nil(instance.owner.contract_code) + is_unique = value == "1" + + assert %{ + "token_type" => ^token_type, + "value" => ^value, + "id" => ^id, + "metadata" => ^metadata, + "token" => %{"address" => ^token_address_hash, "name" => ^token_name, "type" => ^token_type}, + "external_app_url" => ^app_url, + "animation_url" => ^animation_url, + "image_url" => ^image_url, + "is_unique" => ^is_unique + } = json + + if is_unique do + assert owner_address_hash == json["owner"]["hash"] + assert is_contract == json["owner"]["is_contract"] + else + assert json["owner"] == nil + end + end + def compare_item(%Instance{} = instance, json) do assert to_string(instance.token_id) == json["id"] assert Jason.decode!(Jason.encode!(instance.metadata)) == json["metadata"] @@ -1247,6 +1362,9 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do compare_item(Repo.preload(instance, [{:token, :contract_address}]).token, json["token"]) end + defp value("ERC-721", _), do: 1 + defp value(_, nft), do: nft.current_token_balance.value + # with the current implementation no transfers should come with list in totals def check_total(%Token{type: nft}, json, _token_transfer) when nft in ["ERC-721", "ERC-1155"] and is_list(json) do false diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index 955f9df84267..f47e2413959b 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -12,8 +12,7 @@ config :explorer, Explorer.Repo, ownership_timeout: :timer.minutes(7), timeout: :timer.seconds(60), queue_target: 1000, - migration_lock: nil, - log: false + migration_lock: nil # Configure API database config :explorer, Explorer.Repo.Replica1, @@ -27,8 +26,7 @@ config :explorer, Explorer.Repo.Replica1, enable_caching_implementation_data_of_proxy: true, avg_block_time_as_ttl_cached_implementation_data_of_proxy: false, fallback_ttl_cached_implementation_data_of_proxy: :timer.seconds(20), - implementation_data_fetching_timeout: :timer.seconds(20), - log: false + implementation_data_fetching_timeout: :timer.seconds(20) # Configure API database config :explorer, Explorer.Repo.Account, diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 6cc7bc80a227..4f159d2d1635 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -4136,6 +4136,10 @@ defmodule Explorer.Chain do end end + @spec join_associations(atom() | Ecto.Query.t(), map) :: Ecto.Query.t() + @doc """ + Function to preload entities associated with selected in provided query items + """ def join_associations(query, necessity_by_association) when is_map(necessity_by_association) do Enum.reduce(necessity_by_association, query, fn {association, join}, acc_query -> join_association(acc_query, association, join) diff --git a/apps/explorer/lib/explorer/chain/address/current_token_balance.ex b/apps/explorer/lib/explorer/chain/address/current_token_balance.ex index 50af3af4dd5d..fe7f3070122c 100644 --- a/apps/explorer/lib/explorer/chain/address/current_token_balance.ex +++ b/apps/explorer/lib/explorer/chain/address/current_token_balance.ex @@ -38,7 +38,10 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do updated_at: DateTime.t(), value: Decimal.t() | nil, token_id: non_neg_integer() | nil, - token_type: String.t() + token_type: String.t(), + distinct_token_instances_count: non_neg_integer(), + token_ids: list(Decimal.t()), + preloaded_token_instances: list() } schema "address_current_token_balances" do @@ -49,6 +52,9 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do field(:token_id, :decimal) field(:token_type, :string) field(:fiat_value, :decimal, virtual: true) + field(:distinct_token_instances_count, :integer, virtual: true) + field(:token_ids, {:array, :decimal}, virtual: true) + field(:preloaded_token_instances, {:array, :any}, virtual: true) # A transient field for deriving token holder count deltas during address_current_token_balances upserts field(:old_value, :decimal) diff --git a/apps/explorer/lib/explorer/chain/token/instance.ex b/apps/explorer/lib/explorer/chain/token/instance.ex index c1b5bcd5f4ff..608aec33193e 100644 --- a/apps/explorer/lib/explorer/chain/token/instance.ex +++ b/apps/explorer/lib/explorer/chain/token/instance.ex @@ -5,7 +5,9 @@ defmodule Explorer.Chain.Token.Instance do use Explorer.Schema + alias Explorer.{Chain, Helper} alias Explorer.Chain.{Address, Block, Hash, Token, TokenTransfer} + alias Explorer.Chain.Address.CurrentTokenBalance alias Explorer.Chain.Token.Instance alias Explorer.PagingOptions @@ -23,7 +25,8 @@ defmodule Explorer.Chain.Token.Instance do error: String.t(), owner_address_hash: Hash.Address.t(), owner_updated_at_block: Block.block_number(), - owner_updated_at_log_index: non_neg_integer() + owner_updated_at_log_index: non_neg_integer(), + current_token_balance: any() } @primary_key false @@ -33,6 +36,7 @@ defmodule Explorer.Chain.Token.Instance do field(:error, :string) field(:owner_updated_at_block, :integer) field(:owner_updated_at_log_index, :integer) + field(:current_token_balance, :any, virtual: true) belongs_to(:owner, Address, foreign_key: :owner_address_hash, references: :hash, type: Hash.Address) @@ -106,4 +110,307 @@ defmodule Explorer.Chain.Token.Instance do @spec token_instance_query(non_neg_integer(), Hash.Address.t()) :: Ecto.Query.t() def token_instance_query(token_id, token_contract_address), do: from(i in Instance, where: i.token_contract_address_hash == ^token_contract_address and i.token_id == ^token_id) + + @spec nft_list(binary() | Hash.Address.t(), keyword()) :: [Instance.t()] + def nft_list(address_hash, options \\ []) + + def nft_list(address_hash, options) when is_list(options) do + nft_list(address_hash, Keyword.get(options, :token_type, []), options) + end + + defp nft_list(address_hash, ["ERC-721"], options) do + erc_721_token_instances_by_owner_address_hash(address_hash, options) + end + + defp nft_list(address_hash, ["ERC-1155"], options) do + erc_1155_token_instances_by_address_hash(address_hash, options) + end + + defp nft_list(address_hash, _, options) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + + case paging_options do + %PagingOptions{key: {_contract_address_hash, _token_id, "ERC-1155"}} -> + erc_1155_token_instances_by_address_hash(address_hash, options) + + _ -> + erc_721 = erc_721_token_instances_by_owner_address_hash(address_hash, options) + + if length(erc_721) == paging_options.page_size do + erc_721 + else + erc_1155 = erc_1155_token_instances_by_address_hash(address_hash, options) + + (erc_721 ++ erc_1155) |> Enum.take(paging_options.page_size) + end + end + end + + @doc """ + In this function used fact that only ERC-721 instances has NOT NULL owner_address_hash. + """ + @spec erc_721_token_instances_by_owner_address_hash(binary() | Hash.Address.t(), keyword) :: [Instance.t()] + def erc_721_token_instances_by_owner_address_hash(address_hash, options \\ []) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + + __MODULE__ + |> where([ti], ti.owner_address_hash == ^address_hash) + |> order_by([ti], asc: ti.token_contract_address_hash, desc: ti.token_id) + |> limit(^paging_options.page_size) + |> page_erc_721_token_instances(paging_options) + |> Chain.join_associations(necessity_by_association) + |> Chain.select_repo(options).all() + end + + defp page_erc_721_token_instances(query, %PagingOptions{key: {contract_address_hash, token_id, "ERC-721"}}) do + page_token_instance(query, contract_address_hash, token_id) + end + + defp page_erc_721_token_instances(query, _), do: query + + @spec erc_1155_token_instances_by_address_hash(binary() | Hash.Address.t(), keyword) :: [Instance.t()] + def erc_1155_token_instances_by_address_hash(address_hash, options \\ []) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + + __MODULE__ + |> join(:inner, [ti], ctb in CurrentTokenBalance, + as: :ctb, + on: + ctb.token_contract_address_hash == ti.token_contract_address_hash and ctb.token_id == ti.token_id and + ctb.address_hash == ^address_hash + ) + |> where([ctb: ctb], ctb.value > 0 and ctb.token_type == "ERC-1155") + |> order_by([ti], asc: ti.token_contract_address_hash, desc: ti.token_id) + |> limit(^paging_options.page_size) + |> page_erc_1155_token_instances(paging_options) + |> select_merge([ctb: ctb], %{current_token_balance: ctb}) + |> Chain.join_associations(necessity_by_association) + |> Chain.select_repo(options).all() + end + + defp page_erc_1155_token_instances(query, %PagingOptions{key: {contract_address_hash, token_id, "ERC-1155"}}) do + page_token_instance(query, contract_address_hash, token_id) + end + + defp page_erc_1155_token_instances(query, _), do: query + + defp page_token_instance(query, contract_address_hash, token_id) do + query + |> where( + [ti], + ti.token_contract_address_hash > ^contract_address_hash or + (ti.token_contract_address_hash == ^contract_address_hash and ti.token_id < ^token_id) + ) + end + + @doc """ + Function to be used in BlockScoutWeb.Chain.next_page_params/4 + """ + @spec nft_list_next_page_params(Explorer.Chain.Token.Instance.t()) :: %{binary() => any} + def nft_list_next_page_params(%__MODULE__{ + current_token_balance: %CurrentTokenBalance{}, + token_contract_address_hash: token_contract_address_hash, + token_id: token_id + }) do + %{"token_contract_address_hash" => token_contract_address_hash, "token_id" => token_id, "token_type" => "ERC-1155"} + end + + def nft_list_next_page_params(%__MODULE__{ + token_contract_address_hash: token_contract_address_hash, + token_id: token_id + }) do + %{"token_contract_address_hash" => token_contract_address_hash, "token_id" => token_id, "token_type" => "ERC-721"} + end + + @preloaded_nfts_limit 15 + + @spec nft_collections(binary() | Hash.Address.t(), keyword) :: list + def nft_collections(address_hash, options \\ []) + + def nft_collections(address_hash, options) when is_list(options) do + nft_collections(address_hash, Keyword.get(options, :token_type, []), options) + end + + defp nft_collections(address_hash, ["ERC-721"], options) do + erc_721_collections_by_address_hash(address_hash, options) + end + + defp nft_collections(address_hash, ["ERC-1155"], options) do + erc_1155_collections_by_address_hash(address_hash, options) + end + + defp nft_collections(address_hash, _, options) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + + case paging_options do + %PagingOptions{key: {_contract_address_hash, "ERC-1155"}} -> + erc_1155_collections_by_address_hash(address_hash, options) + + _ -> + erc_721 = erc_721_collections_by_address_hash(address_hash, options) + + if length(erc_721) == paging_options.page_size do + erc_721 + else + erc_1155 = erc_1155_collections_by_address_hash(address_hash, options) + + (erc_721 ++ erc_1155) |> Enum.take(paging_options.page_size) + end + end + end + + @spec erc_721_collections_by_address_hash(binary() | Hash.Address.t(), keyword) :: [CurrentTokenBalance.t()] + def erc_721_collections_by_address_hash(address_hash, options) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + + CurrentTokenBalance + |> where([ctb], ctb.address_hash == ^address_hash and ctb.value > 0 and ctb.token_type == "ERC-721") + |> order_by([ctb], asc: ctb.token_contract_address_hash) + |> page_erc_721_nft_collections(paging_options) + |> limit(^paging_options.page_size) + |> Chain.join_associations(necessity_by_association) + |> Chain.select_repo(options).all() + |> Enum.map(&erc_721_preload_nft(&1, options)) + end + + defp page_erc_721_nft_collections(query, %PagingOptions{key: {contract_address_hash, "ERC-721"}}) do + page_nft_collections(query, contract_address_hash) + end + + defp page_erc_721_nft_collections(query, _), do: query + + @spec erc_1155_collections_by_address_hash(binary() | Hash.Address.t(), keyword) :: [ + %{ + token_contract_address_hash: Hash.Address.t(), + distinct_token_instances_count: integer(), + token_ids: [integer()] + } + ] + def erc_1155_collections_by_address_hash(address_hash, options) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + + CurrentTokenBalance + |> where([ctb], ctb.address_hash == ^address_hash and ctb.value > 0 and ctb.token_type == "ERC-1155") + |> group_by([ctb], ctb.token_contract_address_hash) + |> order_by([ctb], asc: ctb.token_contract_address_hash) + |> select([ctb], %{ + token_contract_address_hash: ctb.token_contract_address_hash, + distinct_token_instances_count: fragment("COUNT(*)"), + token_ids: fragment("array_agg(?)", ctb.token_id) + }) + |> page_erc_1155_nft_collections(paging_options) + |> limit(^paging_options.page_size) + |> Chain.select_repo(options).all() + |> Enum.map(&erc_1155_preload_nft(&1, address_hash, options)) + |> Helper.custom_preload(options, Token, :token_contract_address_hash, :contract_address_hash, :token) + end + + defp page_erc_1155_nft_collections(query, %PagingOptions{key: {contract_address_hash, "ERC-1155"}}) do + page_nft_collections(query, contract_address_hash) + end + + defp page_erc_1155_nft_collections(query, _), do: query + + defp page_nft_collections(query, token_contract_address_hash) do + query + |> where([ctb], ctb.token_contract_address_hash > ^token_contract_address_hash) + end + + defp erc_721_preload_nft( + %CurrentTokenBalance{token_contract_address_hash: token_contract_address_hash, address_hash: address_hash} = + ctb, + options + ) do + instances = + Instance + |> where( + [ti], + ti.token_contract_address_hash == ^token_contract_address_hash and ti.owner_address_hash == ^address_hash + ) + |> order_by([ti], desc: ti.token_id) + |> limit(^@preloaded_nfts_limit) + |> Chain.select_repo(options).all() + + %CurrentTokenBalance{ctb | preloaded_token_instances: instances} + end + + defp erc_1155_preload_nft( + %{token_contract_address_hash: token_contract_address_hash, token_ids: token_ids} = collection, + address_hash, + options + ) do + token_ids = token_ids |> Enum.sort(:desc) |> Enum.take(@preloaded_nfts_limit) + + instances = + Instance + |> where([ti], ti.token_contract_address_hash == ^token_contract_address_hash and ti.token_id in ^token_ids) + |> join(:inner, [ti], ctb in CurrentTokenBalance, + as: :ctb, + on: + ctb.token_contract_address_hash == ti.token_contract_address_hash and ti.token_id == ctb.token_id and + ctb.address_hash == ^address_hash + ) + |> limit(^@preloaded_nfts_limit) + |> select_merge([ctb: ctb], %{current_token_balance: ctb}) + |> Chain.select_repo(options).all() + |> Enum.sort_by(& &1.token_id, :desc) + + Map.put(collection, :preloaded_token_instances, instances) + end + + @doc """ + Function to be used in BlockScoutWeb.Chain.next_page_params/4 + """ + @spec nft_collections_next_page_params(%{:token_contract_address_hash => any, optional(any) => any}) :: %{ + binary() => any + } + def nft_collections_next_page_params(%{ + token_contract_address_hash: token_contract_address_hash, + token: %Token{type: token_type} + }) do + %{"token_contract_address_hash" => token_contract_address_hash, "token_type" => token_type} + end + + def nft_collections_next_page_params(%{ + token_contract_address_hash: token_contract_address_hash, + token_type: token_type + }) do + %{"token_contract_address_hash" => token_contract_address_hash, "token_type" => token_type} + end + + @spec token_instances_by_holder_address_hash(Token.t(), binary() | Hash.Address.t(), keyword) :: [Instance.t()] + def token_instances_by_holder_address_hash(token, holder_address_hash, options \\ []) + + def token_instances_by_holder_address_hash(%Token{type: "ERC-721"} = token, holder_address_hash, options) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + + token.contract_address_hash + |> address_to_unique_token_instances() + |> where([ti], ti.owner_address_hash == ^holder_address_hash) + |> limit(^paging_options.page_size) + |> page_token_instance(paging_options) + |> Chain.select_repo(options).all() + end + + def token_instances_by_holder_address_hash(%Token{} = token, holder_address_hash, options) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + + __MODULE__ + |> where([ti], ti.token_contract_address_hash == ^token.contract_address_hash) + |> join(:inner, [ti], ctb in CurrentTokenBalance, + as: :ctb, + on: + ctb.token_contract_address_hash == ti.token_contract_address_hash and ctb.token_id == ti.token_id and + ctb.address_hash == ^holder_address_hash + ) + |> where([ctb: ctb], ctb.value > 0) + |> order_by([ti], desc: ti.token_id) + |> limit(^paging_options.page_size) + |> page_token_instance(paging_options) + |> select_merge([ctb: ctb], %{current_token_balance: ctb}) + |> Chain.select_repo(options).all() + end end diff --git a/apps/explorer/lib/explorer/helper.ex b/apps/explorer/lib/explorer/helper.ex index 4b1dab59a392..5004b1f26702 100644 --- a/apps/explorer/lib/explorer/helper.ex +++ b/apps/explorer/lib/explorer/helper.ex @@ -4,8 +4,11 @@ defmodule Explorer.Helper do """ alias ABI.TypeDecoder + alias Explorer.Chain alias Explorer.Chain.Data + import Ecto.Query, only: [where: 3] + @spec decode_data(binary() | map(), list()) :: list() | nil def decode_data("0x", types) do for _ <- types, do: nil @@ -36,4 +39,23 @@ defmodule Explorer.Helper do _ -> nil end end + + @doc """ + Function to preload a `struct` for each element of the `list`. + You should specify a primary key for a `struct` in `references_field`, + and the list element's foreign key in `foreign_key_field`. + Results will be placed to `preload_field` + """ + @spec custom_preload(list(map()), keyword(), atom(), atom(), atom(), atom()) :: list() + def custom_preload(list, options, struct, foreign_key_field, references_field, preload_field) do + to_fetch_from_db = list |> Enum.map(& &1[foreign_key_field]) |> Enum.uniq() + + associated_elements = + struct + |> where([t], field(t, ^references_field) in ^to_fetch_from_db) + |> Chain.select_repo(options).all() + |> Enum.reduce(%{}, fn el, acc -> Map.put(acc, Map.from_struct(el)[references_field], el) end) + + Enum.map(list, fn el -> Map.put(el, preload_field, associated_elements[el[foreign_key_field]]) end) + end end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 33c9f6347796..89712c83e82e 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -888,8 +888,14 @@ defmodule Explorer.Factory do %Instance{ token_contract_address_hash: insert(:token).contract_address_hash, token_id: sequence("token_id", & &1), - metadata: %{key: "value"}, - error: nil + metadata: %{ + "key" => sequence("value"), + "image_url" => sequence("image_url"), + "animation_url" => sequence("image_url"), + "external_url" => sequence("external_url") + }, + error: nil, + owner_address_hash: insert(:address).hash } end @@ -943,6 +949,41 @@ defmodule Explorer.Factory do } end + def address_current_token_balance_with_token_id_and_fixed_token_type_factory(%{ + token_type: token_type, + address: address, + token_id: token_id, + token_contract_address_hash: token_contract_address_hash, + value: value + }) do + %CurrentTokenBalance{ + address: address, + token_contract_address_hash: token_contract_address_hash, + block_number: block_number(), + value: value, + value_fetched_at: DateTime.utc_now(), + token_id: token_id, + token_type: token_type + } + end + + def address_current_token_balance_with_token_id_and_fixed_token_type_factory(%{ + token_type: token_type, + address: address, + token_id: token_id, + token_contract_address_hash: token_contract_address_hash + }) do + %CurrentTokenBalance{ + address: address, + token_contract_address_hash: token_contract_address_hash, + block_number: block_number(), + value: Enum.random(1_000_000_000_000_000_000..10_000_000_000_000_000_000), + value_fetched_at: DateTime.utc_now(), + token_id: token_id, + token_type: token_type + } + end + def address_current_token_balance_with_token_id_and_fixed_token_type_factory(%{ token_type: token_type, address: address, From 76b9e849d47130e8c9ef8765b6de40e868301870 Mon Sep 17 00:00:00 2001 From: 0xalex88 <113263502+0xalex88@users.noreply.github.com> Date: Fri, 10 Nov 2023 11:58:22 -0800 Subject: [PATCH 024/607] Ignore depositReceiptVersion from optimism receipts --- apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex index 00ededb8d86c..f3cf67aab1d3 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex @@ -315,7 +315,7 @@ defmodule EthereumJSONRPC.Receipt do end # Optimism specific transaction receipt fields - defp entry_to_elixir({key, _}) when key in ~w(depositNonce) do + defp entry_to_elixir({key, _}) when key in ~w(depositNonce depositReceiptVersion) do :ignore end From b70f95caa59cfcd2b36802106162c922fcd44b67 Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Mon, 13 Nov 2023 15:13:02 +0300 Subject: [PATCH 025/607] Do not ignore twin contracts on /api/v2/import/smart-contracts/{address_hash} (#8813) * Ignore twin contracts on /api/v2/import/smart-contracts/{address_hash} * Changelog --- CHANGELOG.md | 1 + .../controllers/api/v2/import_controller.ex | 4 ++-- apps/explorer/lib/explorer/chain.ex | 7 ------- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 998e0c642c10..73c76b48c4a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Fixes +- [#8813](https://github.com/blockscout/blockscout/pull/8813) - Force verify twin contracts on `/api/v2/import/smart-contracts/{address_hash}` - [#8784](https://github.com/blockscout/blockscout/pull/8784) - Fix Indexer.Transform.Addresses for non-Suave setup - [#8770](https://github.com/blockscout/blockscout/pull/8770) - Fix for eth_getbalance API v1 endpoint when requesting latest tag - [#8765](https://github.com/blockscout/blockscout/pull/8765) - Fix for tvl update in market history when row already exists diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/import_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/import_controller.ex index 7c6b59c3bf99..76aa988edfc7 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/import_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/import_controller.ex @@ -54,7 +54,7 @@ defmodule BlockScoutWeb.API.V2.ImportController do @doc """ Function to handle request at: - `/api/v2/smart-contracts/{address_hash_param}` + `/api/v2/import/smart-contracts/{address_hash_param}` Needed to try to import unverified smart contracts via eth-bytecode-db (`/api/v2/bytecodes/sources:search` method). Protected by `x-api-key` header. @@ -73,7 +73,7 @@ defmodule BlockScoutWeb.API.V2.ImportController do {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, {:not_found, {:ok, address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)}, {:already_verified, smart_contract} when is_nil(smart_contract) <- - {:already_verified, Chain.address_hash_to_smart_contract(address_hash, @api_true)} do + {:already_verified, Chain.address_hash_to_smart_contract_without_twin(address_hash, @api_true)} do creation_tx_input = contract_creation_input(address.hash) with {:ok, %{"sourceType" => type} = source} <- diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 4f159d2d1635..9a1f114eae5c 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -3912,13 +3912,6 @@ defmodule Explorer.Chain do end end - @spec address_hash_to_smart_contract(Hash.Address.t()) :: SmartContract.t() | nil - def address_hash_to_one_smart_contract(hash) do - SmartContract - |> where([sc], sc.address_hash == ^hash) - |> Repo.one() - end - @spec address_hash_to_smart_contract_without_twin(Hash.Address.t(), [api?]) :: SmartContract.t() | nil def address_hash_to_smart_contract_without_twin(address_hash, options) do query = From 6377c978e1667f47dd4c80a390978cb3d2db5b3b Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Thu, 28 Sep 2023 12:34:25 +0300 Subject: [PATCH 026/607] Add CoinBalanceDailyUpdater --- CHANGELOG.md | 1 + apps/indexer/lib/indexer/block/fetcher.ex | 7 -- .../lib/indexer/block/realtime/fetcher.ex | 10 ++- .../lib/indexer/fetcher/block_reward.ex | 5 +- .../fetcher/coin_balance_daily_updater.ex | 68 +++++++++++++++++++ apps/indexer/lib/indexer/supervisor.ex | 2 + 6 files changed, 78 insertions(+), 15 deletions(-) create mode 100644 apps/indexer/lib/indexer/fetcher/coin_balance_daily_updater.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index 73c76b48c4a0..39a7a802eb00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - [#8750](https://github.com/blockscout/blockscout/pull/8750) - Support new eth-bytecode-db request metadata fields - [#8634](https://github.com/blockscout/blockscout/pull/8634) - API v2: NFT for address - [#8609](https://github.com/blockscout/blockscout/pull/8609) - Change logs format to JSON; Add endpoint url to the block_scout_web logging +- [#8558](https://github.com/blockscout/blockscout/pull/8558) - Add CoinBalanceDailyUpdater ### Fixes diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 5b5eaf0b8902..7ceebb1009b5 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -173,12 +173,6 @@ defmodule Indexer.Block.Fetcher do withdrawals: withdrawals_params } |> AddressCoinBalances.params_set(), - coin_balances_params_daily_set = - %{ - coin_balances_params: coin_balances_params_set, - blocks: blocks - } - |> AddressCoinBalancesDaily.params_set(), beneficiaries_with_gas_payment = beneficiaries_with_gas_payment(blocks, beneficiary_params_set, transactions_with_receipts), address_token_balances = AddressTokenBalances.params_set(%{token_transfers_params: token_transfers}), @@ -188,7 +182,6 @@ defmodule Indexer.Block.Fetcher do basic_import_options = %{ addresses: %{params: addresses}, address_coin_balances: %{params: coin_balances_params_set}, - address_coin_balances_daily: %{params: coin_balances_params_daily_set}, address_token_balances: %{params: address_token_balances}, address_current_token_balances: %{ params: address_token_balances |> MapSet.to_list() |> TokenBalances.to_address_current_token_balances() diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index b78688b85e18..cb43857e1d0b 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -33,7 +33,7 @@ defmodule Indexer.Block.Realtime.Fetcher do alias Explorer.Utility.MissingRangesManipulator alias Indexer.{Block, Tracer} alias Indexer.Block.Realtime.TaskSupervisor - alias Indexer.Fetcher.CoinBalance + alias Indexer.Fetcher.{CoinBalance, CoinBalanceDailyUpdater} alias Indexer.Fetcher.PolygonEdge.{DepositExecute, Withdrawal} alias Indexer.Prometheus alias Indexer.Transform.Addresses @@ -195,7 +195,6 @@ defmodule Indexer.Block.Realtime.Fetcher do block_fetcher, %{ address_coin_balances: %{params: address_coin_balances_params}, - address_coin_balances_daily: %{params: address_coin_balances_daily_params}, address_hash_to_fetched_balance_block_number: address_hash_to_block_number, addresses: %{params: addresses_params}, block_rewards: block_rewards @@ -212,8 +211,7 @@ defmodule Indexer.Block.Realtime.Fetcher do balances(block_fetcher, %{ address_hash_to_block_number: address_hash_to_block_number, addresses_params: addresses_params, - balances_params: address_coin_balances_params, - balances_daily_params: address_coin_balances_daily_params + balances_params: address_coin_balances_params })}, {block_reward_errors, chain_import_block_rewards} = Map.pop(block_rewards, :errors), chain_import_options = @@ -222,8 +220,8 @@ defmodule Indexer.Block.Realtime.Fetcher do |> put_in([:addresses, :params], balances_addresses_params) |> put_in([:blocks, :params, Access.all(), :consensus], true) |> put_in([:block_rewards], chain_import_block_rewards) - |> put_in([Access.key(:address_coin_balances, %{}), :params], balances_params) - |> put_in([Access.key(:address_coin_balances_daily, %{}), :params], balances_daily_params), + |> put_in([Access.key(:address_coin_balances, %{}), :params], balances_params), + CoinBalanceDailyUpdater.add_daily_balances_params(balances_daily_params), {:import, {:ok, imported} = ok} <- {:import, Chain.import(chain_import_options)} do async_import_remaining_block_data( imported, diff --git a/apps/indexer/lib/indexer/fetcher/block_reward.ex b/apps/indexer/lib/indexer/fetcher/block_reward.ex index f6825a5ffce6..2558b9510fee 100644 --- a/apps/indexer/lib/indexer/fetcher/block_reward.ex +++ b/apps/indexer/lib/indexer/fetcher/block_reward.ex @@ -20,7 +20,7 @@ defmodule Indexer.Fetcher.BlockReward do alias Explorer.Chain.Cache.Accounts alias Indexer.{BufferedTask, Tracer} alias Indexer.Fetcher.BlockReward.Supervisor, as: BlockRewardSupervisor - alias Indexer.Fetcher.CoinBalance + alias Indexer.Fetcher.{CoinBalance, CoinBalanceDailyUpdater} alias Indexer.Transform.{AddressCoinBalances, AddressCoinBalancesDaily, Addresses} @behaviour BufferedTask @@ -292,10 +292,11 @@ defmodule Indexer.Fetcher.BlockReward do address_coin_balances_daily_params_set = AddressCoinBalancesDaily.params_set(address_coin_balances_params_with_block_timestamp_set) + CoinBalanceDailyUpdater.add_daily_balances_params(address_coin_balances_daily_params_set) + Chain.import(%{ addresses: %{params: addresses_params}, address_coin_balances: %{params: address_coin_balances_params_set}, - address_coin_balances_daily: %{params: address_coin_balances_daily_params_set}, block_rewards: %{params: block_rewards_params} }) end diff --git a/apps/indexer/lib/indexer/fetcher/coin_balance_daily_updater.ex b/apps/indexer/lib/indexer/fetcher/coin_balance_daily_updater.ex new file mode 100644 index 000000000000..101178630611 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/coin_balance_daily_updater.ex @@ -0,0 +1,68 @@ +defmodule Indexer.Fetcher.CoinBalanceDailyUpdater do + @moduledoc """ + Accumulates and periodically updates daily coin balances + """ + + use GenServer + + alias Explorer.Chain + alias Explorer.Counters.AverageBlockTime + alias Timex.Duration + + @default_update_interval :timer.seconds(10) + + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + @impl true + def init(_) do + schedule_next_update() + + {:ok, %{}} + end + + def add_daily_balances_params(daily_balances_params) do + GenServer.cast(__MODULE__, {:add_daily_balances_params, daily_balances_params}) + end + + @impl true + def handle_cast({:add_daily_balances_params, daily_balances_params}, state) do + {:noreply, Enum.reduce(daily_balances_params, state, &put_new_param/2)} + end + + defp put_new_param(%{day: day, address_hash: address_hash, value: value} = param, acc) do + Map.update(acc, {address_hash, day}, param, fn %{value: old_value} = old_param -> + if is_nil(old_value) or value > old_value, do: param, else: old_param + end) + end + + @impl true + def handle_info(:update, state) when state == %{} do + schedule_next_update() + + {:noreply, %{}} + end + + def handle_info(:update, state) do + Chain.import(%{address_coin_balances_daily: %{params: Map.values(state)}}) + + schedule_next_update() + + {:noreply, %{}} + end + + def handle_info(_, state) do + {:noreply, state} + end + + defp schedule_next_update do + update_interval = + case AverageBlockTime.average_block_time() do + {:error, :disabled} -> @default_update_interval + block_time -> round(Duration.to_milliseconds(block_time)) + end + + Process.send_after(self(), :update, update_interval) + end +end diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index c7afebbbcee0..832818cba5a7 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -21,6 +21,7 @@ defmodule Indexer.Supervisor do alias Indexer.Fetcher.{ BlockReward, CoinBalance, + CoinBalanceDailyUpdater, ContractCode, EmptyBlocksSanitizer, InternalTransaction, @@ -142,6 +143,7 @@ defmodule Indexer.Supervisor do {EmptyBlocksSanitizer.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments]]}, {PendingTransactionsSanitizer, [[json_rpc_named_arguments: json_rpc_named_arguments]]}, {TokenTotalSupplyUpdater, [[]]}, + {CoinBalanceDailyUpdater, [[]]}, # Temporary workers {UncatalogedTokenTransfers.Supervisor, [[]]}, From 5209cdfc8444bfa12bb837784e65ba529916a485 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 18:03:55 +0000 Subject: [PATCH 027/607] Bump benchee from 1.1.0 to 1.2.0 Bumps [benchee](https://github.com/bencheeorg/benchee) from 1.1.0 to 1.2.0. - [Release notes](https://github.com/bencheeorg/benchee/releases) - [Changelog](https://github.com/bencheeorg/benchee/blob/main/CHANGELOG.md) - [Commits](https://github.com/bencheeorg/benchee/compare/1.1.0...1.2.0) --- updated-dependencies: - dependency-name: benchee dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/explorer/mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index e976d63ce455..6d903ff853b6 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -61,7 +61,7 @@ defmodule Explorer.Mixfile do {:mime, "~> 2.0"}, {:bcrypt_elixir, "~> 3.0"}, # benchmark optimizations - {:benchee, "~> 1.1.0", only: :test}, + {:benchee, "~> 1.2.0", only: :test}, # CSV output for benchee {:benchee_csv, "~> 1.0.0", only: :test}, {:bypass, "~> 2.1", only: :test}, diff --git a/mix.lock b/mix.lock index 605ac6bec0ec..d75ce01e294f 100644 --- a/mix.lock +++ b/mix.lock @@ -6,7 +6,7 @@ "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"}, "bamboo": {:hex, :bamboo, "2.3.0", "d2392a2cabe91edf488553d3c70638b532e8db7b76b84b0a39e3dfe492ffd6fc", [:mix], [{:hackney, ">= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.4 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "dd0037e68e108fd04d0e8773921512c940e35d981e097b5793543e3b2f9cd3f6"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.1.0", "0b110a9a6c619b19a7f73fa3004aa11d6e719a67e672d1633dc36b6b2290a0f7", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2ad2acb5a8bc049e8d5aa267802631912bb80d5f4110a178ae7999e69dca1bf7"}, - "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, + "benchee": {:hex, :benchee, "1.2.0", "afd2f0caec06ce3a70d9c91c514c0b58114636db9d83c2dc6bfd416656618353", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "ee729e53217898b8fd30aaad3cce61973dab61574ae6f48229fe7ff42d5e4457"}, "benchee_csv": {:hex, :benchee_csv, "1.0.0", "0b3b9223290bfcb8003552705bec9bcf1a89b4a83b70bd686e45295c264f3d16", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:csv, "~> 2.0", [hex: :csv, repo: "hexpm", optional: false]}], "hexpm", "cdefb804c021dcf7a99199492026584be9b5a21d6644ac0d01c81c5d97c520d5"}, "briefly": {:git, "https://github.com/CargoSense/briefly.git", "51dfe7fbe0f897ea2a921d9af120762392aca6a1", []}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, From 3a681ae1422a9526fc37f38cc1954694fc7dd646 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 18:04:34 +0000 Subject: [PATCH 028/607] Bump httpoison from 2.1.0 to 2.2.0 Bumps [httpoison](https://github.com/edgurgel/httpoison) from 2.1.0 to 2.2.0. - [Release notes](https://github.com/edgurgel/httpoison/releases) - [Commits](https://github.com/edgurgel/httpoison/compare/v2.1.0...v2.2.0) --- updated-dependencies: - dependency-name: httpoison dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 605ac6bec0ec..2cd2faaea95d 100644 --- a/mix.lock +++ b/mix.lock @@ -68,7 +68,7 @@ "hammer": {:hex, :hammer, "6.1.0", "f263e3c3e9946bd410ea0336b2abe0cb6260af4afb3a221e1027540706e76c55", [:make, :mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b47e415a562a6d072392deabcd58090d8a41182cf9044cdd6b0d0faaaf68ba57"}, "hammer_backend_redis": {:hex, :hammer_backend_redis, "6.1.2", "eb296bb4924928e24135308b2afc189201fd09411c870c6bbadea444a49b2f2c", [:mix], [{:hammer, "~> 6.0", [hex: :hammer, repo: "hexpm", optional: false]}, {:redix, "~> 1.1", [hex: :redix, repo: "hexpm", optional: false]}], "hexpm", "217ea066278910543a5e9b577d5bf2425419446b94fe76bdd9f255f39feec9fa"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, - "httpoison": {:hex, :httpoison, "2.1.0", "655fd9a7b0b95ee3e9a3b535cf7ac8e08ef5229bab187fa86ac4208b122d934b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "fc455cb4306b43827def4f57299b2d5ac8ac331cb23f517e734a4b78210a160c"}, + "httpoison": {:hex, :httpoison, "2.2.0", "839298929243b872b3f53c3693fa369ac3dbe03102cefd0a126194738bf4bb0e", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a49a9337c2b671464948a00cff6a882d271c1c8e3d25a6ca14d0532cbd23f65a"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, From 97bd61577e716d5b918635e8209368183105db2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 18:51:12 +0000 Subject: [PATCH 029/607] Bump luxon from 3.4.3 to 3.4.4 in /apps/block_scout_web/assets Bumps [luxon](https://github.com/moment/luxon) from 3.4.3 to 3.4.4. - [Changelog](https://github.com/moment/luxon/blob/master/CHANGELOG.md) - [Commits](https://github.com/moment/luxon/compare/3.4.3...3.4.4) --- updated-dependencies: - dependency-name: luxon dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 182812353e9b..95c1a67ce74d 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -44,7 +44,7 @@ "lodash.omit": "^4.5.0", "lodash.rangeright": "^4.2.0", "lodash.reduce": "^4.6.0", - "luxon": "^3.4.3", + "luxon": "^3.4.4", "malihu-custom-scrollbar-plugin": "3.1.5", "mixpanel-browser": "^2.47.0", "moment": "^2.29.4", @@ -12522,9 +12522,9 @@ "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=" }, "node_modules/luxon": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.3.tgz", - "integrity": "sha512-tFWBiv3h7z+T/tDaoxA8rqTxy1CHV6gHS//QdaH4pulbq/JuBSGgQspQQqcgnwdAx6pNI7cmvz5Sv/addzHmUg==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", "engines": { "node": ">=12" } @@ -27270,9 +27270,9 @@ "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=" }, "luxon": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.3.tgz", - "integrity": "sha512-tFWBiv3h7z+T/tDaoxA8rqTxy1CHV6gHS//QdaH4pulbq/JuBSGgQspQQqcgnwdAx6pNI7cmvz5Sv/addzHmUg==" + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==" }, "make-dir": { "version": "4.0.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 34655e1073c2..bc19465225e8 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -56,7 +56,7 @@ "lodash.omit": "^4.5.0", "lodash.rangeright": "^4.2.0", "lodash.reduce": "^4.6.0", - "luxon": "^3.4.3", + "luxon": "^3.4.4", "malihu-custom-scrollbar-plugin": "3.1.5", "mixpanel-browser": "^2.47.0", "moment": "^2.29.4", From 874d72b9ba9f91a0e0c5025a4c9cac13e8966c30 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 18:51:47 +0000 Subject: [PATCH 030/607] Bump @babel/core from 7.23.2 to 7.23.3 in /apps/block_scout_web/assets Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.23.2 to 7.23.3. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.3/packages/babel-core) --- updated-dependencies: - dependency-name: "@babel/core" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 110 +++++++++--------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 182812353e9b..a62f5f49d19f 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -71,7 +71,7 @@ "xss": "^1.0.14" }, "devDependencies": { - "@babel/core": "^7.23.2", + "@babel/core": "^7.23.3", "@babel/preset-env": "^7.23.2", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", @@ -249,20 +249,20 @@ } }, "node_modules/@babel/core": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", - "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", + "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/generator": "^7.23.3", "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-module-transforms": "^7.23.3", "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.0", + "@babel/parser": "^7.23.3", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0", + "@babel/traverse": "^7.23.3", + "@babel/types": "^7.23.3", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -283,11 +283,11 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", + "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", "dependencies": { - "@babel/types": "^7.23.0", + "@babel/types": "^7.23.3", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -458,9 +458,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", - "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", @@ -628,9 +628,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", + "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1943,18 +1943,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", + "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", "dependencies": { "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/generator": "^7.23.3", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", + "@babel/parser": "^7.23.3", + "@babel/types": "^7.23.3", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1963,9 +1963,9 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", + "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", "dependencies": { "@babel/helper-string-parser": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.20", @@ -17971,20 +17971,20 @@ "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==" }, "@babel/core": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", - "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", + "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/generator": "^7.23.3", "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-module-transforms": "^7.23.3", "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.0", + "@babel/parser": "^7.23.3", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0", + "@babel/traverse": "^7.23.3", + "@babel/types": "^7.23.3", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -18000,11 +18000,11 @@ } }, "@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", + "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", "requires": { - "@babel/types": "^7.23.0", + "@babel/types": "^7.23.3", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -18135,9 +18135,9 @@ } }, "@babel/helper-module-transforms": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", - "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "requires": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", @@ -18254,9 +18254,9 @@ } }, "@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", + "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.22.15", @@ -19142,26 +19142,26 @@ } }, "@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", + "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", "requires": { "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/generator": "^7.23.3", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", + "@babel/parser": "^7.23.3", + "@babel/types": "^7.23.3", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", + "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", "requires": { "@babel/helper-string-parser": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.20", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 34655e1073c2..afb9419c1447 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -83,7 +83,7 @@ "xss": "^1.0.14" }, "devDependencies": { - "@babel/core": "^7.23.2", + "@babel/core": "^7.23.3", "@babel/preset-env": "^7.23.2", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", From 480adc964ac87988fb1e2110fa83d1541df01b11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 07:31:18 +0000 Subject: [PATCH 031/607] Bump @babel/preset-env in /apps/block_scout_web/assets Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.23.2 to 7.23.3. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.3/packages/babel-preset-env) --- updated-dependencies: - dependency-name: "@babel/preset-env" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 1060 +++++++++-------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 544 insertions(+), 518 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index c347760f4802..0fc330c2a4a0 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -72,7 +72,7 @@ }, "devDependencies": { "@babel/core": "^7.23.3", - "@babel/preset-env": "^7.23.2", + "@babel/preset-env": "^7.23.3", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^11.0.0", @@ -241,9 +241,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", - "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", + "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==", "engines": { "node": ">=6.9.0" } @@ -308,12 +308,12 @@ } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz", - "integrity": "sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -348,15 +348,15 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.11.tgz", - "integrity": "sha512-y1grdYL4WzmUDBRGK0pDbIoFd7UZKoDurDzWEoNMYoj1EL+foGRQNyPWDcC+YyegN5y1DUsFFmzjGijB3nSVAQ==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz", + "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-function-name": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.15", "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-replace-supers": "^7.22.9", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", @@ -371,14 +371,14 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz", - "integrity": "sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "regexpu-core": "^5.3.1", - "semver": "^6.3.0" + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -435,12 +435,12 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", - "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -513,13 +513,13 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz", - "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", "@babel/helper-optimise-call-expression": "^7.22.5" }, "engines": { @@ -639,9 +639,9 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz", - "integrity": "sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", + "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -654,14 +654,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz", - "integrity": "sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", + "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.15" + "@babel/plugin-transform-optional-chaining": "^7.23.3" }, "engines": { "node": ">=6.9.0" @@ -670,6 +670,22 @@ "@babel/core": "^7.13.0" } }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", + "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", @@ -758,9 +774,9 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", - "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", + "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -773,9 +789,9 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", - "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", + "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -960,9 +976,9 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", - "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", + "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -975,9 +991,9 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.2.tgz", - "integrity": "sha512-BBYVGxbDVHfoeXbOwcagAkOQAm9NxoTdMGfTqghu1GrvadSaw6iW3Je6IcL5PNOw8VwjxqBECXy50/iCQSY/lQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.3.tgz", + "integrity": "sha512-59GsVNavGxAXCDDbakWSMJhajASb4kBCqDjqJsv+p5nKdbz7istmZ3HrX3L2LuiI80+zsOADCvooqQH3qGCucQ==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -993,14 +1009,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", - "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5" + "@babel/helper-remap-async-to-generator": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -1010,9 +1026,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", - "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", + "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1025,9 +1041,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz", - "integrity": "sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.3.tgz", + "integrity": "sha512-QPZxHrThbQia7UdvfpaRRlq/J9ciz1J4go0k+lPBXbgaNeY7IQrBj/9ceWjvMMI07/ZBzHl/F0R/2K0qH7jCVw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1040,12 +1056,12 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", - "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", + "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -1056,12 +1072,12 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz", - "integrity": "sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.3.tgz", + "integrity": "sha512-PENDVxdr7ZxKPyi5Ffc0LjXdnJyrJxyqF5T5YjlVg4a0VFfQHW0r8iAtRiDXkfHlu1wwcvdtnndGYIeJLSuRMQ==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.11", + "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, @@ -1073,18 +1089,18 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz", - "integrity": "sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.3.tgz", + "integrity": "sha512-FGEQmugvAEu2QtgtU0uTASXevfLMFfBeVCIIdcQhn/uBQsMTjBajdnAtanQlOcuihWh10PZ7+HWvc7NtBwP74w==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-replace-supers": "^7.22.20", "@babel/helper-split-export-declaration": "^7.22.6", "globals": "^11.1.0" }, @@ -1096,13 +1112,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", - "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", + "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.5" + "@babel/template": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -1112,9 +1128,9 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz", - "integrity": "sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", + "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1127,12 +1143,12 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", - "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", + "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-create-regexp-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -1143,9 +1159,9 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", - "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", + "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1158,9 +1174,9 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz", - "integrity": "sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.3.tgz", + "integrity": "sha512-vTG+cTGxPFou12Rj7ll+eD5yWeNl5/8xvQvF08y5Gv3v4mZQoyFf8/n9zg4q5vvCWt5jmgymfzMAldO7orBn7A==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1174,12 +1190,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", - "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", + "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", "dev": true, "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -1190,9 +1206,9 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz", - "integrity": "sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.3.tgz", + "integrity": "sha512-yCLhW34wpJWRdTxxWtFZASJisihrfyMOTOQexhVzA78jlU+dH7Dw+zQgcPepQ5F3C6bAIiblZZ+qBggJdHiBAg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1206,9 +1222,9 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz", - "integrity": "sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.3.tgz", + "integrity": "sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1221,13 +1237,13 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", - "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", + "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -1238,9 +1254,9 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz", - "integrity": "sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.3.tgz", + "integrity": "sha512-H9Ej2OiISIZowZHaBwF0tsJOih1PftXJtE8EWqlEIwpc7LMTGq0rPOrywKLQ4nefzx8/HMR0D3JGXoMHYvhi0A==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1254,9 +1270,9 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", - "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", + "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1269,9 +1285,9 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz", - "integrity": "sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.3.tgz", + "integrity": "sha512-+pD5ZbxofyOygEp+zZAfujY2ShNCXRpDRIPOiBmTO693hhyOEteZgl876Xs9SAHPQpcV0vz8LvA/T+w8AzyX8A==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1285,9 +1301,9 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", - "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", + "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1300,12 +1316,12 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz", - "integrity": "sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", + "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -1316,12 +1332,12 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz", - "integrity": "sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", + "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-simple-access": "^7.22.5" }, @@ -1333,13 +1349,13 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz", - "integrity": "sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", + "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", "dev": true, "dependencies": { "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.20" }, @@ -1351,12 +1367,12 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", - "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", + "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -1383,9 +1399,9 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", - "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", + "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1398,9 +1414,9 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz", - "integrity": "sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.3.tgz", + "integrity": "sha512-xzg24Lnld4DYIdysyf07zJ1P+iIfJpxtVFOzX4g+bsJ3Ng5Le7rXx9KwqKzuyaUeRnt+I1EICwQITqc0E2PmpA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1414,9 +1430,9 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz", - "integrity": "sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.3.tgz", + "integrity": "sha512-s9GO7fIBi/BLsZ0v3Rftr6Oe4t0ctJ8h4CCXfPoEJwmvAPMyNrfkOOJzm6b9PX9YXcCJWWQd/sBF/N26eBiMVw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1430,16 +1446,16 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz", - "integrity": "sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.3.tgz", + "integrity": "sha512-VxHt0ANkDmu8TANdE9Kc0rndo/ccsmfe2Cx2y5sI4hu3AukHQ5wAu4cM7j3ba8B9548ijVyclBU+nuDQftZsog==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.9", + "@babel/compat-data": "^7.23.3", "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.22.15" + "@babel/plugin-transform-parameters": "^7.23.3" }, "engines": { "node": ">=6.9.0" @@ -1449,13 +1465,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", - "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", + "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5" + "@babel/helper-replace-supers": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -1465,9 +1481,9 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz", - "integrity": "sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.3.tgz", + "integrity": "sha512-LxYSb0iLjUamfm7f1D7GpiS4j0UAC8AOiehnsGAP8BEsIX8EOi3qV6bbctw8M7ZvLtcoZfZX5Z7rN9PlWk0m5A==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1481,9 +1497,9 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz", - "integrity": "sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.3.tgz", + "integrity": "sha512-zvL8vIfIUgMccIAK1lxjvNv572JHFJIKb4MWBz5OGdBQA0fB0Xluix5rmOby48exiJc987neOmP/m9Fnpkz3Tg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1498,9 +1514,9 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz", - "integrity": "sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", + "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1513,12 +1529,12 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", - "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", + "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -1529,13 +1545,13 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz", - "integrity": "sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.3.tgz", + "integrity": "sha512-a5m2oLNFyje2e/rGKjVfAELTVI5mbA0FeZpBnkOWWV7eSmKQ+T/XW0Vf+29ScLzSxX+rnsarvU0oie/4m6hkxA==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.11", + "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, @@ -1547,9 +1563,9 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", - "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", + "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1562,9 +1578,9 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz", - "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", + "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1578,9 +1594,9 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", - "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", + "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1624,9 +1640,9 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", - "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", + "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1639,9 +1655,9 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", - "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", + "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1655,9 +1671,9 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", - "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", + "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1670,9 +1686,9 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", - "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", + "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1685,9 +1701,9 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", - "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", + "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1700,9 +1716,9 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz", - "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", + "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1715,12 +1731,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", - "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", + "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-create-regexp-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -1731,12 +1747,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", - "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", + "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-create-regexp-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -1747,12 +1763,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", - "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", + "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-create-regexp-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -1763,25 +1779,26 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.2.tgz", - "integrity": "sha512-BW3gsuDD+rvHL2VO2SjAUNTBe5YrjsTiDyqamPDWY723na3/yPQ65X5oQkFVJZ0o50/2d+svm1rkPoJeR1KxVQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.3.tgz", + "integrity": "sha512-ovzGc2uuyNfNAs/jyjIGxS8arOHS5FENZaNn4rtE7UdKMMkqHCvboHfcuhWLZNX5cB44QfcGNWjaevxMzzMf+Q==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.2", + "@babel/compat-data": "^7.23.3", "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.15", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.15", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.22.5", - "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", @@ -1793,56 +1810,55 @@ "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.22.5", - "@babel/plugin-transform-async-generator-functions": "^7.23.2", - "@babel/plugin-transform-async-to-generator": "^7.22.5", - "@babel/plugin-transform-block-scoped-functions": "^7.22.5", - "@babel/plugin-transform-block-scoping": "^7.23.0", - "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-class-static-block": "^7.22.11", - "@babel/plugin-transform-classes": "^7.22.15", - "@babel/plugin-transform-computed-properties": "^7.22.5", - "@babel/plugin-transform-destructuring": "^7.23.0", - "@babel/plugin-transform-dotall-regex": "^7.22.5", - "@babel/plugin-transform-duplicate-keys": "^7.22.5", - "@babel/plugin-transform-dynamic-import": "^7.22.11", - "@babel/plugin-transform-exponentiation-operator": "^7.22.5", - "@babel/plugin-transform-export-namespace-from": "^7.22.11", - "@babel/plugin-transform-for-of": "^7.22.15", - "@babel/plugin-transform-function-name": "^7.22.5", - "@babel/plugin-transform-json-strings": "^7.22.11", - "@babel/plugin-transform-literals": "^7.22.5", - "@babel/plugin-transform-logical-assignment-operators": "^7.22.11", - "@babel/plugin-transform-member-expression-literals": "^7.22.5", - "@babel/plugin-transform-modules-amd": "^7.23.0", - "@babel/plugin-transform-modules-commonjs": "^7.23.0", - "@babel/plugin-transform-modules-systemjs": "^7.23.0", - "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.3", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.3", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.3", + "@babel/plugin-transform-classes": "^7.23.3", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.3", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.3", + "@babel/plugin-transform-for-of": "^7.23.3", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.3", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.3", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.3", + "@babel/plugin-transform-modules-umd": "^7.23.3", "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.22.5", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.11", - "@babel/plugin-transform-numeric-separator": "^7.22.11", - "@babel/plugin-transform-object-rest-spread": "^7.22.15", - "@babel/plugin-transform-object-super": "^7.22.5", - "@babel/plugin-transform-optional-catch-binding": "^7.22.11", - "@babel/plugin-transform-optional-chaining": "^7.23.0", - "@babel/plugin-transform-parameters": "^7.22.15", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.11", - "@babel/plugin-transform-property-literals": "^7.22.5", - "@babel/plugin-transform-regenerator": "^7.22.10", - "@babel/plugin-transform-reserved-words": "^7.22.5", - "@babel/plugin-transform-shorthand-properties": "^7.22.5", - "@babel/plugin-transform-spread": "^7.22.5", - "@babel/plugin-transform-sticky-regex": "^7.22.5", - "@babel/plugin-transform-template-literals": "^7.22.5", - "@babel/plugin-transform-typeof-symbol": "^7.22.5", - "@babel/plugin-transform-unicode-escapes": "^7.22.10", - "@babel/plugin-transform-unicode-property-regex": "^7.22.5", - "@babel/plugin-transform-unicode-regex": "^7.22.5", - "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.3", + "@babel/plugin-transform-numeric-separator": "^7.23.3", + "@babel/plugin-transform-object-rest-spread": "^7.23.3", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.3", + "@babel/plugin-transform-optional-chaining": "^7.23.3", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.3", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", "@babel/preset-modules": "0.1.6-no-external-plugins", - "@babel/types": "^7.23.0", "babel-plugin-polyfill-corejs2": "^0.4.6", "babel-plugin-polyfill-corejs3": "^0.8.5", "babel-plugin-polyfill-regenerator": "^0.5.3", @@ -17966,9 +17982,9 @@ } }, "@babel/compat-data": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", - "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==" + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", + "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==" }, "@babel/core": { "version": "7.23.3", @@ -18019,12 +18035,12 @@ } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz", - "integrity": "sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", "dev": true, "requires": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.22.15" } }, "@babel/helper-compilation-targets": { @@ -18055,15 +18071,15 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.11.tgz", - "integrity": "sha512-y1grdYL4WzmUDBRGK0pDbIoFd7UZKoDurDzWEoNMYoj1EL+foGRQNyPWDcC+YyegN5y1DUsFFmzjGijB3nSVAQ==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz", + "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-function-name": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.15", "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-replace-supers": "^7.22.9", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", @@ -18072,14 +18088,14 @@ } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz", - "integrity": "sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.22.5", "regexpu-core": "^5.3.1", - "semver": "^6.3.0" + "semver": "^6.3.1" } }, "@babel/helper-define-polyfill-provider": { @@ -18118,12 +18134,12 @@ } }, "@babel/helper-member-expression-to-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", - "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", "dev": true, "requires": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.23.0" } }, "@babel/helper-module-imports": { @@ -18172,13 +18188,13 @@ } }, "@babel/helper-replace-supers": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz", - "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", "dev": true, "requires": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", "@babel/helper-optimise-call-expression": "^7.22.5" } }, @@ -18259,23 +18275,33 @@ "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz", - "integrity": "sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", + "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz", - "integrity": "sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", + "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.15" + "@babel/plugin-transform-optional-chaining": "^7.23.3" + } + }, + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", + "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-proposal-private-property-in-object": { @@ -18340,18 +18366,18 @@ } }, "@babel/plugin-syntax-import-assertions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", - "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", + "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-syntax-import-attributes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", - "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", + "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" @@ -18476,18 +18502,18 @@ } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", - "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", + "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-async-generator-functions": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.2.tgz", - "integrity": "sha512-BBYVGxbDVHfoeXbOwcagAkOQAm9NxoTdMGfTqghu1GrvadSaw6iW3Je6IcL5PNOw8VwjxqBECXy50/iCQSY/lQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.3.tgz", + "integrity": "sha512-59GsVNavGxAXCDDbakWSMJhajASb4kBCqDjqJsv+p5nKdbz7istmZ3HrX3L2LuiI80+zsOADCvooqQH3qGCucQ==", "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.22.20", @@ -18497,114 +18523,114 @@ } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", - "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5" + "@babel/helper-remap-async-to-generator": "^7.22.20" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", - "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", + "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz", - "integrity": "sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.3.tgz", + "integrity": "sha512-QPZxHrThbQia7UdvfpaRRlq/J9ciz1J4go0k+lPBXbgaNeY7IQrBj/9ceWjvMMI07/ZBzHl/F0R/2K0qH7jCVw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-class-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", - "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", + "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-class-static-block": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz", - "integrity": "sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.3.tgz", + "integrity": "sha512-PENDVxdr7ZxKPyi5Ffc0LjXdnJyrJxyqF5T5YjlVg4a0VFfQHW0r8iAtRiDXkfHlu1wwcvdtnndGYIeJLSuRMQ==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.11", + "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-class-static-block": "^7.14.5" } }, "@babel/plugin-transform-classes": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz", - "integrity": "sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.3.tgz", + "integrity": "sha512-FGEQmugvAEu2QtgtU0uTASXevfLMFfBeVCIIdcQhn/uBQsMTjBajdnAtanQlOcuihWh10PZ7+HWvc7NtBwP74w==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-replace-supers": "^7.22.20", "@babel/helper-split-export-declaration": "^7.22.6", "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", - "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", + "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.5" + "@babel/template": "^7.22.15" } }, "@babel/plugin-transform-destructuring": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz", - "integrity": "sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", + "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", - "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", + "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-create-regexp-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", - "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", + "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-dynamic-import": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz", - "integrity": "sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.3.tgz", + "integrity": "sha512-vTG+cTGxPFou12Rj7ll+eD5yWeNl5/8xvQvF08y5Gv3v4mZQoyFf8/n9zg4q5vvCWt5jmgymfzMAldO7orBn7A==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18612,19 +18638,19 @@ } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", - "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", + "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-export-namespace-from": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz", - "integrity": "sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.3.tgz", + "integrity": "sha512-yCLhW34wpJWRdTxxWtFZASJisihrfyMOTOQexhVzA78jlU+dH7Dw+zQgcPepQ5F3C6bAIiblZZ+qBggJdHiBAg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18632,29 +18658,29 @@ } }, "@babel/plugin-transform-for-of": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz", - "integrity": "sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.3.tgz", + "integrity": "sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", - "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", + "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", "dev": true, "requires": { - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-json-strings": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz", - "integrity": "sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.3.tgz", + "integrity": "sha512-H9Ej2OiISIZowZHaBwF0tsJOih1PftXJtE8EWqlEIwpc7LMTGq0rPOrywKLQ4nefzx8/HMR0D3JGXoMHYvhi0A==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18662,18 +18688,18 @@ } }, "@babel/plugin-transform-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", - "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", + "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-logical-assignment-operators": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz", - "integrity": "sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.3.tgz", + "integrity": "sha512-+pD5ZbxofyOygEp+zZAfujY2ShNCXRpDRIPOiBmTO693hhyOEteZgl876Xs9SAHPQpcV0vz8LvA/T+w8AzyX8A==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18681,54 +18707,54 @@ } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", - "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", + "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz", - "integrity": "sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", + "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz", - "integrity": "sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", + "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-simple-access": "^7.22.5" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz", - "integrity": "sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", + "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", "dev": true, "requires": { "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.20" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", - "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", + "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", "@babel/helper-plugin-utils": "^7.22.5" } }, @@ -18743,18 +18769,18 @@ } }, "@babel/plugin-transform-new-target": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", - "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", + "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz", - "integrity": "sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.3.tgz", + "integrity": "sha512-xzg24Lnld4DYIdysyf07zJ1P+iIfJpxtVFOzX4g+bsJ3Ng5Le7rXx9KwqKzuyaUeRnt+I1EICwQITqc0E2PmpA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18762,9 +18788,9 @@ } }, "@babel/plugin-transform-numeric-separator": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz", - "integrity": "sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.3.tgz", + "integrity": "sha512-s9GO7fIBi/BLsZ0v3Rftr6Oe4t0ctJ8h4CCXfPoEJwmvAPMyNrfkOOJzm6b9PX9YXcCJWWQd/sBF/N26eBiMVw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18772,32 +18798,32 @@ } }, "@babel/plugin-transform-object-rest-spread": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz", - "integrity": "sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.3.tgz", + "integrity": "sha512-VxHt0ANkDmu8TANdE9Kc0rndo/ccsmfe2Cx2y5sI4hu3AukHQ5wAu4cM7j3ba8B9548ijVyclBU+nuDQftZsog==", "dev": true, "requires": { - "@babel/compat-data": "^7.22.9", + "@babel/compat-data": "^7.23.3", "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.22.15" + "@babel/plugin-transform-parameters": "^7.23.3" } }, "@babel/plugin-transform-object-super": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", - "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", + "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5" + "@babel/helper-replace-supers": "^7.22.20" } }, "@babel/plugin-transform-optional-catch-binding": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz", - "integrity": "sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.3.tgz", + "integrity": "sha512-LxYSb0iLjUamfm7f1D7GpiS4j0UAC8AOiehnsGAP8BEsIX8EOi3qV6bbctw8M7ZvLtcoZfZX5Z7rN9PlWk0m5A==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18805,9 +18831,9 @@ } }, "@babel/plugin-transform-optional-chaining": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz", - "integrity": "sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.3.tgz", + "integrity": "sha512-zvL8vIfIUgMccIAK1lxjvNv572JHFJIKb4MWBz5OGdBQA0fB0Xluix5rmOby48exiJc987neOmP/m9Fnpkz3Tg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18816,49 +18842,49 @@ } }, "@babel/plugin-transform-parameters": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz", - "integrity": "sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", + "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-private-methods": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", - "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", + "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-private-property-in-object": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz", - "integrity": "sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.3.tgz", + "integrity": "sha512-a5m2oLNFyje2e/rGKjVfAELTVI5mbA0FeZpBnkOWWV7eSmKQ+T/XW0Vf+29ScLzSxX+rnsarvU0oie/4m6hkxA==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.11", + "@babel/helper-create-class-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" } }, "@babel/plugin-transform-property-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", - "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", + "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-regenerator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz", - "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", + "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18866,9 +18892,9 @@ } }, "@babel/plugin-transform-reserved-words": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", - "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", + "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" @@ -18899,18 +18925,18 @@ } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", - "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", + "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", - "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", + "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18918,91 +18944,92 @@ } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", - "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", + "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-template-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", - "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", + "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", - "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", + "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz", - "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", + "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-unicode-property-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", - "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", + "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-create-regexp-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", - "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", + "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-create-regexp-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-transform-unicode-sets-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", - "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", + "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-create-regexp-features-plugin": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/preset-env": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.2.tgz", - "integrity": "sha512-BW3gsuDD+rvHL2VO2SjAUNTBe5YrjsTiDyqamPDWY723na3/yPQ65X5oQkFVJZ0o50/2d+svm1rkPoJeR1KxVQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.3.tgz", + "integrity": "sha512-ovzGc2uuyNfNAs/jyjIGxS8arOHS5FENZaNn4rtE7UdKMMkqHCvboHfcuhWLZNX5cB44QfcGNWjaevxMzzMf+Q==", "dev": true, "requires": { - "@babel/compat-data": "^7.23.2", + "@babel/compat-data": "^7.23.3", "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.15", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.15", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.22.5", - "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", @@ -19014,56 +19041,55 @@ "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.22.5", - "@babel/plugin-transform-async-generator-functions": "^7.23.2", - "@babel/plugin-transform-async-to-generator": "^7.22.5", - "@babel/plugin-transform-block-scoped-functions": "^7.22.5", - "@babel/plugin-transform-block-scoping": "^7.23.0", - "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-class-static-block": "^7.22.11", - "@babel/plugin-transform-classes": "^7.22.15", - "@babel/plugin-transform-computed-properties": "^7.22.5", - "@babel/plugin-transform-destructuring": "^7.23.0", - "@babel/plugin-transform-dotall-regex": "^7.22.5", - "@babel/plugin-transform-duplicate-keys": "^7.22.5", - "@babel/plugin-transform-dynamic-import": "^7.22.11", - "@babel/plugin-transform-exponentiation-operator": "^7.22.5", - "@babel/plugin-transform-export-namespace-from": "^7.22.11", - "@babel/plugin-transform-for-of": "^7.22.15", - "@babel/plugin-transform-function-name": "^7.22.5", - "@babel/plugin-transform-json-strings": "^7.22.11", - "@babel/plugin-transform-literals": "^7.22.5", - "@babel/plugin-transform-logical-assignment-operators": "^7.22.11", - "@babel/plugin-transform-member-expression-literals": "^7.22.5", - "@babel/plugin-transform-modules-amd": "^7.23.0", - "@babel/plugin-transform-modules-commonjs": "^7.23.0", - "@babel/plugin-transform-modules-systemjs": "^7.23.0", - "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.3", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.3", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.3", + "@babel/plugin-transform-classes": "^7.23.3", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.3", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.3", + "@babel/plugin-transform-for-of": "^7.23.3", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.3", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.3", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.3", + "@babel/plugin-transform-modules-umd": "^7.23.3", "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.22.5", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.11", - "@babel/plugin-transform-numeric-separator": "^7.22.11", - "@babel/plugin-transform-object-rest-spread": "^7.22.15", - "@babel/plugin-transform-object-super": "^7.22.5", - "@babel/plugin-transform-optional-catch-binding": "^7.22.11", - "@babel/plugin-transform-optional-chaining": "^7.23.0", - "@babel/plugin-transform-parameters": "^7.22.15", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.11", - "@babel/plugin-transform-property-literals": "^7.22.5", - "@babel/plugin-transform-regenerator": "^7.22.10", - "@babel/plugin-transform-reserved-words": "^7.22.5", - "@babel/plugin-transform-shorthand-properties": "^7.22.5", - "@babel/plugin-transform-spread": "^7.22.5", - "@babel/plugin-transform-sticky-regex": "^7.22.5", - "@babel/plugin-transform-template-literals": "^7.22.5", - "@babel/plugin-transform-typeof-symbol": "^7.22.5", - "@babel/plugin-transform-unicode-escapes": "^7.22.10", - "@babel/plugin-transform-unicode-property-regex": "^7.22.5", - "@babel/plugin-transform-unicode-regex": "^7.22.5", - "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.3", + "@babel/plugin-transform-numeric-separator": "^7.23.3", + "@babel/plugin-transform-object-rest-spread": "^7.23.3", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.3", + "@babel/plugin-transform-optional-chaining": "^7.23.3", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.3", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", "@babel/preset-modules": "0.1.6-no-external-plugins", - "@babel/types": "^7.23.0", "babel-plugin-polyfill-corejs2": "^0.4.6", "babel-plugin-polyfill-corejs3": "^0.8.5", "babel-plugin-polyfill-regenerator": "^0.5.3", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 751f438fcfdd..b6d4b193cf2c 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -84,7 +84,7 @@ }, "devDependencies": { "@babel/core": "^7.23.3", - "@babel/preset-env": "^7.23.2", + "@babel/preset-env": "^7.23.3", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^11.0.0", From 3accea963190f5e8893a77020dce18284c1cca0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 07:33:06 +0000 Subject: [PATCH 032/607] Bump solc from 0.8.22 to 0.8.23 in /apps/explorer Bumps [solc](https://github.com/ethereum/solc-js) from 0.8.22 to 0.8.23. - [Commits](https://github.com/ethereum/solc-js/compare/v0.8.22...v0.8.23) --- updated-dependencies: - dependency-name: solc dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/explorer/package-lock.json | 35 ++++++++++++++++++++++++++------- apps/explorer/package.json | 2 +- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/apps/explorer/package-lock.json b/apps/explorer/package-lock.json index 9a78d3dd8dde..86af32e1175b 100644 --- a/apps/explorer/package-lock.json +++ b/apps/explorer/package-lock.json @@ -7,7 +7,7 @@ "name": "blockscout", "license": "GPL-3.0", "dependencies": { - "solc": "0.8.22" + "solc": "0.8.23" }, "engines": { "node": "18.x", @@ -59,6 +59,20 @@ "node": ">= 0.10.0" } }, + "node_modules/n": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/n/-/n-9.2.0.tgz", + "integrity": "sha512-R8mFN2OWwNVc+r1f9fDzcT34DnDwUIHskrpTesZ6SdluaXBBnRtTu5tlfaSPloBi1Z/eGJoPO9nhyawWPad5UQ==", + "os": [ + "!win32" + ], + "bin": { + "n": "bin/n" + }, + "engines": { + "node": "*" + } + }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -76,15 +90,16 @@ } }, "node_modules/solc": { - "version": "0.8.22", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.22.tgz", - "integrity": "sha512-bA2tMZXx93R8L5LUH7TlB/f+QhkVyxrrY6LmgJnFFZlRknrhYVlBK1e3uHIdKybwoFabOFSzeaZjPeL/GIpFGQ==", + "version": "0.8.23", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.23.tgz", + "integrity": "sha512-uqe69kFWfJc3cKdxj+Eg9CdW1CP3PLZDPeyJStQVWL8Q9jjjKD0VuRAKBFR8mrWiq5A7gJqERxJFYJsklrVsfA==", "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", + "n": "^9.2.0", "semver": "^5.5.0", "tmp": "0.0.33" }, @@ -133,6 +148,11 @@ "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=" }, + "n": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/n/-/n-9.2.0.tgz", + "integrity": "sha512-R8mFN2OWwNVc+r1f9fDzcT34DnDwUIHskrpTesZ6SdluaXBBnRtTu5tlfaSPloBi1Z/eGJoPO9nhyawWPad5UQ==" + }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -144,15 +164,16 @@ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" }, "solc": { - "version": "0.8.22", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.22.tgz", - "integrity": "sha512-bA2tMZXx93R8L5LUH7TlB/f+QhkVyxrrY6LmgJnFFZlRknrhYVlBK1e3uHIdKybwoFabOFSzeaZjPeL/GIpFGQ==", + "version": "0.8.23", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.23.tgz", + "integrity": "sha512-uqe69kFWfJc3cKdxj+Eg9CdW1CP3PLZDPeyJStQVWL8Q9jjjKD0VuRAKBFR8mrWiq5A7gJqERxJFYJsklrVsfA==", "requires": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", + "n": "^9.2.0", "semver": "^5.5.0", "tmp": "0.0.33" } diff --git a/apps/explorer/package.json b/apps/explorer/package.json index 366fdd199b1e..01333da43558 100644 --- a/apps/explorer/package.json +++ b/apps/explorer/package.json @@ -13,6 +13,6 @@ }, "scripts": {}, "dependencies": { - "solc": "0.8.22" + "solc": "0.8.23" } } From 668d5a288953866f0e38491e5592c6fd04cd2c0a Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Mon, 13 Nov 2023 13:31:16 +0300 Subject: [PATCH 033/607] Do not preload smart contract info if address has no contract_code --- .../controllers/api/v2/address_controller.ex | 26 +++++++++++++------ .../lib/block_scout_web/views/address_view.ex | 6 ++--- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex index 1ca5db5dcdc2..43418c135de7 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -16,7 +16,8 @@ defmodule BlockScoutWeb.API.V2.AddressController do alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.API.V2.{BlockView, TransactionView, WithdrawalView} - alias Explorer.{Chain, Market} + alias Explorer.{Chain, Market, Repo} + alias Explorer.Chain.Address alias Explorer.Chain.Address.Counters alias Explorer.Chain.Token.Instance alias Indexer.Fetcher.{CoinBalanceOnDemand, TokenBalanceOnDemand} @@ -46,15 +47,18 @@ defmodule BlockScoutWeb.API.V2.AddressController do @address_options [ necessity_by_association: %{ - :contracts_creation_internal_transaction => :optional, :names => :optional, - :smart_contract => :optional, - :token => :optional, - :contracts_creation_transaction => :optional + :token => :optional }, api?: true ] + @contract_address_preloads [ + :smart_contract, + :contracts_creation_internal_transaction, + :contracts_creation_transaction + ] + @nft_necessity_by_association [ necessity_by_association: %{ :token => :optional @@ -66,12 +70,13 @@ defmodule BlockScoutWeb.API.V2.AddressController do action_fallback(BlockScoutWeb.API.V2.FallbackController) def address(conn, %{"address_hash_param" => address_hash_string} = params) do - with {:ok, _address_hash, address} <- validate_address(address_hash_string, params, @address_options) do - CoinBalanceOnDemand.trigger_fetch(address) + with {:ok, _address_hash, address} <- validate_address(address_hash_string, params, @address_options), + fully_preloaded_address <- maybe_preload_smart_contract_associations(address) do + CoinBalanceOnDemand.trigger_fetch(fully_preloaded_address) conn |> put_status(200) - |> render(:address, %{address: address}) + |> render(:address, %{address: fully_preloaded_address}) end end @@ -477,4 +482,9 @@ defmodule BlockScoutWeb.API.V2.AddressController do {:ok, address_hash, address} end end + + defp maybe_preload_smart_contract_associations(%Address{contract_code: nil} = address), do: address + + defp maybe_preload_smart_contract_associations(%Address{contract_code: _} = address), + do: Repo.replica().preload(address, @contract_address_preloads) end diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex index 41ebf80976d6..3098dfc09692 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex @@ -258,7 +258,7 @@ defmodule BlockScoutWeb.AddressView do Enum.any?(address.smart_contract.abi || [], &is_read_function?(&1)) end - def smart_contract_with_read_only_functions?(%Address{smart_contract: nil}), do: false + def smart_contract_with_read_only_functions?(%Address{smart_contract: _}), do: false def is_read_function?(function), do: Helper.queriable_method?(function) || Helper.read_with_wallet_method?(function) @@ -268,7 +268,7 @@ defmodule BlockScoutWeb.AddressView do SmartContract.proxy_contract?(smart_contract, options) end - def smart_contract_is_proxy?(%Address{smart_contract: nil}, _), do: false + def smart_contract_is_proxy?(%Address{smart_contract: _}, _), do: false def smart_contract_with_write_functions?(%Address{smart_contract: %SmartContract{}} = address) do !contract_interaction_disabled?() && @@ -278,7 +278,7 @@ defmodule BlockScoutWeb.AddressView do ) end - def smart_contract_with_write_functions?(%Address{smart_contract: nil}), do: false + def smart_contract_with_write_functions?(%Address{smart_contract: _}), do: false def has_decompiled_code?(address) do address.has_decompiled_code? || From aee725b151385e971c5e152f323cd96e900d9de8 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Mon, 13 Nov 2023 13:33:30 +0300 Subject: [PATCH 034/607] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39a7a802eb00..72e480d8180e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### Fixes +- [#8814](https://github.com/blockscout/blockscout/pull/8814) - Improve performance for EOA addresses in `/api/v2/addresses/{address_hash}` - [#8813](https://github.com/blockscout/blockscout/pull/8813) - Force verify twin contracts on `/api/v2/import/smart-contracts/{address_hash}` - [#8784](https://github.com/blockscout/blockscout/pull/8784) - Fix Indexer.Transform.Addresses for non-Suave setup - [#8770](https://github.com/blockscout/blockscout/pull/8770) - Fix for eth_getbalance API v1 endpoint when requesting latest tag From 98210e4bf133c9b95ce3e178163cccaa88f4ef6d Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Tue, 14 Nov 2023 11:26:39 +0300 Subject: [PATCH 035/607] Fix after review; Add test --- .../controllers/api/v2/address_controller.ex | 10 +-- .../api/v2/address_controller_test.exs | 62 ++++++++++++++++++- apps/explorer/lib/explorer/chain/address.ex | 11 ++++ 3 files changed, 75 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex index 43418c135de7..6d3c767a49e7 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -16,7 +16,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.API.V2.{BlockView, TransactionView, WithdrawalView} - alias Explorer.{Chain, Market, Repo} + alias Explorer.{Chain, Market} alias Explorer.Chain.Address alias Explorer.Chain.Address.Counters alias Explorer.Chain.Token.Instance @@ -71,7 +71,8 @@ defmodule BlockScoutWeb.API.V2.AddressController do def address(conn, %{"address_hash_param" => address_hash_string} = params) do with {:ok, _address_hash, address} <- validate_address(address_hash_string, params, @address_options), - fully_preloaded_address <- maybe_preload_smart_contract_associations(address) do + fully_preloaded_address <- + Address.maybe_preload_smart_contract_associations(address, @contract_address_preloads, @api_true) do CoinBalanceOnDemand.trigger_fetch(fully_preloaded_address) conn @@ -482,9 +483,4 @@ defmodule BlockScoutWeb.API.V2.AddressController do {:ok, address_hash, address} end end - - defp maybe_preload_smart_contract_associations(%Address{contract_code: nil} = address), do: address - - defp maybe_preload_smart_contract_associations(%Address{contract_code: _} = address), - do: Repo.replica().preload(address, @contract_address_preloads) end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index ebe923dff6d5..411803430cd0 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -1,5 +1,6 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do use BlockScoutWeb.ConnCase + use EthereumJSONRPC.Case, async: false alias BlockScoutWeb.Models.UserFromAuth alias Explorer.{Chain, Repo} @@ -22,6 +23,9 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do alias Explorer.Chain.Address.CurrentTokenBalance import Explorer.Chain, only: [hash_to_lower_case_string: 1] + import Mox + + setup :set_mox_global describe "/addresses/{address_hash}" do test "get 404 on non existing address", %{conn: conn} do @@ -44,7 +48,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do correct_response = %{ "hash" => Address.checksum(address.hash), "is_contract" => false, - "is_verified" => false, + "is_verified" => nil, "name" => nil, "private_tags" => [], "public_tags" => [], @@ -79,6 +83,47 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do assert ^correct_response = json_response(request, 200) end + test "get contract info", %{conn: conn} do + smart_contract = insert(:smart_contract) + + tx = + insert(:transaction, + to_address_hash: nil, + to_address: nil, + created_contract_address_hash: smart_contract.address_hash, + created_contract_address: smart_contract.address + ) + + insert(:address_name, + address: smart_contract.address, + primary: true, + name: smart_contract.name, + address_hash: smart_contract.address_hash + ) + + name = smart_contract.name + from = Address.checksum(tx.from_address_hash) + tx_hash = to_string(tx.hash) + address_hash = Address.checksum(smart_contract.address_hash) + + get_eip1967_implementation_non_zero_address() + + request = get(conn, "/api/v2/addresses/#{Address.checksum(smart_contract.address_hash)}") + + assert %{ + "hash" => ^address_hash, + "is_contract" => true, + "is_verified" => true, + "name" => ^name, + "private_tags" => [], + "public_tags" => [], + "watchlist_names" => [], + "creator_address_hash" => ^from, + "creation_tx_hash" => ^tx_hash, + "implementation_address" => "0x0000000000000000000000000000000000000001" + } = json_response(request, 200) + end + test "get watchlist id", %{conn: conn} do auth = build(:auth) address = insert(:address) @@ -2622,4 +2667,19 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do end def check_total(_, _, _), do: true + + def get_eip1967_implementation_non_zero_address do + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000001"} + end) + end end diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index 0b38b0801448..5803f3573227 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -8,6 +8,7 @@ defmodule Explorer.Chain.Address do use Explorer.Schema alias Ecto.Changeset + alias Explorer.Chain alias Explorer.Chain.{ Address, @@ -252,6 +253,16 @@ defmodule Explorer.Chain.Address do end) end + @doc """ + Preloads provided contracts associations if address has contract_code which is not nil + """ + @spec maybe_preload_smart_contract_associations(Address.t(), list, list) :: Address.t() + def maybe_preload_smart_contract_associations(%Address{contract_code: nil} = address, _associations, _options), + do: address + + def maybe_preload_smart_contract_associations(%Address{contract_code: _} = address, associations, options), + do: Chain.select_repo(options).preload(address, associations) + @doc """ Counts all the addresses where the `fetched_coin_balance` is > 0. """ From 8be0d92f5f981716f948f699d952d1381b4ef011 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 14 Nov 2023 14:28:04 +0300 Subject: [PATCH 036/607] Log more details in regards 413 error --- CHANGELOG.md | 1 + apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72e480d8180e..d75afb139956 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ ### Chore +- [#8832](https://github.com/blockscout/blockscout/pull/8832) - Log more details in regards 413 error - [#8802](https://github.com/blockscout/blockscout/pull/8802) - Enable API v2 by default - [#8728](https://github.com/blockscout/blockscout/pull/8728) - Remove repos_list (default value for ecto repos) from Explorer.ReleaseTasks diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex index e7abd430391b..f7b607e77e8c 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex @@ -94,10 +94,21 @@ defmodule EthereumJSONRPC.HTTP do case length(batch) do # it can't be made any smaller 1 -> + old_truncate = Application.get_env(:logger, :truncate) + Logger.configure(truncate: :infinity) + Logger.error(fn -> - "413 Request Entity Too Large returned from single request batch. Cannot shrink batch further." + [ + "413 Request Entity Too Large returned from single request batch. Cannot shrink batch further. ", + "The actual batched request was ", + "#{inspect(batch)}. ", + "The actual response of the method was ", + "#{inspect(response)}." + ] end) + Logger.configure(truncate: old_truncate) + {:error, response} batch_size -> From d63a94056897785d048eded4974b536f7c75e96c Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Tue, 14 Nov 2023 22:17:38 +0600 Subject: [PATCH 037/607] Safe token update --- CHANGELOG.md | 1 + .../lib/explorer/chain/import/runner/tokens.ex | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72e480d8180e..779767492b14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### Fixes +- [#8836](https://github.com/blockscout/blockscout/pull/8836) - Safe token update - [#8814](https://github.com/blockscout/blockscout/pull/8814) - Improve performance for EOA addresses in `/api/v2/addresses/{address_hash}` - [#8813](https://github.com/blockscout/blockscout/pull/8813) - Force verify twin contracts on `/api/v2/import/smart-contracts/{address_hash}` - [#8784](https://github.com/blockscout/blockscout/pull/8784) - Fix Indexer.Transform.Addresses for non-Suave setup diff --git a/apps/explorer/lib/explorer/chain/import/runner/tokens.ex b/apps/explorer/lib/explorer/chain/import/runner/tokens.ex index d60afaee2d7a..791ee9daa2f5 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/tokens.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/tokens.ex @@ -141,13 +141,13 @@ defmodule Explorer.Chain.Import.Runner.Tokens do token in Token, update: [ set: [ - name: fragment("EXCLUDED.name"), - symbol: fragment("EXCLUDED.symbol"), - total_supply: fragment("EXCLUDED.total_supply"), - decimals: fragment("EXCLUDED.decimals"), - type: fragment("EXCLUDED.type"), - cataloged: fragment("EXCLUDED.cataloged"), - skip_metadata: fragment("EXCLUDED.skip_metadata"), + name: fragment("COALESCE(EXCLUDED.name, ?)", token.name), + symbol: fragment("COALESCE(EXCLUDED.symbol, ?)", token.symbol), + total_supply: fragment("COALESCE(EXCLUDED.total_supply, ?)", token.total_supply), + decimals: fragment("COALESCE(EXCLUDED.decimals, ?)", token.decimals), + type: fragment("COALESCE(EXCLUDED.type, ?)", token.type), + cataloged: fragment("COALESCE(EXCLUDED.cataloged, ?)", token.cataloged), + skip_metadata: fragment("COALESCE(EXCLUDED.skip_metadata, ?)", token.skip_metadata), # `holder_count` is not updated as a pre-existing token means the `holder_count` is already initialized OR # need to be migrated with `priv/repo/migrations/scripts/update_new_tokens_holder_count_in_batches.sql.exs` # Don't update `contract_address_hash` as it is the primary key and used for the conflict target From 95b3101483961437492f0193e83ffa9c0ebbea0e Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 15 Nov 2023 15:21:15 +0300 Subject: [PATCH 038/607] Smart-contract proxy detection refactoring --- .dialyzer-ignore | 2 +- .github/workflows/config.yml | 26 +- CHANGELOG.md | 1 + ...ddress_contract_verification_controller.ex | 5 +- ...ification_via_flattened_code_controller.ex | 3 +- ...ntract_verification_via_json_controller.ex | 3 +- ...ication_via_multi_part_files_controller.ex | 3 +- ...tion_via_standard_json_input_controller.ex | 3 +- ..._contract_verification_vyper_controller.ex | 3 +- .../api/rpc/contract_controller.ex | 6 +- .../v1/verified_smart_contract_controller.ex | 3 +- .../controllers/api/v2/import_controller.ex | 6 +- .../api/v2/smart_contract_controller.ex | 4 +- .../api/v2/verification_controller.ex | 3 +- .../controllers/smart_contract_controller.ex | 2 +- .../controllers/tokens/contract_controller.ex | 4 +- .../templates/address_contract/index.html.eex | 14 +- .../new.html.eex | 2 +- .../new.html.eex | 2 +- .../new.html.eex | 4 +- .../new.html.eex | 2 +- .../smart_contract/_functions.html.eex | 22 +- .../templates/tokens/overview/_tabs.html.eex | 4 +- ...ct_verification_via_flattened_code_view.ex | 1 - ..._verification_via_multi_part_files_view.ex | 1 - ...rification_via_standard_json_input_view.ex | 1 - ...ddress_contract_verification_vyper_view.ex | 1 - .../views/address_contract_view.ex | 2 + .../lib/block_scout_web/views/address_view.ex | 9 +- .../views/api/rpc/contract_view.ex | 3 +- .../views/api/v2/smart_contract_view.ex | 7 +- .../views/smart_contract_view.ex | 4 +- .../views/tokens/overview_view.ex | 9 +- apps/block_scout_web/priv/gettext/default.pot | 48 +- .../priv/gettext/en/LC_MESSAGES/default.po | 48 +- .../address_read_contract_controller_test.exs | 16 +- .../address_read_proxy_controller_test.exs | 16 +- ...address_write_contract_controller_test.exs | 16 +- .../address_write_proxy_controller_test.exs | 16 +- .../api/rpc/contract_controller_test.exs | 20 +- .../api/v2/smart_contract_controller_test.exs | 40 + .../api/v2/transaction_controller_test.exs | 4 +- .../smart_contract_controller_test.exs | 54 +- .../tokens/read_contract_controller_test.exs | 16 +- ...saction_token_transfer_controller_test.exs | 12 + apps/explorer/lib/explorer/chain.ex | 655 +----------- .../lib/explorer/chain/address/name.ex | 44 +- .../lib/explorer/chain/block/reward.ex | 8 +- apps/explorer/lib/explorer/chain/hash/full.ex | 2 +- apps/explorer/lib/explorer/chain/log.ex | 3 +- .../lib/explorer/chain/smart_contract.ex | 963 +++++++++++------- .../explorer/chain/smart_contract/proxy.ex | 230 +++++ .../chain/smart_contract/proxy/basic.ex | 44 + .../chain/smart_contract/proxy/eip_1167.ex | 54 + .../chain/smart_contract/proxy/eip_1822.ex | 32 + .../chain/smart_contract/proxy/eip_1967.ex | 101 ++ .../chain/smart_contract/proxy/eip_930.ex | 31 + .../chain/smart_contract/proxy/master_copy.ex | 43 + .../smart_contract_additional_sources.ex | 21 + .../lib/explorer/chain/transaction.ex | 3 +- .../lib/explorer/etherscan/contracts.ex | 10 +- .../lib/explorer/smart_contract/reader.ex | 12 +- .../smart_contract/solidity/publisher.ex | 7 +- .../smart_contract/vyper/publisher.ex | 3 +- .../lib/explorer/smart_contract/writer.ex | 3 +- .../explorer/test/explorer/chain/log_test.exs | 16 +- .../chain/smart_contract/proxy_test.exs | 384 +++++++ .../explorer/chain/smart_contract_test.exs | 660 ++++++++++-- .../test/explorer/chain/transaction_test.exs | 19 +- apps/explorer/test/explorer/chain_test.exs | 643 ------------ apps/indexer/lib/indexer/block/fetcher.ex | 1 - cspell.json | 2 + 72 files changed, 2547 insertions(+), 1918 deletions(-) create mode 100644 apps/explorer/lib/explorer/chain/smart_contract/proxy.ex create mode 100644 apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex create mode 100644 apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex create mode 100644 apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1822.ex create mode 100644 apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex create mode 100644 apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_930.ex create mode 100644 apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex create mode 100644 apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs diff --git a/.dialyzer-ignore b/.dialyzer-ignore index c36e55ab7ae5..7ee843f9190e 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -25,4 +25,4 @@ 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:166 +lib/explorer/chain/transaction.ex:167 diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 0b7346dc7b11..b286c178f161 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -55,7 +55,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps- @@ -113,7 +113,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -137,7 +137,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -160,7 +160,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -200,7 +200,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -226,7 +226,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -255,7 +255,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -303,7 +303,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -349,7 +349,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -406,7 +406,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -460,7 +460,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -525,7 +525,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -589,7 +589,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" diff --git a/CHANGELOG.md b/CHANGELOG.md index 84ecb8bef440..bdd5dbb4f823 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ ### Chore - [#8832](https://github.com/blockscout/blockscout/pull/8832) - Log more details in regards 413 error +- [#8807](https://github.com/blockscout/blockscout/pull/8807) - Smart-contract proxy detection refactoring - [#8802](https://github.com/blockscout/blockscout/pull/8802) - Enable API v2 by default - [#8728](https://github.com/blockscout/blockscout/pull/8728) - Remove repos_list (default value for ecto repos) from Explorer.ReleaseTasks diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex index 5a00556fac09..60f4720aef37 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex @@ -2,7 +2,6 @@ defmodule BlockScoutWeb.AddressContractVerificationController do use BlockScoutWeb, :controller alias BlockScoutWeb.Controller - alias Explorer.Chain alias Explorer.Chain.Events.Publisher, as: EventsPublisher alias Explorer.Chain.SmartContract alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler} @@ -12,7 +11,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do alias Explorer.ThirdPartyIntegrations.Sourcify def new(conn, %{"address_id" => address_hash_string}) do - if Chain.smart_contract_fully_verified?(address_hash_string) do + if SmartContract.verified_with_full_match?(address_hash_string) do address_contract_path = conn |> address_contract_path(:index, address_hash_string) @@ -122,7 +121,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do json_file = PublishHelper.get_one_json(files_array) if json_file do - if Chain.smart_contract_fully_verified?(address_hash_string) do + if SmartContract.verified_with_full_match?(address_hash_string) do EventsPublisher.broadcast( PublishHelper.prepare_verification_error( "This contract already verified in Blockscout.", diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex index fab2192cfae0..72cd3aeb3bca 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex @@ -2,12 +2,11 @@ defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeController do use BlockScoutWeb, :controller alias BlockScoutWeb.Controller - alias Explorer.Chain alias Explorer.Chain.SmartContract alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler, Solidity.PublisherWorker} def new(conn, %{"address_id" => address_hash_string}) do - if Chain.smart_contract_fully_verified?(address_hash_string) do + if SmartContract.verified_with_full_match?(address_hash_string) do address_contract_path = conn |> address_contract_path(:index, address_hash_string) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_json_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_json_controller.ex index 87a75ac7cb7e..b41ae1517fdc 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_json_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_json_controller.ex @@ -2,7 +2,6 @@ defmodule BlockScoutWeb.AddressContractVerificationViaJsonController do use BlockScoutWeb, :controller alias BlockScoutWeb.Controller - alias Explorer.Chain alias Explorer.Chain.SmartContract alias Explorer.SmartContract.Solidity.PublishHelper alias Explorer.ThirdPartyIntegrations.Sourcify @@ -13,7 +12,7 @@ defmodule BlockScoutWeb.AddressContractVerificationViaJsonController do |> address_contract_path(:index, address_hash_string) |> Controller.full_path() - if Chain.smart_contract_fully_verified?(address_hash_string) do + if SmartContract.verified_with_full_match?(address_hash_string) do redirect(conn, to: address_contract_path) else case Sourcify.check_by_address(address_hash_string) do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_multi_part_files_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_multi_part_files_controller.ex index 7d0f819e96e2..78f6ea6b99c4 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_multi_part_files_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_multi_part_files_controller.ex @@ -2,12 +2,11 @@ defmodule BlockScoutWeb.AddressContractVerificationViaMultiPartFilesController d use BlockScoutWeb, :controller alias BlockScoutWeb.Controller - alias Explorer.Chain alias Explorer.Chain.SmartContract alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler} def new(conn, %{"address_id" => address_hash_string}) do - if Chain.smart_contract_fully_verified?(address_hash_string) do + if SmartContract.verified_with_full_match?(address_hash_string) do address_contract_path = conn |> address_contract_path(:index, address_hash_string) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_standard_json_input_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_standard_json_input_controller.ex index 1938269f79ef..37447acd8195 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_standard_json_input_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_standard_json_input_controller.ex @@ -2,12 +2,11 @@ defmodule BlockScoutWeb.AddressContractVerificationViaStandardJsonInputControlle use BlockScoutWeb, :controller alias BlockScoutWeb.Controller - alias Explorer.Chain alias Explorer.Chain.SmartContract alias Explorer.SmartContract.CompilerVersion def new(conn, %{"address_id" => address_hash_string}) do - if Chain.smart_contract_fully_verified?(address_hash_string) do + if SmartContract.verified_with_full_match?(address_hash_string) do address_contract_path = conn |> address_contract_path(:index, address_hash_string) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex index 19123f940057..6c978901cd0a 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex @@ -2,12 +2,11 @@ defmodule BlockScoutWeb.AddressContractVerificationVyperController do use BlockScoutWeb, :controller alias BlockScoutWeb.Controller - alias Explorer.Chain alias Explorer.Chain.SmartContract alias Explorer.SmartContract.{CompilerVersion, Vyper.PublisherWorker} def new(conn, %{"address_id" => address_hash_string}) do - if Chain.smart_contract_fully_verified?(address_hash_string) do + if SmartContract.verified_with_full_match?(address_hash_string) do address_contract_path = conn |> address_contract_path(:index, address_hash_string) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex index fd80cf7ad77d..5939df0d2569 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex @@ -83,7 +83,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do [] end - if Chain.smart_contract_fully_verified?(address_hash) do + if SmartContract.verified_with_full_match?(address_hash) do render(conn, :error, error: @verified) else case Sourcify.check_by_address(address_hash) do @@ -114,7 +114,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do } = params ) do with {:check_verified_status, false} <- - {:check_verified_status, Chain.smart_contract_fully_verified?(address_hash)}, + {:check_verified_status, SmartContract.verified_with_full_match?(address_hash)}, {:format, {:ok, _casted_address_hash}} <- to_address_hash(address_hash), {:params, {:ok, fetched_params}} <- {:params, fetch_verifysourcecode_params(params)}, uid <- VerificationStatus.generate_uid(address_hash) do @@ -457,7 +457,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do _ = PublishHelper.check_and_verify(Hash.to_string(address_hash)) result = - case Chain.address_hash_to_smart_contract(address_hash) do + case SmartContract.address_hash_to_smart_contract(address_hash) do nil -> :not_found diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex index 6608c246d062..2edc07e8dec8 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex @@ -3,12 +3,13 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do alias Explorer.Chain alias Explorer.Chain.Hash.Address + alias Explorer.Chain.SmartContract alias Explorer.SmartContract.Solidity.Publisher def create(conn, params) do with {:ok, hash} <- validate_address_hash(params["address_hash"]), :ok <- Chain.check_address_exists(hash), - {:contract, :not_found} <- {:contract, Chain.check_verified_smart_contract_exists(hash)} do + {:contract, :not_found} <- {:contract, SmartContract.check_verified_smart_contract_exists(hash)} do external_libraries = fetch_external_libraries(params) case Publisher.publish(hash, params, external_libraries) do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/import_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/import_controller.ex index 76aa988edfc7..d8fefb4f2b5c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/import_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/import_controller.ex @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.API.V2.ImportController do alias BlockScoutWeb.API.V2.ApiView alias Explorer.{Chain, Repo} - alias Explorer.Chain.{Data, Token} + alias Explorer.Chain.{Data, SmartContract, Token} alias Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand alias Explorer.SmartContract.EthBytecodeDBInterface @@ -60,7 +60,7 @@ defmodule BlockScoutWeb.API.V2.ImportController do Protected by `x-api-key` header. """ @spec try_to_search_contract(Plug.Conn.t(), map()) :: - {:already_verified, nil | Explorer.Chain.SmartContract.t()} + {:already_verified, nil | SmartContract.t()} | {:api_key, nil | binary()} | {:format, :error} | {:not_found, {:error, :not_found}} @@ -73,7 +73,7 @@ defmodule BlockScoutWeb.API.V2.ImportController do {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, {:not_found, {:ok, address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)}, {:already_verified, smart_contract} when is_nil(smart_contract) <- - {:already_verified, Chain.address_hash_to_smart_contract_without_twin(address_hash, @api_true)} do + {:already_verified, SmartContract.address_hash_to_smart_contract_without_twin(address_hash, @api_true)} do creation_tx_input = contract_creation_input(address.hash) with {:ok, %{"sourceType" => type} = source} <- diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex index d9c60969a48c..ab443c33571f 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex @@ -60,7 +60,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do def methods_read(conn, %{"address_hash" => address_hash_string} = params) do with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - smart_contract <- Chain.address_hash_to_smart_contract(address_hash, @api_true), + smart_contract <- SmartContract.address_hash_to_smart_contract(address_hash, @api_true), {:not_found, false} <- {:not_found, is_nil(smart_contract)} do read_only_functions_from_abi = Reader.read_only_functions(smart_contract, address_hash, params["from"]) @@ -90,7 +90,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do {:contract_interaction_disabled, AddressView.contract_interaction_disabled?()}, {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - smart_contract <- Chain.address_hash_to_smart_contract(address_hash, @api_true), + smart_contract <- SmartContract.address_hash_to_smart_contract(address_hash, @api_true), {:not_found, false} <- {:not_found, is_nil(smart_contract)} do conn |> put_status(200) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex index b2bbb07d3189..449630b14cad 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex @@ -8,6 +8,7 @@ defmodule BlockScoutWeb.API.V2.VerificationController do alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.API.V2.ApiView alias Explorer.Chain + alias Explorer.Chain.SmartContract alias Explorer.SmartContract.Solidity.PublisherWorker, as: SolidityPublisherWorker alias Explorer.SmartContract.Solidity.PublishHelper alias Explorer.SmartContract.Vyper.PublisherWorker, as: VyperPublisherWorker @@ -282,7 +283,7 @@ defmodule BlockScoutWeb.API.V2.VerificationController do with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), {:already_verified, false} <- - {:already_verified, Chain.smart_contract_fully_verified?(address_hash, @api_true)} do + {:already_verified, SmartContract.verified_with_full_match?(address_hash, @api_true)} do :validated end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex index 30832eee4aae..acaee3f7bd4d 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex @@ -65,7 +65,7 @@ defmodule BlockScoutWeb.SmartContractController do implementation_abi = if contract_type == "proxy" do implementation_address_hash_string - |> Chain.get_implementation_abi() + |> SmartContract.get_smart_contract_abi() |> Poison.encode!() else [] diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/contract_controller.ex index f792297e3a3c..1e9828a4735d 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/contract_controller.ex @@ -6,13 +6,13 @@ defmodule BlockScoutWeb.Tokens.ContractController do alias BlockScoutWeb.{AccessHelper, TabHelper} alias Explorer.Chain - alias Explorer.Chain.Address + alias Explorer.Chain.{Address, SmartContract} def index(conn, %{"token_id" => address_hash_string} = params) do options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}] with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), - :ok <- Chain.check_verified_smart_contract_exists(address_hash), + :ok <- SmartContract.check_verified_smart_contract_exists(address_hash), {:ok, token} <- Chain.token_from_address_hash(address_hash, options), {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params) do %{type: type, action: action} = diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex index 77c96739cb5d..1d6776ecf04d 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex @@ -1,9 +1,9 @@ <% contract_creation_code = contract_creation_code(@address) %> -<% minimal_proxy_template = Chain.get_minimal_proxy_template(@address.hash) %> -<% metadata_for_verification = minimal_proxy_template || Chain.get_address_verified_twin_contract(@address.hash).verified_contract %> +<% minimal_proxy_template = EIP1167.get_implementation_address(@address.hash) %> +<% metadata_for_verification = minimal_proxy_template || SmartContract.get_address_verified_twin_contract(@address.hash).verified_contract %> <% smart_contract_verified = BlockScoutWeb.AddressView.smart_contract_verified?(@address) %> -<% additional_sources_from_twin = Chain.get_address_verified_twin_contract(@address.hash).additional_sources %> -<% fully_verified = Chain.smart_contract_fully_verified?(@address.hash)%> +<% additional_sources_from_twin = SmartContract.get_address_verified_twin_contract(@address.hash).additional_sources %> +<% fully_verified = SmartContract.verified_with_full_match?(@address.hash)%> <% additional_sources = if smart_contract_verified, do: @address.smart_contract_additional_sources, else: additional_sources_from_twin %> <% visualize_sol2uml_enabled = Explorer.Visualize.Sol2uml.enabled?() %>
@@ -43,16 +43,16 @@ <% end %> <%= if smart_contract_verified || (!smart_contract_verified && metadata_for_verification) do %> <% target_contract = if smart_contract_verified, do: @address.smart_contract, else: metadata_for_verification %> - <%= if @address.smart_contract.verified_via_sourcify && @address.smart_contract.partially_verified && smart_contract_verified do %> + <%= if @address.smart_contract && @address.smart_contract.verified_via_sourcify && @address.smart_contract.partially_verified && smart_contract_verified do %>
<%= gettext("This contract has been partially verified via Sourcify.") %> <% else %> - <%= if @address.smart_contract.verified_via_sourcify && smart_contract_verified do %> + <%= if @address.smart_contract && @address.smart_contract.verified_via_sourcify && smart_contract_verified do %>
<%= gettext("This contract has been verified via Sourcify.") %> <% end %> <% end %> - <%= if @address.smart_contract.verified_via_sourcify && smart_contract_verified do %> + <%= if @address.smart_contract && @address.smart_contract.verified_via_sourcify && smart_contract_verified do %> target="_blank"> View contract in Sourcify repository <%= render BlockScoutWeb.IconsView, "_external_link.html" %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex index 383aa4584d4c..b0a22d4bace6 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex @@ -1,4 +1,4 @@ -<% metadata_for_verification = if assigns[:retrying], do: nil, else: Chain.get_address_verified_twin_contract(@address_hash).verified_contract %> +<% metadata_for_verification = if assigns[:retrying], do: nil, else: SmartContract.get_address_verified_twin_contract(@address_hash).verified_contract %> <% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %> <% fetch_constructor_arguments_automatically = if metadata_for_verification, do: true, else: changeset.changes[:autodetect_constructor_args] || true %> <% display_constructor_arguments_text_area = if fetch_constructor_arguments_automatically, do: "none", else: "block" %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex index a3b772456f61..938631b3e1aa 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex @@ -1,4 +1,4 @@ -<% metadata_for_verification = if assigns[:retrying], do: nil, else: Chain.get_address_verified_twin_contract(@address_hash).verified_contract %> +<% metadata_for_verification = if assigns[:retrying], do: nil, else: SmartContract.get_address_verified_twin_contract(@address_hash).verified_contract %> <% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %>
<%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost") %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex index 34b8d06cfe9d..a1fb8db78646 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex @@ -1,4 +1,4 @@ -<% metadata_for_verification = Chain.get_address_verified_twin_contract(@address_hash).verified_contract %> +<% metadata_for_verification = SmartContract.get_address_verified_twin_contract(@address_hash).verified_contract %> <% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %> <% fetch_constructor_arguments_automatically = if metadata_for_verification, do: true, else: changeset.changes[:autodetect_constructor_args] || true %> <% display_constructor_arguments_text_area = if fetch_constructor_arguments_automatically, do: "none", else: "block" %> @@ -10,7 +10,7 @@ <%= form_for changeset, address_contract_verification_path(@conn, :create), [id: "standard-json-dropzone-form"], - fn f -> %> + fn f -> %> <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_contract_address_field.html", f: f %> <%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_contract_name_field.html", f: f, tooltip: "Must match the name specified in the code. For example, in contract MyContract {..} MyContract is the contract name. Also contract name could be: path/to/file.sol:MyContract" %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex index 4b07896c63a6..a98282193b15 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex @@ -1,4 +1,4 @@ -<% metadata_for_verification = Chain.get_address_verified_twin_contract(@address_hash).verified_contract %> +<% metadata_for_verification = SmartContract.get_address_verified_twin_contract(@address_hash).verified_contract %> <% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_vyper_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %>
<%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost") %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex index af6885a21160..9fc0998535ab 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex @@ -1,5 +1,5 @@ -<% minimal_proxy_template = if assigns[:custom_abi], do: nil, else: Chain.get_minimal_proxy_template(@address.hash) %> -<% metadata_for_verification = if assigns[:custom_abi], do: nil, else: minimal_proxy_template || Chain.get_address_verified_twin_contract(@address.hash).verified_contract %> +<% minimal_proxy_template = if assigns[:custom_abi], do: nil, else: EIP1167.get_implementation_address(@address.hash) %> +<% metadata_for_verification = if assigns[:custom_abi], do: nil, else: minimal_proxy_template || SmartContract.get_address_verified_twin_contract(@address.hash).verified_contract %> <% smart_contract_verified = if assigns[:custom_abi], do: false, else: BlockScoutWeb.AddressView.smart_contract_verified?(@address) %> <%= unless smart_contract_verified do %> <%= if metadata_for_verification do %> @@ -52,8 +52,8 @@ <%= if queryable?(function["inputs"]) || writable?(function) || Helper.read_with_wallet_method?(function) do %>
- <% function_abi = - case Jason.encode([function]) do + <% function_abi = + case Jason.encode([function]) do {:ok, abi_string} -> abi_string _ -> @@ -61,7 +61,7 @@ @implementation_abi else @contract_abi - end + end end %>
" data-type="<%= @contract_type %>" data-url="<%= smart_contract_path(@conn, :show, Address.checksum(@address.hash)) %>" data-contract-address="<%= @address.hash %>" data-contract-abi="<%= function_abi %>" data-implementation-abi="<%= function_abi %>" data-chain-id="<%= Application.get_env(:block_scout_web, :chain_id) %>" data-custom-abi="<%= if assigns[:custom_abi], do: true, else: false %>"> @@ -78,17 +78,17 @@ - @@ -143,7 +143,7 @@ <%= Explorer.coin_name() %>
-
+
<% else %>
"><%= raw(values_with_type(output["value"], output["type"], fetch_name(function["names"], index), 0)) %>
<% end %> @@ -158,4 +158,4 @@ <% end %> <% end %> -<% end %> \ No newline at end of file +<% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex index 0d92f45a146b..518c16191172 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex @@ -1,5 +1,5 @@ <% address_hash = Address.checksum(@token.contract_address_hash) %> -<% is_proxy = BlockScoutWeb.Tokens.OverviewView.smart_contract_is_proxy?(@token) %> +<% is_proxy = BlockScoutWeb.Tokens.OverviewView.token_smart_contract_is_proxy?(@token) %>
<%= link( gettext("Token Transfers"), @@ -50,4 +50,4 @@ class: "card-tab #{tab_status("write-proxy", @conn.request_path)}") %> <% end %> -
\ No newline at end of file + diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_flattened_code_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_flattened_code_view.ex index f6e676d7fcdf..e5adf9b9d4ae 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_flattened_code_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_flattened_code_view.ex @@ -1,7 +1,6 @@ defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeView do use BlockScoutWeb, :view - alias Explorer.Chain alias Explorer.Chain.SmartContract alias Explorer.SmartContract.RustVerifierInterface end diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_multi_part_files_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_multi_part_files_view.ex index 12a80e5eb282..76f88f059ac0 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_multi_part_files_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_multi_part_files_view.ex @@ -1,7 +1,6 @@ defmodule BlockScoutWeb.AddressContractVerificationViaMultiPartFilesView do use BlockScoutWeb, :view - alias Explorer.Chain alias Explorer.Chain.SmartContract alias Explorer.SmartContract.RustVerifierInterface end diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_standard_json_input_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_standard_json_input_view.ex index cf45efe96f0a..9a4298a01c4b 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_standard_json_input_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_standard_json_input_view.ex @@ -1,6 +1,5 @@ defmodule BlockScoutWeb.AddressContractVerificationViaStandardJsonInputView do use BlockScoutWeb, :view - alias Explorer.Chain alias Explorer.Chain.SmartContract end diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_vyper_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_vyper_view.ex index e0ebba9694a1..47da5eab9093 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_vyper_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_vyper_view.ex @@ -1,6 +1,5 @@ defmodule BlockScoutWeb.AddressContractVerificationVyperView do use BlockScoutWeb, :view - alias Explorer.Chain alias Explorer.Chain.SmartContract end diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex index 608971ec7af6..230f17c8ed60 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex @@ -6,6 +6,8 @@ defmodule BlockScoutWeb.AddressContractView do alias ABI.FunctionSelector alias Explorer.Chain alias Explorer.Chain.{Address, Data, InternalTransaction, Transaction} + alias Explorer.Chain.SmartContract + alias Explorer.Chain.SmartContract.Proxy.EIP1167 def render("scripts.html", %{conn: conn}) do render_scripts(conn, "address_contract/code_highlighting.js") diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex index 3098dfc09692..3884c86db8dd 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex @@ -9,6 +9,7 @@ defmodule BlockScoutWeb.AddressView do alias Explorer.Chain.Address.Counters alias Explorer.Chain.{Address, Hash, InternalTransaction, Log, SmartContract, Token, TokenTransfer, Transaction, Wei} alias Explorer.Chain.Block.Reward + alias Explorer.Chain.SmartContract.Proxy alias Explorer.ExchangeRates.Token, as: TokenExchangeRate alias Explorer.SmartContract.{Helper, Writer} @@ -199,7 +200,7 @@ defmodule BlockScoutWeb.AddressView do def primary_name(%Address{names: _} = address) do with false <- is_nil(address.contract_code), - twin <- Chain.get_verified_twin_contract(address), + twin <- SmartContract.get_verified_twin_contract(address), false <- is_nil(twin) do twin.name else @@ -264,8 +265,8 @@ defmodule BlockScoutWeb.AddressView do def smart_contract_is_proxy?(address, options \\ []) - def smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{} = smart_contract}, options) do - SmartContract.proxy_contract?(smart_contract, options) + def smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{} = smart_contract}, _options) do + Proxy.proxy_contract?(smart_contract) end def smart_contract_is_proxy?(%Address{smart_contract: _}, _), do: false @@ -456,7 +457,7 @@ defmodule BlockScoutWeb.AddressView do end def smart_contract_is_gnosis_safe_proxy?(%Address{smart_contract: %SmartContract{}} = address) do - address.smart_contract.name == "GnosisSafeProxy" && Chain.gnosis_safe_contract?(address.smart_contract.abi) + address.smart_contract.name == "GnosisSafeProxy" && Proxy.gnosis_safe_contract?(address.smart_contract.abi) end def smart_contract_is_gnosis_safe_proxy?(_address), do: false diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex index d1686cef1565..bc47f2bd85cc 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex @@ -4,7 +4,6 @@ defmodule BlockScoutWeb.API.RPC.ContractView do alias BlockScoutWeb.AddressView alias BlockScoutWeb.API.RPC.RPCView alias Ecto.Association.NotLoaded - alias Explorer.Chain alias Explorer.Chain.{Address, DecompiledSmartContract, SmartContract} defguardp is_empty_string(input) when input == "" or input == nil @@ -168,7 +167,7 @@ defmodule BlockScoutWeb.API.RPC.ContractView do end defp insert_additional_sources(output, address) do - additional_sources_from_twin = Chain.get_address_verified_twin_contract(address.hash).additional_sources + additional_sources_from_twin = SmartContract.get_address_verified_twin_contract(address.hash).additional_sources additional_sources = if AddressView.smart_contract_verified?(address), diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex index 08fa9377b0d9..868536acfbbe 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex @@ -11,6 +11,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do alias Ecto.Changeset alias Explorer.Chain alias Explorer.Chain.{Address, SmartContract} + alias Explorer.Chain.SmartContract.Proxy.EIP1167 alias Explorer.Visualize.Sol2uml require Logger @@ -134,12 +135,12 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do # credo:disable-for-next-line def prepare_smart_contract(%Address{smart_contract: %SmartContract{} = smart_contract} = address) do - minimal_proxy_template = Chain.get_minimal_proxy_template(address.hash, @api_true) - twin = Chain.get_address_verified_twin_contract(address.hash, @api_true) + minimal_proxy_template = EIP1167.get_implementation_address(address.hash, @api_true) + twin = SmartContract.get_address_verified_twin_contract(address.hash, @api_true) metadata_for_verification = minimal_proxy_template || twin.verified_contract smart_contract_verified = AddressView.smart_contract_verified?(address) additional_sources_from_twin = twin.additional_sources - fully_verified = Chain.smart_contract_fully_verified?(address.hash, @api_true) + fully_verified = SmartContract.verified_with_full_match?(address.hash, @api_true) additional_sources = if smart_contract_verified, do: address.smart_contract_additional_sources, else: additional_sources_from_twin diff --git a/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex index f0245adc2dfc..ad32647d14e0 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex @@ -6,6 +6,8 @@ defmodule BlockScoutWeb.SmartContractView do alias Explorer.Chain alias Explorer.Chain.{Address, Transaction} alias Explorer.Chain.Hash.Address, as: HashAddress + alias Explorer.Chain.SmartContract + alias Explorer.Chain.SmartContract.Proxy.EIP1167 alias Explorer.SmartContract.Helper require Logger @@ -210,7 +212,7 @@ defmodule BlockScoutWeb.SmartContractView do end def decode_revert_reason(to_address, revert_reason, options \\ []) do - smart_contract = Chain.address_hash_to_smart_contract(to_address, options) + smart_contract = SmartContract.address_hash_to_smart_contract(to_address, options) Transaction.decoded_revert_reason( %Transaction{to_address: %{smart_contract: smart_contract}, hash: to_address}, diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex index 9bc2dbfe45e6..441ac1a0a4f1 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex @@ -3,6 +3,7 @@ defmodule BlockScoutWeb.Tokens.OverviewView do alias Explorer.{Chain, CustomContractsHelper} alias Explorer.Chain.{Address, SmartContract, Token} + alias Explorer.Chain.SmartContract.Proxy alias Explorer.SmartContract.{Helper, Writer} alias BlockScoutWeb.{AccessHelper, CurrencyHelper, LayoutView} @@ -53,11 +54,13 @@ defmodule BlockScoutWeb.Tokens.OverviewView do def smart_contract_with_read_only_functions?(%Token{contract_address: %Address{smart_contract: nil}}), do: false - def smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: %SmartContract{} = smart_contract}}) do - SmartContract.proxy_contract?(smart_contract) + def token_smart_contract_is_proxy?(%Token{ + contract_address: %Address{smart_contract: %SmartContract{} = smart_contract} + }) do + Proxy.proxy_contract?(smart_contract) end - def smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: nil}}), do: false + def token_smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: nil}}), do: false def smart_contract_with_write_functions?(%Token{ contract_address: %Address{smart_contract: %SmartContract{}} = address diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index d15c1455abf1..aed1dc8afd75 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -265,7 +265,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:20 #: lib/block_scout_web/templates/transaction_state/index.html.eex:34 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:60 -#: lib/block_scout_web/views/address_view.ex:108 +#: lib/block_scout_web/views/address_view.ex:109 #, elixir-autogen, elixir-format msgid "Address" msgstr "" @@ -556,7 +556,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:56 #: lib/block_scout_web/templates/address/overview.html.eex:275 #: lib/block_scout_web/templates/address_validation/index.html.eex:11 -#: lib/block_scout_web/views/address_view.ex:385 +#: lib/block_scout_web/views/address_view.ex:386 #, elixir-autogen, elixir-format msgid "Blocks Validated" msgstr "" @@ -656,13 +656,13 @@ msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 -#: lib/block_scout_web/views/address_view.ex:378 +#: lib/block_scout_web/views/address_view.ex:379 #, elixir-autogen, elixir-format msgid "Code" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:42 -#: lib/block_scout_web/views/address_view.ex:384 +#: lib/block_scout_web/views/address_view.ex:385 #, elixir-autogen, elixir-format msgid "Coin Balance History" msgstr "" @@ -771,14 +771,14 @@ msgstr "" #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:18 #: lib/block_scout_web/templates/account/custom_abi/index.html.eex:29 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex:3 -#: lib/block_scout_web/views/address_view.ex:106 +#: lib/block_scout_web/views/address_view.ex:107 #, elixir-autogen, elixir-format msgid "Contract Address" msgstr "" #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16 -#: lib/block_scout_web/views/address_view.ex:46 -#: lib/block_scout_web/views/address_view.ex:80 +#: lib/block_scout_web/views/address_view.ex:47 +#: lib/block_scout_web/views/address_view.ex:81 #, elixir-autogen, elixir-format msgid "Contract Address Pending" msgstr "" @@ -1084,7 +1084,7 @@ msgstr "" msgid "Decoded" msgstr "" -#: lib/block_scout_web/views/address_view.ex:379 +#: lib/block_scout_web/views/address_view.ex:380 #, elixir-autogen, elixir-format msgid "Decompiled Code" msgstr "" @@ -1601,7 +1601,7 @@ msgstr "" #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 #: 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:375 +#: lib/block_scout_web/views/address_view.ex:376 #: lib/block_scout_web/views/transaction_view.ex:533 #, elixir-autogen, elixir-format msgid "Internal Transactions" @@ -1614,7 +1614,7 @@ msgstr "" #: lib/block_scout_web/templates/tokens/inventory/index.html.eex:16 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19 -#: lib/block_scout_web/views/tokens/overview_view.ex:42 +#: lib/block_scout_web/views/tokens/overview_view.ex:43 #, elixir-autogen, elixir-format msgid "Inventory" msgstr "" @@ -1718,7 +1718,7 @@ msgstr "" #: lib/block_scout_web/templates/address_logs/index.html.eex:10 #: 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:386 +#: lib/block_scout_web/views/address_view.ex:387 #: lib/block_scout_web/views/transaction_view.ex:534 #, elixir-autogen, elixir-format msgid "Logs" @@ -1732,7 +1732,7 @@ msgstr "" #: lib/block_scout_web/templates/chain/show.html.eex:53 #: lib/block_scout_web/templates/layout/app.html.eex:50 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:85 -#: lib/block_scout_web/views/address_view.ex:146 +#: lib/block_scout_web/views/address_view.ex:147 #, elixir-autogen, elixir-format msgid "Market Cap" msgstr "" @@ -2208,15 +2208,15 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:89 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 -#: lib/block_scout_web/views/address_view.ex:380 -#: lib/block_scout_web/views/tokens/overview_view.ex:41 +#: lib/block_scout_web/views/address_view.ex:381 +#: lib/block_scout_web/views/tokens/overview_view.ex:42 #, elixir-autogen, elixir-format msgid "Read Contract" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:96 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 -#: lib/block_scout_web/views/address_view.ex:381 +#: lib/block_scout_web/views/address_view.ex:382 #, elixir-autogen, elixir-format msgid "Read Proxy" msgstr "" @@ -2870,7 +2870,7 @@ msgstr "" #: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16 #: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11 -#: lib/block_scout_web/views/tokens/overview_view.ex:40 +#: lib/block_scout_web/views/tokens/overview_view.ex:41 #, elixir-autogen, elixir-format msgid "Token Holders" msgstr "" @@ -2903,9 +2903,9 @@ msgstr "" #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 -#: lib/block_scout_web/views/address_view.ex:377 +#: 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:39 +#: lib/block_scout_web/views/tokens/overview_view.ex:40 #: lib/block_scout_web/views/transaction_view.ex:532 #, elixir-autogen, elixir-format msgid "Token Transfers" @@ -2927,7 +2927,7 @@ msgstr "" #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 #: lib/block_scout_web/templates/layout/_topnav.html.eex:84 #: lib/block_scout_web/templates/tokens/index.html.eex:10 -#: lib/block_scout_web/views/address_view.ex:374 +#: lib/block_scout_web/views/address_view.ex:375 #, elixir-autogen, elixir-format msgid "Tokens" msgstr "" @@ -3099,7 +3099,7 @@ msgstr "" #: lib/block_scout_web/templates/block/overview.html.eex:80 #: lib/block_scout_web/templates/chain/show.html.eex:214 #: lib/block_scout_web/templates/layout/_topnav.html.eex:49 -#: lib/block_scout_web/views/address_view.ex:376 +#: lib/block_scout_web/views/address_view.ex:377 #, elixir-autogen, elixir-format msgid "Transactions" msgstr "" @@ -3469,14 +3469,14 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:103 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 -#: lib/block_scout_web/views/address_view.ex:382 +#: lib/block_scout_web/views/address_view.ex:383 #, elixir-autogen, elixir-format msgid "Write Contract" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:110 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 -#: lib/block_scout_web/views/address_view.ex:383 +#: lib/block_scout_web/views/address_view.ex:384 #, elixir-autogen, elixir-format msgid "Write Proxy" msgstr "" @@ -3585,7 +3585,7 @@ msgstr "" msgid "fallback" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:26 +#: lib/block_scout_web/views/address_contract_view.ex:28 #, elixir-autogen, elixir-format msgid "false" msgstr "" @@ -3641,7 +3641,7 @@ msgstr "" msgid "string" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:25 +#: lib/block_scout_web/views/address_contract_view.ex:27 #, elixir-autogen, elixir-format msgid "true" msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index d770bca3428d..4ed272ce9984 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -265,7 +265,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:20 #: lib/block_scout_web/templates/transaction_state/index.html.eex:34 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:60 -#: lib/block_scout_web/views/address_view.ex:108 +#: lib/block_scout_web/views/address_view.ex:109 #, elixir-autogen, elixir-format msgid "Address" msgstr "" @@ -556,7 +556,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:56 #: lib/block_scout_web/templates/address/overview.html.eex:275 #: lib/block_scout_web/templates/address_validation/index.html.eex:11 -#: lib/block_scout_web/views/address_view.ex:385 +#: lib/block_scout_web/views/address_view.ex:386 #, elixir-autogen, elixir-format msgid "Blocks Validated" msgstr "" @@ -656,13 +656,13 @@ msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 -#: lib/block_scout_web/views/address_view.ex:378 +#: lib/block_scout_web/views/address_view.ex:379 #, elixir-autogen, elixir-format msgid "Code" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:42 -#: lib/block_scout_web/views/address_view.ex:384 +#: lib/block_scout_web/views/address_view.ex:385 #, elixir-autogen, elixir-format msgid "Coin Balance History" msgstr "" @@ -771,14 +771,14 @@ msgstr "" #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:18 #: lib/block_scout_web/templates/account/custom_abi/index.html.eex:29 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex:3 -#: lib/block_scout_web/views/address_view.ex:106 +#: lib/block_scout_web/views/address_view.ex:107 #, elixir-autogen, elixir-format msgid "Contract Address" msgstr "" #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16 -#: lib/block_scout_web/views/address_view.ex:46 -#: lib/block_scout_web/views/address_view.ex:80 +#: lib/block_scout_web/views/address_view.ex:47 +#: lib/block_scout_web/views/address_view.ex:81 #, elixir-autogen, elixir-format msgid "Contract Address Pending" msgstr "" @@ -1084,7 +1084,7 @@ msgstr "" msgid "Decoded" msgstr "" -#: lib/block_scout_web/views/address_view.ex:379 +#: lib/block_scout_web/views/address_view.ex:380 #, elixir-autogen, elixir-format msgid "Decompiled Code" msgstr "" @@ -1601,7 +1601,7 @@ msgstr "" #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 #: 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:375 +#: lib/block_scout_web/views/address_view.ex:376 #: lib/block_scout_web/views/transaction_view.ex:533 #, elixir-autogen, elixir-format msgid "Internal Transactions" @@ -1614,7 +1614,7 @@ msgstr "" #: lib/block_scout_web/templates/tokens/inventory/index.html.eex:16 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19 -#: lib/block_scout_web/views/tokens/overview_view.ex:42 +#: lib/block_scout_web/views/tokens/overview_view.ex:43 #, elixir-autogen, elixir-format msgid "Inventory" msgstr "" @@ -1718,7 +1718,7 @@ msgstr "" #: lib/block_scout_web/templates/address_logs/index.html.eex:10 #: 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:386 +#: lib/block_scout_web/views/address_view.ex:387 #: lib/block_scout_web/views/transaction_view.ex:534 #, elixir-autogen, elixir-format msgid "Logs" @@ -1732,7 +1732,7 @@ msgstr "" #: lib/block_scout_web/templates/chain/show.html.eex:53 #: lib/block_scout_web/templates/layout/app.html.eex:50 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:85 -#: lib/block_scout_web/views/address_view.ex:146 +#: lib/block_scout_web/views/address_view.ex:147 #, elixir-autogen, elixir-format msgid "Market Cap" msgstr "" @@ -2208,15 +2208,15 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:89 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 -#: lib/block_scout_web/views/address_view.ex:380 -#: lib/block_scout_web/views/tokens/overview_view.ex:41 +#: lib/block_scout_web/views/address_view.ex:381 +#: lib/block_scout_web/views/tokens/overview_view.ex:42 #, elixir-autogen, elixir-format msgid "Read Contract" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:96 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 -#: lib/block_scout_web/views/address_view.ex:381 +#: lib/block_scout_web/views/address_view.ex:382 #, elixir-autogen, elixir-format msgid "Read Proxy" msgstr "" @@ -2870,7 +2870,7 @@ msgstr "" #: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16 #: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11 -#: lib/block_scout_web/views/tokens/overview_view.ex:40 +#: lib/block_scout_web/views/tokens/overview_view.ex:41 #, elixir-autogen, elixir-format msgid "Token Holders" msgstr "" @@ -2903,9 +2903,9 @@ msgstr "" #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 -#: lib/block_scout_web/views/address_view.ex:377 +#: 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:39 +#: lib/block_scout_web/views/tokens/overview_view.ex:40 #: lib/block_scout_web/views/transaction_view.ex:532 #, elixir-autogen, elixir-format msgid "Token Transfers" @@ -2927,7 +2927,7 @@ msgstr "" #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 #: lib/block_scout_web/templates/layout/_topnav.html.eex:84 #: lib/block_scout_web/templates/tokens/index.html.eex:10 -#: lib/block_scout_web/views/address_view.ex:374 +#: lib/block_scout_web/views/address_view.ex:375 #, elixir-autogen, elixir-format msgid "Tokens" msgstr "" @@ -3099,7 +3099,7 @@ msgstr "" #: lib/block_scout_web/templates/block/overview.html.eex:80 #: lib/block_scout_web/templates/chain/show.html.eex:214 #: lib/block_scout_web/templates/layout/_topnav.html.eex:49 -#: lib/block_scout_web/views/address_view.ex:376 +#: lib/block_scout_web/views/address_view.ex:377 #, elixir-autogen, elixir-format msgid "Transactions" msgstr "" @@ -3469,14 +3469,14 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:103 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 -#: lib/block_scout_web/views/address_view.ex:382 +#: lib/block_scout_web/views/address_view.ex:383 #, elixir-autogen, elixir-format msgid "Write Contract" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:110 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 -#: lib/block_scout_web/views/address_view.ex:383 +#: lib/block_scout_web/views/address_view.ex:384 #, elixir-autogen, elixir-format msgid "Write Proxy" msgstr "" @@ -3585,7 +3585,7 @@ msgstr "" msgid "fallback" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:26 +#: lib/block_scout_web/views/address_contract_view.ex:28 #, elixir-autogen, elixir-format msgid "false" msgstr "" @@ -3641,7 +3641,7 @@ msgstr "" msgid "string" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:25 +#: lib/block_scout_web/views/address_contract_view.ex:27 #, elixir-autogen, elixir-format msgid "true" msgstr "" diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs index 2a05b0c65980..8537ecd4e7a3 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs @@ -51,7 +51,7 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do insert(:smart_contract, address_hash: contract_address.hash, contract_code_md5: "123") - get_eip1967_implementation() + request_zero_implementations() conn = get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash))) @@ -82,7 +82,7 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do end end - def get_eip1967_implementation do + def request_zero_implementations do EthereumJSONRPC.Mox |> expect(:json_rpc, fn %{ id: 0, @@ -120,5 +120,17 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs index 77d5a8060f9e..1b49b35d3979 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs @@ -51,7 +51,7 @@ defmodule BlockScoutWeb.AddressReadProxyControllerTest do insert(:smart_contract, address_hash: contract_address.hash, contract_code_md5: "123") - get_eip1967_implementation() + request_zero_implementations() conn = get(conn, address_read_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash))) @@ -80,7 +80,7 @@ defmodule BlockScoutWeb.AddressReadProxyControllerTest do end end - def get_eip1967_implementation do + def request_zero_implementations do EthereumJSONRPC.Mox |> expect(:json_rpc, fn %{ id: 0, @@ -118,5 +118,17 @@ defmodule BlockScoutWeb.AddressReadProxyControllerTest do _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs index b577be9c74e3..30caf25e7273 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs @@ -53,7 +53,7 @@ defmodule BlockScoutWeb.AddressWriteContractControllerTest do insert(:smart_contract, address_hash: contract_address.hash, contract_code_md5: "123") - get_eip1967_implementation() + request_zero_implementations() conn = get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash))) @@ -84,7 +84,7 @@ defmodule BlockScoutWeb.AddressWriteContractControllerTest do end end - def get_eip1967_implementation do + def request_zero_implementations do EthereumJSONRPC.Mox |> expect(:json_rpc, fn %{ id: 0, @@ -122,5 +122,17 @@ defmodule BlockScoutWeb.AddressWriteContractControllerTest do _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs index 01b1b931134a..7225e06e7b57 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs @@ -51,7 +51,7 @@ defmodule BlockScoutWeb.AddressWriteProxyControllerTest do insert(:smart_contract, address_hash: contract_address.hash, contract_code_md5: "123") - get_eip1967_implementation() + request_zero_implementations() conn = get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash))) @@ -82,7 +82,7 @@ defmodule BlockScoutWeb.AddressWriteProxyControllerTest do end end - def get_eip1967_implementation do + def request_zero_implementations do EthereumJSONRPC.Mox |> expect(:json_rpc, fn %{ id: 0, @@ -120,5 +120,17 @@ defmodule BlockScoutWeb.AddressWriteProxyControllerTest do _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs index 752c393c4710..f715149a9961 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs @@ -1,8 +1,6 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do use BlockScoutWeb.ConnCase alias Explorer.Chain.SmartContract - alias Explorer.Chain - # alias Explorer.{Chain, Factory} import Mox @@ -649,7 +647,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do %SmartContract.ExternalLibrary{:address_hash => "0x283539e1b1daf24cdd58a3e934d55062ea663c3f", :name => "Test2"} ] - {:ok, %SmartContract{} = contract} = Chain.create_smart_contract(valid_attrs, external_libraries) + {:ok, %SmartContract{} = contract} = SmartContract.create_smart_contract(valid_attrs, external_libraries) params = %{ "module" => "contract", @@ -771,7 +769,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do # |> get("/api", params) # |> json_response(200) - # verified_contract = Chain.address_hash_to_smart_contract(contract_address.hash) + # verified_contract = SmartContract.address_hash_to_smart_contract(contract_address.hash) # expected_result = %{ # "Address" => to_string(contract_address.hash), @@ -844,7 +842,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do # result = response["result"] - # verified_contract = Chain.address_hash_to_smart_contract(contract_address.hash) + # verified_contract = SmartContract.address_hash_to_smart_contract(contract_address.hash) # assert result["Address"] == to_string(contract_address.hash) @@ -967,5 +965,17 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index 9c3a1f63254e..87e2c0455dca 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -1920,6 +1920,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} end) + request_zero_implementations() + expect( EthereumJSONRPC.Mox, :json_rpc, @@ -2030,6 +2032,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} end) + request_zero_implementations() + expect( EthereumJSONRPC.Mox, :json_rpc, @@ -2115,6 +2119,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} end) + request_zero_implementations() + expect( EthereumJSONRPC.Mox, :json_rpc, @@ -2184,6 +2190,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} end) + request_zero_implementations() + expect( EthereumJSONRPC.Mox, :json_rpc, @@ -2252,6 +2260,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} end) + request_zero_implementations() + expect( EthereumJSONRPC.Mox, :json_rpc, @@ -2344,6 +2354,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} end) + request_zero_implementations() + contract = insert(:smart_contract) request = get(conn, "/api/v2/smart-contracts/#{contract.address_hash}/methods-write-proxy") @@ -2460,4 +2472,32 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "solidity" end end + + defp request_zero_implementations do + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs index f718a35b4770..ede7f16822d1 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs @@ -970,7 +970,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do test "check stability fees", %{conn: conn} do tx = insert(:transaction) |> with_block() - log = + _log = insert(:log, transaction: tx, index: 1, @@ -1033,7 +1033,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do test "check stability if token absent in DB", %{conn: conn} do tx = insert(:transaction) |> with_block() - log = + _log = insert(:log, transaction: tx, index: 1, diff --git a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs index 5a52e764a3cd..eb8c1973ef98 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs @@ -122,6 +122,8 @@ defmodule BlockScoutWeb.SmartContractControllerTest do ) blockchain_get_implementation_mock() + blockchain_get_implementation_mock_empty() + blockchain_get_implementation_mock_empty() path = smart_contract_path(BlockScoutWeb.Endpoint, :index, @@ -159,6 +161,8 @@ defmodule BlockScoutWeb.SmartContractControllerTest do ) blockchain_get_implementation_mock_2() + blockchain_get_implementation_mock_empty() + blockchain_get_implementation_mock_empty() path = smart_contract_path(BlockScoutWeb.Endpoint, :index, @@ -285,53 +289,23 @@ defmodule BlockScoutWeb.SmartContractControllerTest do ) end - defp blockchain_get_implementation_mock_2 do + defp blockchain_get_implementation_mock_empty do expect( EthereumJSONRPC.Mox, :json_rpc, fn %{id: _, method: _, params: [_, _, _]}, _options -> - {:ok, "0x000000000000000000000000cebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"} + {:ok, "0x"} end ) end - def get_eip1967_implementation do - EthereumJSONRPC.Mox - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) + defp blockchain_get_implementation_mock_2 do + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn %{id: _, method: _, params: [_, _, _]}, _options -> + {:ok, "0x000000000000000000000000cebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"} + end + ) end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/tokens/read_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/tokens/read_contract_controller_test.exs index ab5ba16f9c5d..589d53a71664 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/tokens/read_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/read_contract_controller_test.exs @@ -53,7 +53,7 @@ defmodule BlockScoutWeb.Tokens.ContractControllerTest do token: token ) - get_eip1967_implementation() + request_zero_implementations() conn = get(conn, token_read_contract_path(BlockScoutWeb.Endpoint, :index, token.contract_address_hash)) @@ -62,7 +62,7 @@ defmodule BlockScoutWeb.Tokens.ContractControllerTest do end end - def get_eip1967_implementation do + def request_zero_implementations do EthereumJSONRPC.Mox |> expect(:json_rpc, fn %{ id: 0, @@ -100,5 +100,17 @@ defmodule BlockScoutWeb.Tokens.ContractControllerTest do _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs index 6b789a2d0f4c..273a53286d62 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs @@ -201,6 +201,18 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options -> {:ok, "100"} end) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 9a1f114eae5c..5717a4997e20 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -26,7 +26,6 @@ defmodule Explorer.Chain do ] import EthereumJSONRPC, only: [integer_to_quantity: 1, fetch_block_internal_transactions: 2] - import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] require Logger @@ -58,7 +57,6 @@ defmodule Explorer.Chain do PendingBlockOperation, Search, SmartContract, - SmartContractAdditionalSource, Token, Token.Instance, TokenTransfer, @@ -87,11 +85,10 @@ defmodule Explorer.Chain do alias Explorer.Chain.Fetcher.{CheckBytecodeMatchingOnDemand, LookUpSmartContractSourcesOnDemand} alias Explorer.Chain.Import.Runner alias Explorer.Chain.InternalTransaction.{CallType, Type} + alias Explorer.Chain.SmartContract.Proxy.EIP1167 alias Explorer.Market.MarketHistoryCache alias Explorer.{PagingOptions, Repo} - alias Explorer.SmartContract.Helper - alias Explorer.SmartContract.Solidity.Verifier alias Dataloader.Ecto, as: DataloaderEcto @@ -1065,6 +1062,33 @@ defmodule Explorer.Chain do end end + defp set_address_decompiled(repo, address_hash) do + query = + from( + address in Address, + where: address.hash == ^address_hash + ) + + case repo.update_all(query, set: [decompiled: true]) do + {1, _} -> {:ok, []} + _ -> {:error, "There was an error annotating that the address has been decompiled."} + end + end + + @spec verified_contracts_top(non_neg_integer()) :: [Hash.Address.t()] + def verified_contracts_top(limit) do + query = + from(contract in SmartContract, + inner_join: address in Address, + on: contract.address_hash == address.hash, + order_by: [desc: address.transactions_count], + limit: ^limit, + select: contract.address_hash + ) + + Repo.all(query) + end + @doc """ Converts the `Explorer.Chain.Data.t:t/0` to `iodata` representation that can be written to users efficiently. @@ -1289,7 +1313,7 @@ defmodule Explorer.Chain do if smart_contract do address_result else - compose_smart_contract(address_result, hash, options) + SmartContract.compose_smart_contract(address_result, hash, options) end _ -> @@ -1303,27 +1327,6 @@ defmodule Explorer.Chain do end end - defp compose_smart_contract(address_result, hash, options) do - address_verified_twin_contract = - get_minimal_proxy_template(hash, options) || - get_address_verified_twin_contract(hash, options).verified_contract - - if address_verified_twin_contract do - address_verified_twin_contract_updated = - address_verified_twin_contract - |> Map.put(:address_hash, hash) - |> Map.put(:metadata_from_verified_twin, true) - |> Map.put(:implementation_address_hash, nil) - |> Map.put(:implementation_name, nil) - |> Map.put(:implementation_fetched_at, nil) - - address_result - |> Map.put(:smart_contract, address_verified_twin_contract_updated) - else - address_result - end - end - def decompiled_code(address_hash, version) do query = from(contract in DecompiledSmartContract, @@ -1481,15 +1484,15 @@ defmodule Explorer.Chain do if smart_contract do CheckBytecodeMatchingOnDemand.trigger_check(address_result, smart_contract) LookUpSmartContractSourcesOnDemand.trigger_fetch(address_result, smart_contract) - check_and_update_constructor_args(address_result) + SmartContract.check_and_update_constructor_args(address_result) else LookUpSmartContractSourcesOnDemand.trigger_fetch(address_result, nil) address_verified_twin_contract = - get_minimal_proxy_template(hash, options) || - get_address_verified_twin_contract(hash, options).verified_contract + EIP1167.get_implementation_address(hash, options) || + SmartContract.get_address_verified_twin_contract(hash, options).verified_contract - add_twin_info_to_contract(address_result, address_verified_twin_contract, hash) + SmartContract.add_twin_info_to_contract(address_result, address_verified_twin_contract, hash) end _ -> @@ -1504,53 +1507,6 @@ defmodule Explorer.Chain do end end - defp check_and_update_constructor_args( - %SmartContract{address_hash: address_hash, constructor_arguments: nil, verified_via_sourcify: true} = - smart_contract - ) do - if args = Verifier.parse_constructor_arguments_for_sourcify_contract(address_hash, smart_contract.abi) do - smart_contract |> SmartContract.changeset(%{constructor_arguments: args}) |> Repo.update() - %SmartContract{smart_contract | constructor_arguments: args} - else - smart_contract - end - end - - defp check_and_update_constructor_args( - %Address{ - hash: address_hash, - contract_code: deployed_bytecode, - smart_contract: %SmartContract{constructor_arguments: nil, verified_via_sourcify: true} = smart_contract - } = address - ) do - if args = - Verifier.parse_constructor_arguments_for_sourcify_contract(address_hash, smart_contract.abi, deployed_bytecode) do - smart_contract |> SmartContract.changeset(%{constructor_arguments: args}) |> Repo.update() - %Address{address | smart_contract: %SmartContract{smart_contract | constructor_arguments: args}} - else - address - end - end - - defp check_and_update_constructor_args(other), do: other - - defp add_twin_info_to_contract(address_result, address_verified_twin_contract, _hash) - when is_nil(address_verified_twin_contract), - do: address_result - - defp add_twin_info_to_contract(address_result, address_verified_twin_contract, hash) do - address_verified_twin_contract_updated = - address_verified_twin_contract - |> Map.put(:address_hash, hash) - |> Map.put(:metadata_from_verified_twin, true) - |> Map.put(:implementation_address_hash, nil) - |> Map.put(:implementation_name, nil) - |> Map.put(:implementation_fetched_at, nil) - - address_result - |> Map.put(:smart_contract, address_verified_twin_contract_updated) - end - @spec find_decompiled_contract_address(Hash.Address.t()) :: {:ok, Address.t()} | {:error, :not_found} def find_decompiled_contract_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do query = @@ -3569,412 +3525,6 @@ defmodule Explorer.Chain do end end - @doc """ - Inserts a `t:SmartContract.t/0`. - - As part of inserting a new smart contract, an additional record is inserted for - naming the address for reference. - """ - @spec create_smart_contract(map()) :: {:ok, SmartContract.t()} | {:error, Ecto.Changeset.t()} - def create_smart_contract(attrs \\ %{}, external_libraries \\ [], secondary_sources \\ []) do - new_contract = %SmartContract{} - - attrs = - attrs - |> Helper.add_contract_code_md5() - - smart_contract_changeset = - new_contract - |> SmartContract.changeset(attrs) - |> Changeset.put_change(:external_libraries, external_libraries) - - new_contract_additional_source = %SmartContractAdditionalSource{} - - smart_contract_additional_sources_changesets = - if secondary_sources do - secondary_sources - |> Enum.map(fn changeset -> - new_contract_additional_source - |> SmartContractAdditionalSource.changeset(changeset) - end) - else - [] - end - - address_hash = Changeset.get_field(smart_contract_changeset, :address_hash) - - # Enforce ShareLocks tables order (see docs: sharelocks.md) - insert_contract_query = - Multi.new() - |> Multi.run(:set_address_verified, fn repo, _ -> set_address_verified(repo, address_hash) end) - |> Multi.run(:clear_primary_address_names, fn repo, _ -> clear_primary_address_names(repo, address_hash) end) - |> Multi.insert(:smart_contract, smart_contract_changeset) - - insert_contract_query_with_additional_sources = - smart_contract_additional_sources_changesets - |> Enum.with_index() - |> Enum.reduce(insert_contract_query, fn {changeset, index}, multi -> - Multi.insert(multi, "smart_contract_additional_source_#{Integer.to_string(index)}", changeset) - end) - - insert_result = - insert_contract_query_with_additional_sources - |> Repo.transaction() - - create_address_name(Repo, Changeset.get_field(smart_contract_changeset, :name), address_hash) - - case insert_result do - {:ok, %{smart_contract: smart_contract}} -> - {:ok, smart_contract} - - {:error, :smart_contract, changeset, _} -> - {:error, changeset} - - {:error, :set_address_verified, message, _} -> - {:error, message} - end - end - - @doc """ - Updates a `t:SmartContract.t/0`. - - Has the similar logic as create_smart_contract/1. - Used in cases when you need to update row in DB contains SmartContract, e.g. in case of changing - status `partially verified` to `fully verified` (re-verify). - """ - @spec update_smart_contract(map()) :: {:ok, SmartContract.t()} | {:error, Ecto.Changeset.t()} - def update_smart_contract(attrs \\ %{}, external_libraries \\ [], secondary_sources \\ []) do - address_hash = Map.get(attrs, :address_hash) - - query = - from( - smart_contract in SmartContract, - where: smart_contract.address_hash == ^address_hash - ) - - query_sources = - from( - source in SmartContractAdditionalSource, - where: source.address_hash == ^address_hash - ) - - _delete_sources = Repo.delete_all(query_sources) - - smart_contract = Repo.one(query) - - smart_contract_changeset = - smart_contract - |> SmartContract.changeset(attrs) - |> Changeset.put_change(:external_libraries, external_libraries) - - new_contract_additional_source = %SmartContractAdditionalSource{} - - smart_contract_additional_sources_changesets = - if secondary_sources do - secondary_sources - |> Enum.map(fn changeset -> - new_contract_additional_source - |> SmartContractAdditionalSource.changeset(changeset) - end) - else - [] - end - - # Enforce ShareLocks tables order (see docs: sharelocks.md) - insert_contract_query = - Multi.new() - |> Multi.run(:clear_primary_address_names, fn repo, _ -> clear_primary_address_names(repo, address_hash) end) - |> Multi.update(:smart_contract, smart_contract_changeset) - - insert_contract_query_with_additional_sources = - smart_contract_additional_sources_changesets - |> Enum.with_index() - |> Enum.reduce(insert_contract_query, fn {changeset, index}, multi -> - Multi.insert(multi, "smart_contract_additional_source_#{Integer.to_string(index)}", changeset) - end) - - insert_result = - insert_contract_query_with_additional_sources - |> Repo.transaction() - - create_address_name(Repo, Changeset.get_field(smart_contract_changeset, :name), address_hash) - - case insert_result do - {:ok, %{smart_contract: smart_contract}} -> - {:ok, smart_contract} - - {:error, :smart_contract, changeset, _} -> - {:error, changeset} - - {:error, :set_address_verified, message, _} -> - {:error, message} - end - end - - defp set_address_verified(repo, address_hash) do - query = - from( - address in Address, - where: address.hash == ^address_hash - ) - - case repo.update_all(query, set: [verified: true]) do - {1, _} -> {:ok, []} - _ -> {:error, "There was an error annotating that the address has been verified."} - end - end - - defp set_address_decompiled(repo, address_hash) do - query = - from( - address in Address, - where: address.hash == ^address_hash - ) - - case repo.update_all(query, set: [decompiled: true]) do - {1, _} -> {:ok, []} - _ -> {:error, "There was an error annotating that the address has been decompiled."} - end - end - - defp clear_primary_address_names(repo, address_hash) do - query = - from( - address_name in Address.Name, - where: address_name.address_hash == ^address_hash, - # Enforce Name ShareLocks order (see docs: sharelocks.md) - order_by: [asc: :address_hash, asc: :name], - lock: "FOR NO KEY UPDATE" - ) - - repo.update_all( - from(n in Address.Name, join: s in subquery(query), on: n.address_hash == s.address_hash and n.name == s.name), - set: [primary: false] - ) - - {:ok, []} - end - - defp create_address_name(repo, name, address_hash) do - params = %{ - address_hash: address_hash, - name: name, - primary: true - } - - %Address.Name{} - |> Address.Name.changeset(params) - |> repo.insert(on_conflict: :nothing, conflict_target: [:address_hash, :name]) - end - - def get_verified_twin_contract(%Explorer.Chain.Address{} = target_address, options \\ []) do - case target_address do - %{contract_code: %Chain.Data{bytes: contract_code_bytes}} -> - target_address_hash = target_address.hash - - contract_code_md5 = Helper.contract_code_md5(contract_code_bytes) - - verified_contract_twin_query = - from( - smart_contract in SmartContract, - where: smart_contract.contract_code_md5 == ^contract_code_md5, - where: smart_contract.address_hash != ^target_address_hash, - select: smart_contract, - limit: 1 - ) - - verified_contract_twin_query - |> select_repo(options).one(timeout: 10_000) - - _ -> - nil - end - end - - @doc """ - Finds metadata for verification of a contract from verified twins: contracts with the same bytecode - which were verified previously, returns a single t:SmartContract.t/0 - """ - def get_address_verified_twin_contract(hash, options \\ []) - - def get_address_verified_twin_contract(hash, options) when is_binary(hash) do - case string_to_address_hash(hash) do - {:ok, address_hash} -> get_address_verified_twin_contract(address_hash, options) - _ -> %{:verified_contract => nil, :additional_sources => nil} - end - end - - def get_address_verified_twin_contract(%Explorer.Chain.Hash{} = address_hash, options) do - with target_address <- select_repo(options).get(Address, address_hash), - false <- is_nil(target_address) do - verified_contract_twin = get_verified_twin_contract(target_address, options) - - verified_contract_twin_additional_sources = get_contract_additional_sources(verified_contract_twin, options) - - %{ - :verified_contract => check_and_update_constructor_args(verified_contract_twin), - :additional_sources => verified_contract_twin_additional_sources - } - else - _ -> - %{:verified_contract => nil, :additional_sources => nil} - end - end - - def get_minimal_proxy_template(address_hash, options \\ []) do - minimal_proxy_template = - case select_repo(options).get(Address, address_hash) do - nil -> - nil - - target_address -> - contract_code = target_address.contract_code - - case contract_code do - %Chain.Data{bytes: contract_code_bytes} -> - contract_bytecode = Base.encode16(contract_code_bytes, case: :lower) - - get_minimal_proxy_from_template_code(contract_bytecode, options) - - _ -> - nil - end - end - - minimal_proxy_template - end - - defp get_minimal_proxy_from_template_code(contract_bytecode, options) do - case contract_bytecode do - "363d3d373d3d3d363d73" <> <> <> _ -> - template_address = "0x" <> template_address - - query = - from( - smart_contract in SmartContract, - where: smart_contract.address_hash == ^template_address, - select: smart_contract - ) - - template = - query - |> select_repo(options).one(timeout: 10_000) - - template - - _ -> - nil - end - end - - defp get_contract_additional_sources(verified_contract_twin, options) do - if verified_contract_twin do - verified_contract_twin_additional_sources_query = - from( - s in SmartContractAdditionalSource, - where: s.address_hash == ^verified_contract_twin.address_hash - ) - - verified_contract_twin_additional_sources_query - |> select_repo(options).all() - else - [] - end - end - - @spec address_hash_to_smart_contract(Hash.Address.t(), [api?]) :: SmartContract.t() | nil - def address_hash_to_smart_contract(address_hash, options \\ []) do - query = - from( - smart_contract in SmartContract, - where: smart_contract.address_hash == ^address_hash - ) - - current_smart_contract = select_repo(options).one(query) - - if current_smart_contract do - current_smart_contract - else - address_verified_twin_contract = - get_minimal_proxy_template(address_hash, options) || - get_address_verified_twin_contract(address_hash, options).verified_contract - - if address_verified_twin_contract do - address_verified_twin_contract - |> Map.put(:address_hash, address_hash) - |> Map.put(:metadata_from_verified_twin, true) - |> Map.put(:implementation_address_hash, nil) - |> Map.put(:implementation_name, nil) - |> Map.put(:implementation_fetched_at, nil) - else - current_smart_contract - end - end - end - - @spec address_hash_to_smart_contract_without_twin(Hash.Address.t(), [api?]) :: SmartContract.t() | nil - def address_hash_to_smart_contract_without_twin(address_hash, options) do - query = - from( - smart_contract in SmartContract, - where: smart_contract.address_hash == ^address_hash - ) - - select_repo(options).one(query) - end - - def smart_contract_fully_verified?(address_hash, options \\ []) - - def smart_contract_fully_verified?(address_hash_str, options) when is_binary(address_hash_str) do - case string_to_address_hash(address_hash_str) do - {:ok, address_hash} -> - check_fully_verified(address_hash, options) - - _ -> - false - end - end - - def smart_contract_fully_verified?(address_hash, options) do - check_fully_verified(address_hash, options) - end - - defp check_fully_verified(address_hash, options) do - query = - from( - smart_contract in SmartContract, - where: smart_contract.address_hash == ^address_hash - ) - - result = select_repo(options).one(query) - - if result, do: !result.partially_verified, else: false - end - - def smart_contract_verified?(address_hash_str) when is_binary(address_hash_str) do - case string_to_address_hash(address_hash_str) do - {:ok, address_hash} -> - check_verified(address_hash) - - _ -> - false - end - end - - def smart_contract_verified?(address_hash) do - check_verified(address_hash) - end - - defp check_verified(address_hash) do - query = - from( - smart_contract in SmartContract, - where: smart_contract.address_hash == ^address_hash - ) - - if Repo.one(query), do: true, else: false - end - defp fetch_transactions(paging_options \\ nil, from_block \\ nil, to_block \\ nil, with_pending? \\ false) do Transaction |> order_for_transactions(with_pending?) @@ -5457,36 +5007,6 @@ defmodule Explorer.Chain do Repo.exists?(query) end - @doc """ - Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the - `t:Explorer.Chain.Address.t/0` with the provided `hash`. - - Returns `:ok` if found and `:not_found` otherwise. - """ - @spec check_verified_smart_contract_exists(Hash.Address.t()) :: :ok | :not_found - def check_verified_smart_contract_exists(address_hash) do - address_hash - |> verified_smart_contract_exists?() - |> boolean_to_check_result() - end - - @doc """ - Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the - `t:Explorer.Chain.Address.t/0` with the provided `hash`. - - Returns `true` if found and `false` otherwise. - """ - @spec verified_smart_contract_exists?(Hash.Address.t()) :: boolean() - def verified_smart_contract_exists?(address_hash) do - query = - from( - smart_contract in SmartContract, - where: smart_contract.address_hash == ^address_hash - ) - - Repo.exists?(query) - end - @doc """ Checks if a `t:Explorer.Chain.Transaction.t/0` with the given `hash` exists. @@ -5647,9 +5167,9 @@ defmodule Explorer.Chain do Repo.exists?(query) end - defp boolean_to_check_result(true), do: :ok + def boolean_to_check_result(true), do: :ok - defp boolean_to_check_result(false), do: :not_found + def boolean_to_check_result(false), do: :not_found @doc """ Fetches the first trace from the Nethermind trace URL. @@ -5667,95 +5187,6 @@ defmodule Explorer.Chain do end end - def combine_proxy_implementation_abi(smart_contract, options \\ []) - - def combine_proxy_implementation_abi(%SmartContract{abi: abi} = smart_contract, options) when not is_nil(abi) do - implementation_abi = get_implementation_abi_from_proxy(smart_contract, options) - - if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi - end - - def combine_proxy_implementation_abi(_, _) do - [] - end - - def gnosis_safe_contract?(abi) when not is_nil(abi) do - implementation_method_abi = - abi - |> Enum.find(fn method -> - master_copy_pattern?(method) - end) - - if implementation_method_abi, do: true, else: false - end - - def gnosis_safe_contract?(abi) when is_nil(abi), do: false - - def master_copy_pattern?(method) do - Map.get(method, "type") == "constructor" && - method - |> Enum.find(fn item -> - case item do - {"inputs", inputs} -> - master_copy_input?(inputs) || singleton_input?(inputs) - - _ -> - false - end - end) - end - - defp master_copy_input?(inputs) do - inputs - |> Enum.find(fn input -> - Map.get(input, "name") == "_masterCopy" - end) - end - - defp singleton_input?(inputs) do - inputs - |> Enum.find(fn input -> - Map.get(input, "name") == "_singleton" - end) - end - - def get_implementation_abi(implementation_address_hash_string, options \\ []) - - def get_implementation_abi(implementation_address_hash_string, options) - when not is_nil(implementation_address_hash_string) do - case Chain.string_to_address_hash(implementation_address_hash_string) do - {:ok, implementation_address_hash} -> - implementation_smart_contract = - implementation_address_hash - |> address_hash_to_smart_contract(options) - - if implementation_smart_contract do - implementation_smart_contract - |> Map.get(:abi) - else - [] - end - - _ -> - [] - end - end - - def get_implementation_abi(implementation_address_hash_string, _) when is_nil(implementation_address_hash_string) do - [] - end - - def get_implementation_abi_from_proxy( - %SmartContract{address_hash: proxy_address_hash, abi: abi} = smart_contract, - options - ) - when not is_nil(proxy_address_hash) and not is_nil(abi) do - {implementation_address_hash_string, _name} = SmartContract.get_implementation_address_hash(smart_contract, options) - get_implementation_abi(implementation_address_hash_string) - end - - def get_implementation_abi_from_proxy(_, _), do: [] - defp format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) do {:ok, to_address_hash} = if Map.has_key?(first_trace, :to_address_hash) do @@ -5882,7 +5313,7 @@ defmodule Explorer.Chain do @spec get_token_transfer_type(TokenTransfer.t()) :: :token_burning | :token_minting | :token_spawning | :token_transfer def get_token_transfer_type(transfer) do - {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string()) + {:ok, burn_address_hash} = Chain.string_to_address_hash(SmartContract.burn_address_hash_string()) cond do transfer.to_address_hash == burn_address_hash && transfer.from_address_hash !== burn_address_hash -> @@ -6415,20 +5846,6 @@ defmodule Explorer.Chain do ) end - @spec verified_contracts_top(non_neg_integer()) :: [Hash.Address.t()] - def verified_contracts_top(limit) do - query = - from(contract in SmartContract, - inner_join: address in Address, - on: contract.address_hash == address.hash, - order_by: [desc: address.transactions_count], - limit: ^limit, - select: contract.address_hash - ) - - Repo.all(query) - end - @spec default_paging_options() :: map() def default_paging_options do @default_paging_options diff --git a/apps/explorer/lib/explorer/chain/address/name.ex b/apps/explorer/lib/explorer/chain/address/name.ex index 4364adf98795..91382eff6648 100644 --- a/apps/explorer/lib/explorer/chain/address/name.ex +++ b/apps/explorer/lib/explorer/chain/address/name.ex @@ -7,9 +7,11 @@ defmodule Explorer.Chain.Address.Name do import Ecto.Changeset - alias Ecto.Changeset + alias Ecto.{Changeset, Repo} alias Explorer.Chain.{Address, Hash} + import Ecto.Query, only: [from: 2] + @typedoc """ * `address` - the `t:Explorer.Chain.Address.t/0` with `value` at end of `block_number`. * `address_hash` - foreign key for `address`. @@ -46,6 +48,46 @@ defmodule Explorer.Chain.Address.Name do |> foreign_key_constraint(:address_hash) end + @doc """ + Sets primary false for all primary names for the given address hash + """ + @spec clear_primary_address_names(Repo.t(), Hash.Address.t()) :: {:ok, []} + def clear_primary_address_names(repo, address_hash) do + query = + from( + address_name in __MODULE__, + where: address_name.address_hash == ^address_hash, + where: address_name.primary == true, + # Enforce Name ShareLocks order (see docs: sharelocks.md) + order_by: [asc: :address_hash, asc: :name], + lock: "FOR NO KEY UPDATE" + ) + + repo.update_all( + from(n in __MODULE__, join: s in subquery(query), on: n.address_hash == s.address_hash and n.name == s.name), + set: [primary: false] + ) + + {:ok, []} + end + + @doc """ + Creates primary address name for the given address hash + """ + @spec create_primary_address_name(Repo.t(), String.t(), Hash.Address.t()) :: + {:ok, [__MODULE__.t()]} | {:error, [Changeset.t()]} + def create_primary_address_name(repo, name, address_hash) do + params = %{ + address_hash: address_hash, + name: name, + primary: true + } + + %__MODULE__{} + |> __MODULE__.changeset(params) + |> repo.insert(on_conflict: :nothing, conflict_target: [:address_hash, :name]) + end + defp trim_name(%Changeset{valid?: false} = changeset), do: changeset defp trim_name(%Changeset{valid?: true} = changeset) do diff --git a/apps/explorer/lib/explorer/chain/block/reward.ex b/apps/explorer/lib/explorer/chain/block/reward.ex index fd6296a550ea..92897cd80848 100644 --- a/apps/explorer/lib/explorer/chain/block/reward.ex +++ b/apps/explorer/lib/explorer/chain/block/reward.ex @@ -5,8 +5,6 @@ defmodule Explorer.Chain.Block.Reward do use Explorer.Schema - import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] - alias Explorer.Application.Constants alias Explorer.{Chain, PagingOptions} alias Explorer.Chain.Block.Reward.AddressType @@ -16,6 +14,8 @@ defmodule Explorer.Chain.Block.Reward do @required_attrs ~w(address_hash address_type block_hash reward)a + @burn_address_hash_string "0x0000000000000000000000000000000000000000" + @get_payout_by_mining_abi %{ "type" => "function", "stateMutability" => "view", @@ -252,6 +252,10 @@ defmodule Explorer.Chain.Block.Reward do end end + defp burn_address_hash_string do + @burn_address_hash_string + end + defp join_associations(query) do query |> preload(:address) diff --git a/apps/explorer/lib/explorer/chain/hash/full.ex b/apps/explorer/lib/explorer/chain/hash/full.ex index eb8ae148dd3a..db2a586bd770 100644 --- a/apps/explorer/lib/explorer/chain/hash/full.ex +++ b/apps/explorer/lib/explorer/chain/hash/full.ex @@ -90,7 +90,7 @@ defmodule Explorer.Chain.Hash.Full do ...> ) {:ok, <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>>} - If the field from the struct is an incorrect format such as `t:Explorer.Chain.Address.Hash.t/0`, `:error` is returned. + If the field from the struct is an incorrect format such as `t:Explorer.Chain.Hash.Address.t/0`, `:error` is returned. iex> Explorer.Chain.Hash.Full.dump( ...> %Explorer.Chain.Hash{ diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 3ee92d04240b..3b04958fdcb7 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -8,6 +8,7 @@ defmodule Explorer.Chain.Log do alias ABI.{Event, FunctionSelector} alias Explorer.Chain alias Explorer.Chain.{Address, Block, ContractMethod, Data, Hash, Transaction} + alias Explorer.Chain.SmartContract.Proxy alias Explorer.SmartContract.SigProviderInterface @required_attrs ~w(address_hash data block_hash index transaction_hash)a @@ -165,7 +166,7 @@ defmodule Explorer.Chain.Log do else case Chain.find_contract_address(address_hash, address_options, false) do {:ok, %{smart_contract: smart_contract}} -> - full_abi = Chain.combine_proxy_implementation_abi(smart_contract, options) + full_abi = Proxy.combine_proxy_implementation_abi(smart_contract, options) {full_abi, Map.put(acc, address_hash, full_abi)} _ -> diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index 5391349acf09..ea91857a1a83 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -12,26 +12,35 @@ defmodule Explorer.Chain.SmartContract do use Explorer.Schema - alias Ecto.Changeset - alias EthereumJSONRPC.Contract + alias Ecto.{Changeset, Multi} alias Explorer.Counters.AverageBlockTime alias Explorer.{Chain, Repo} - alias Explorer.Chain.{Address, ContractMethod, Data, DecompiledSmartContract, Hash, InternalTransaction, Transaction} - alias Explorer.Chain.SmartContract.ExternalLibrary - alias Explorer.SmartContract.Reader + + alias Explorer.Chain.{ + Address, + ContractMethod, + Data, + DecompiledSmartContract, + Hash, + InternalTransaction, + SmartContractAdditionalSource, + Transaction + } + + alias Explorer.Chain.Address.Name, as: AddressName + + alias Explorer.Chain.SmartContract.{ExternalLibrary, Proxy} + alias Explorer.Chain.SmartContract.Proxy.EIP1167 + alias Explorer.SmartContract.Helper + alias Explorer.SmartContract.Solidity.Verifier alias Timex.Duration - # supported signatures: - # 5c60da1b = keccak256(implementation()) - @implementation_signature "5c60da1b" - # aaf10f42 = keccak256(getImplementation()) - @get_implementation_signature "aaf10f42" + @typep api? :: {:api?, true | false} @burn_address_hash_string "0x0000000000000000000000000000000000000000" @burn_address_hash_string_32 "0x0000000000000000000000000000000000000000000000000000000000000000" defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string_32] - defguard is_burn_signature_or_nil(term) when is_burn_signature(term) or term == nil defguard is_burn_signature_extended(term) when is_burn_signature(term) or term == @burn_address_hash_string @doc """ @@ -42,8 +51,6 @@ defmodule Explorer.Chain.SmartContract do @burn_address_hash_string end - @typep api? :: {:api?, true | false} - @typedoc """ The name of a parameter to a function or event. """ @@ -427,79 +434,6 @@ defmodule Explorer.Chain.SmartContract do end end - defp upsert_contract_methods(%Changeset{changes: %{abi: abi}} = changeset) do - ContractMethod.upsert_from_abi(abi, get_field(changeset, :address_hash)) - - changeset - rescue - exception -> - message = Exception.format(:error, exception, __STACKTRACE__) - - Logger.error(fn -> ["Error while upserting contract methods: ", message] end) - - changeset - end - - defp upsert_contract_methods(changeset), do: changeset - - defp error_message(:compilation), do: error_message_with_log("There was an error compiling your contract.") - - defp error_message(:compiler_version), - do: error_message_with_log("Compiler version does not match, please try again.") - - defp error_message(:generated_bytecode), do: error_message_with_log("Bytecode does not match, please try again.") - - defp error_message(:constructor_arguments), - do: error_message_with_log("Constructor arguments do not match, please try again.") - - defp error_message(:name), do: error_message_with_log("Wrong contract name, please try again.") - defp error_message(:json), do: error_message_with_log("Invalid JSON file.") - - defp error_message(:autodetect_constructor_arguments_failed), - do: - error_message_with_log( - "Autodetection of constructor arguments failed. Please try to input constructor arguments manually." - ) - - defp error_message(:no_creation_data), - do: - error_message_with_log( - "The contract creation transaction has not been indexed yet. Please wait a few minutes and try again." - ) - - defp error_message(:unknown_error), do: error_message_with_log("Unable to verify: unknown error.") - - defp error_message(:deployed_bytecode), - do: error_message_with_log("Deployed bytecode does not correspond to contract creation code.") - - defp error_message(:contract_source_code), do: error_message_with_log("Empty contract source code.") - - defp error_message(string) when is_binary(string), do: error_message_with_log(string) - defp error_message(%{"message" => string} = error) when is_map(error), do: error_message_with_log(string) - - defp error_message(error) do - Logger.warn(fn -> ["Unknown verifier error: ", inspect(error)] end) - "There was an error validating your contract, please try again." - end - - defp error_message(:compilation, error_message), - do: error_message_with_log("There was an error compiling your contract: #{error_message}") - - defp error_message_with_log(error_string) do - Logger.error("Smart-contract verification error: #{error_string}") - error_string - end - - defp select_error_field(:no_creation_data), do: :address_hash - defp select_error_field(:compiler_version), do: :compiler_version - - defp select_error_field(constructor_arguments) - when constructor_arguments in [:constructor_arguments, :autodetect_constructor_arguments_failed], - do: :constructor_arguments - - defp select_error_field(:name), do: :name - defp select_error_field(_), do: :contract_source_code - def merge_twin_contract_with_changeset(%__MODULE__{} = twin_contract, %Changeset{} = changeset) do %__MODULE__{} |> changeset(Map.from_struct(twin_contract)) @@ -544,6 +478,10 @@ defmodule Explorer.Chain.SmartContract do |> Changeset.put_change(:contract_source_code, "") end + @doc """ + Returns smart-contract changeset with checksummed address hash + """ + @spec address_to_checksum_address(Changeset.t()) :: Changeset.t() def address_to_checksum_address(changeset) do checksum_address = changeset @@ -554,36 +492,10 @@ defmodule Explorer.Chain.SmartContract do Changeset.force_change(changeset, :address_hash, checksum_address) end - defp to_address_hash(string) when is_binary(string) do - {:ok, address_hash} = Chain.string_to_address_hash(string) - address_hash - end - - defp to_address_hash(address_hash), do: address_hash - - def proxy_contract?(smart_contract, options \\ []) - - def proxy_contract?(%__MODULE__{abi: abi} = smart_contract, options) when not is_nil(abi) do - implementation_method_abi = - abi - |> Enum.find(fn method -> - Map.get(method, "name") == "implementation" || - Chain.master_copy_pattern?(method) - end) - - if implementation_method_abi || - not is_nil( - smart_contract - |> get_implementation_address_hash(options) - |> Tuple.to_list() - |> List.first() - ), - do: true, - else: false - end - - def proxy_contract?(_, _), do: false - + @doc """ + Returns implementation address and name of the given SmartContract by hash address + """ + @spec get_implementation_address_hash(any(), any()) :: {any(), any()} def get_implementation_address_hash(smart_contract, options \\ []) def get_implementation_address_hash(%__MODULE__{abi: nil}, _), do: {nil, nil} @@ -602,7 +514,7 @@ defmodule Explorer.Chain.SmartContract do updated_smart_contract = if Application.get_env(:explorer, :enable_caching_implementation_data_of_proxy) && check_implementation_refetch_necessity(implementation_fetched_at) do - Chain.address_hash_to_smart_contract_without_twin(address_hash, options) + address_hash_to_smart_contract_without_twin(address_hash, options) else smart_contract end @@ -624,7 +536,9 @@ defmodule Explorer.Chain.SmartContract do ) do if check_implementation_refetch_necessity(implementation_fetched_at) do get_implementation_address_hash_task = - Task.async(fn -> get_implementation_address_hash(address_hash, abi, metadata_from_verified_twin, options) end) + Task.async(fn -> + Proxy.fetch_implementation_address_hash(address_hash, abi, metadata_from_verified_twin, options) + end) timeout = Application.get_env(:explorer, :implementation_data_fetching_timeout) @@ -648,236 +562,13 @@ defmodule Explorer.Chain.SmartContract do def get_implementation_address_hash(_, _), do: {nil, nil} - defp db_implementation_data_converter(nil), do: nil - defp db_implementation_data_converter(string) when is_binary(string), do: string - defp db_implementation_data_converter(other), do: to_string(other) - - defp check_implementation_refetch_necessity(nil), do: true - - defp check_implementation_refetch_necessity(timestamp) do - if Application.get_env(:explorer, :enable_caching_implementation_data_of_proxy) do - now = DateTime.utc_now() - - average_block_time = get_average_block_time() - - fresh_time_distance = - case average_block_time do - 0 -> - Application.get_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy) - - time -> - round(time) - end - - timestamp - |> DateTime.add(fresh_time_distance, :millisecond) - |> DateTime.compare(now) != :gt - else - true - end - end - - defp get_average_block_time do - if Application.get_env(:explorer, :avg_block_time_as_ttl_cached_implementation_data_of_proxy) do - case AverageBlockTime.average_block_time() do - {:error, :disabled} -> - 0 - - duration -> - duration - |> Duration.to_milliseconds() - end - else - 0 - end - end - - @spec get_implementation_address_hash(Hash.Address.t(), list(), boolean() | nil, [api?]) :: - {String.t() | nil, String.t() | nil} - defp get_implementation_address_hash(proxy_address_hash, abi, metadata_from_verified_twin, options) - when not is_nil(proxy_address_hash) and not is_nil(abi) do - implementation_method_abi = - abi - |> Enum.find(fn method -> - Map.get(method, "name") == "implementation" && Map.get(method, "stateMutability") == "view" - end) - - get_implementation_method_abi = - abi - |> Enum.find(fn method -> - Map.get(method, "name") == "getImplementation" && Map.get(method, "stateMutability") == "view" - end) - - master_copy_method_abi = - abi - |> Enum.find(fn method -> - Chain.master_copy_pattern?(method) - end) - - implementation_address = - cond do - implementation_method_abi -> - get_implementation_address_hash_basic(@implementation_signature, proxy_address_hash, abi) - - get_implementation_method_abi -> - get_implementation_address_hash_basic(@get_implementation_signature, proxy_address_hash, abi) - - master_copy_method_abi -> - get_implementation_address_hash_from_master_copy_pattern(proxy_address_hash) - - true -> - get_implementation_address_hash_eip_1967(proxy_address_hash) - end - - save_implementation_data(implementation_address, proxy_address_hash, metadata_from_verified_twin, options) - end - - defp get_implementation_address_hash(_proxy_address_hash, _abi, _, _) do - {nil, nil} - end - - defp get_implementation_address_hash_eip_1967(proxy_address_hash) do - json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) - - # https://eips.ethereum.org/EIPS/eip-1967 - storage_slot_logic_contract_address = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" - - {_status, implementation_address} = - case Contract.eth_get_storage_at_request( - proxy_address_hash, - storage_slot_logic_contract_address, - nil, - json_rpc_named_arguments - ) do - {:ok, empty_address} - when is_burn_signature_or_nil(empty_address) -> - fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) - - {:ok, implementation_logic_address} -> - {:ok, implementation_logic_address} - - _ -> - {:ok, nil} - end - - abi_decode_address_output(implementation_address) - end - - # changes requested by https://github.com/blockscout/blockscout/issues/4770 - # for support BeaconProxy pattern - defp fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) do - # https://eips.ethereum.org/EIPS/eip-1967 - storage_slot_beacon_contract_address = "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50" - - implementation_method_abi = [ - %{ - "type" => "function", - "stateMutability" => "view", - "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}], - "name" => "implementation", - "inputs" => [] - } - ] - - case Contract.eth_get_storage_at_request( - proxy_address_hash, - storage_slot_beacon_contract_address, - nil, - json_rpc_named_arguments - ) do - {:ok, empty_address} - when is_burn_signature_or_nil(empty_address) -> - fetch_openzeppelin_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) - - {:ok, beacon_contract_address} -> - case beacon_contract_address - |> abi_decode_address_output() - |> get_implementation_address_hash_basic(@implementation_signature, implementation_method_abi) do - <> -> - {:ok, implementation_address} - - _ -> - {:ok, beacon_contract_address} - end - - _ -> - {:ok, nil} - end - end - - # changes requested by https://github.com/blockscout/blockscout/issues/5292 - defp fetch_openzeppelin_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) do - # This is the keccak-256 hash of "org.zeppelinos.proxy.implementation" - storage_slot_logic_contract_address = "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3" - - case Contract.eth_get_storage_at_request( - proxy_address_hash, - storage_slot_logic_contract_address, - nil, - json_rpc_named_arguments - ) do - {:ok, empty_address} - when is_burn_signature(empty_address) -> - {:ok, "0x"} - - {:ok, logic_contract_address} -> - {:ok, logic_contract_address} - - _ -> - {:ok, nil} - end - end - - defp get_implementation_address_hash_basic(signature, proxy_address_hash, abi) do - implementation_address = - case Reader.query_contract( - proxy_address_hash, - abi, - %{ - "#{signature}" => [] - }, - false - ) do - %{^signature => {:ok, [result]}} -> result - _ -> nil - end - - address_to_hex(implementation_address) - end - - defp get_implementation_address_hash_from_master_copy_pattern(proxy_address_hash) do - json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + def save_implementation_data(nil, _, _, _), do: {nil, nil} - master_copy_storage_pointer = "0x0" - - {:ok, implementation_address} = - case Contract.eth_get_storage_at_request( - proxy_address_hash, - master_copy_storage_pointer, - nil, - json_rpc_named_arguments - ) do - {:ok, empty_address} - when is_burn_signature(empty_address) -> - {:ok, "0x"} - - {:ok, logic_contract_address} -> - {:ok, logic_contract_address} - - _ -> - {:ok, nil} - end - - abi_decode_address_output(implementation_address) - end - - defp save_implementation_data(nil, _, _, _), do: {nil, nil} - - defp save_implementation_data(empty_address_hash_string, proxy_address_hash, metadata_from_verified_twin, options) - when is_burn_signature_extended(empty_address_hash_string) do + def save_implementation_data(empty_address_hash_string, proxy_address_hash, metadata_from_verified_twin, options) + when is_burn_signature_extended(empty_address_hash_string) do if is_nil(metadata_from_verified_twin) or !metadata_from_verified_twin do proxy_address_hash - |> Chain.address_hash_to_smart_contract_without_twin(options) + |> address_hash_to_smart_contract_without_twin(options) |> changeset(%{ implementation_name: nil, implementation_address_hash: nil, @@ -889,13 +580,13 @@ defmodule Explorer.Chain.SmartContract do {:empty, :empty} end - defp save_implementation_data(implementation_address_hash_string, proxy_address_hash, _, options) - when is_binary(implementation_address_hash_string) do + def save_implementation_data(implementation_address_hash_string, proxy_address_hash, _, options) + when is_binary(implementation_address_hash_string) do with {:ok, address_hash} <- Chain.string_to_address_hash(implementation_address_hash_string), - proxy_contract <- Chain.address_hash_to_smart_contract_without_twin(proxy_address_hash, options), + proxy_contract <- address_hash_to_smart_contract_without_twin(proxy_address_hash, options), false <- is_nil(proxy_contract), %{implementation: %__MODULE__{name: name}, proxy: proxy_contract} <- %{ - implementation: Chain.address_hash_to_smart_contract(address_hash, options), + implementation: address_hash_to_smart_contract(address_hash, options), proxy: proxy_contract } do proxy_contract @@ -921,7 +612,7 @@ defmodule Explorer.Chain.SmartContract do true -> {:ok, address_hash} = Chain.string_to_address_hash(implementation_address_hash_string) - smart_contract = Chain.address_hash_to_smart_contract(address_hash, options) + smart_contract = address_hash_to_smart_contract(address_hash, options) {implementation_address_hash_string, smart_contract && smart_contract.name} @@ -930,30 +621,9 @@ defmodule Explorer.Chain.SmartContract do end end - defp address_to_hex(address) do - if address do - if String.starts_with?(address, "0x") do - address - else - "0x" <> Base.encode16(address, case: :lower) - end - end - end - - defp abi_decode_address_output(nil), do: nil - - defp abi_decode_address_output("0x"), do: burn_address_hash_string() - - defp abi_decode_address_output(address) when is_binary(address) do - if String.length(address) > 42 do - "0x" <> String.slice(address, -40, 40) - else - address - end - end - - defp abi_decode_address_output(_), do: nil - + @doc """ + Returns SmartContract by the given smart-contract address hash, if it is partially verified + """ @spec select_partially_verified_by_address_hash(binary() | Hash.t(), keyword) :: boolean() | nil def select_partially_verified_by_address_hash(address_hash, options \\ []) do query = @@ -1009,4 +679,549 @@ defmodule Explorer.Chain.SmartContract do end end end + + @doc """ + Composes address object for smart-contract + """ + @spec compose_smart_contract(map(), Explorer.Chain.Hash.t(), any()) :: map() + def compose_smart_contract(address_result, hash, options) do + address_verified_twin_contract = + EIP1167.get_implementation_address(hash, options) || + get_address_verified_twin_contract(hash, options).verified_contract + + if address_verified_twin_contract do + address_verified_twin_contract_updated = + address_verified_twin_contract + |> Map.put(:address_hash, hash) + |> Map.put(:metadata_from_verified_twin, true) + |> Map.put(:implementation_address_hash, nil) + |> Map.put(:implementation_name, nil) + |> Map.put(:implementation_fetched_at, nil) + + address_result + |> Map.put(:smart_contract, address_verified_twin_contract_updated) + else + address_result + end + end + + @doc """ + Finds metadata for verification of a contract from verified twins: contracts with the same bytecode + which were verified previously, returns a single t:SmartContract.t/0 + """ + def get_address_verified_twin_contract(hash, options \\ []) + + def get_address_verified_twin_contract(hash, options) when is_binary(hash) do + case Chain.string_to_address_hash(hash) do + {:ok, address_hash} -> get_address_verified_twin_contract(address_hash, options) + _ -> %{:verified_contract => nil, :additional_sources => nil} + end + end + + def get_address_verified_twin_contract(%Explorer.Chain.Hash{} = address_hash, options) do + with target_address <- Chain.select_repo(options).get(Address, address_hash), + false <- is_nil(target_address) do + verified_contract_twin = get_verified_twin_contract(target_address, options) + + verified_contract_twin_additional_sources = + SmartContractAdditionalSource.get_contract_additional_sources(verified_contract_twin, options) + + %{ + :verified_contract => check_and_update_constructor_args(verified_contract_twin), + :additional_sources => verified_contract_twin_additional_sources + } + else + _ -> + %{:verified_contract => nil, :additional_sources => nil} + end + end + + def get_verified_twin_contract(%Explorer.Chain.Address{} = target_address, options \\ []) do + case target_address do + %{contract_code: %Chain.Data{bytes: contract_code_bytes}} -> + target_address_hash = target_address.hash + + contract_code_md5 = Helper.contract_code_md5(contract_code_bytes) + + verified_contract_twin_query = + from( + smart_contract in __MODULE__, + where: smart_contract.contract_code_md5 == ^contract_code_md5, + where: smart_contract.address_hash != ^target_address_hash, + select: smart_contract, + limit: 1 + ) + + verified_contract_twin_query + |> Chain.select_repo(options).one(timeout: 10_000) + + _ -> + nil + end + end + + def check_and_update_constructor_args( + %__MODULE__{address_hash: address_hash, constructor_arguments: nil, verified_via_sourcify: true} = + smart_contract + ) do + if args = Verifier.parse_constructor_arguments_for_sourcify_contract(address_hash, smart_contract.abi) do + smart_contract |> __MODULE__.changeset(%{constructor_arguments: args}) |> Repo.update() + %__MODULE__{smart_contract | constructor_arguments: args} + else + smart_contract + end + end + + def check_and_update_constructor_args( + %Address{ + hash: address_hash, + contract_code: deployed_bytecode, + smart_contract: %__MODULE__{constructor_arguments: nil, verified_via_sourcify: true} = smart_contract + } = address + ) do + if args = + Verifier.parse_constructor_arguments_for_sourcify_contract(address_hash, smart_contract.abi, deployed_bytecode) do + smart_contract |> __MODULE__.changeset(%{constructor_arguments: args}) |> Repo.update() + %Address{address | smart_contract: %__MODULE__{smart_contract | constructor_arguments: args}} + else + address + end + end + + def check_and_update_constructor_args(other), do: other + + @doc """ + Adds verified metadata from bytecode twin smart-contract to the given smart-contract + """ + @spec add_twin_info_to_contract(map(), Chain.Hash.t(), Chain.Hash.t()) :: map() + def add_twin_info_to_contract(address_result, address_verified_twin_contract, _hash) + when is_nil(address_verified_twin_contract), + do: address_result + + def add_twin_info_to_contract(address_result, address_verified_twin_contract, hash) do + address_verified_twin_contract_updated = + address_verified_twin_contract + |> Map.put(:address_hash, hash) + |> Map.put(:metadata_from_verified_twin, true) + |> Map.put(:implementation_address_hash, nil) + |> Map.put(:implementation_name, nil) + |> Map.put(:implementation_fetched_at, nil) + + address_result + |> Map.put(:smart_contract, address_verified_twin_contract_updated) + end + + @doc """ + Inserts a `t:SmartContract.t/0`. + + As part of inserting a new smart contract, an additional record is inserted for + naming the address for reference. + """ + @spec create_smart_contract(map()) :: {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()} + def create_smart_contract(attrs \\ %{}, external_libraries \\ [], secondary_sources \\ []) do + new_contract = %__MODULE__{} + + attrs = + attrs + |> Helper.add_contract_code_md5() + + smart_contract_changeset = + new_contract + |> __MODULE__.changeset(attrs) + |> Changeset.put_change(:external_libraries, external_libraries) + + new_contract_additional_source = %SmartContractAdditionalSource{} + + smart_contract_additional_sources_changesets = + if secondary_sources do + secondary_sources + |> Enum.map(fn changeset -> + new_contract_additional_source + |> SmartContractAdditionalSource.changeset(changeset) + end) + else + [] + end + + address_hash = Changeset.get_field(smart_contract_changeset, :address_hash) + + # Enforce ShareLocks tables order (see docs: sharelocks.md) + insert_contract_query = + Multi.new() + |> Multi.run(:set_address_verified, fn repo, _ -> set_address_verified(repo, address_hash) end) + |> Multi.run(:clear_primary_address_names, fn repo, _ -> + AddressName.clear_primary_address_names(repo, address_hash) + end) + |> Multi.insert(:smart_contract, smart_contract_changeset) + + insert_contract_query_with_additional_sources = + smart_contract_additional_sources_changesets + |> Enum.with_index() + |> Enum.reduce(insert_contract_query, fn {changeset, index}, multi -> + Multi.insert(multi, "smart_contract_additional_source_#{Integer.to_string(index)}", changeset) + end) + + insert_result = + insert_contract_query_with_additional_sources + |> Repo.transaction() + + AddressName.create_primary_address_name(Repo, Changeset.get_field(smart_contract_changeset, :name), address_hash) + + case insert_result do + {:ok, %{smart_contract: smart_contract}} -> + {:ok, smart_contract} + + {:error, :smart_contract, changeset, _} -> + {:error, changeset} + + {:error, :set_address_verified, message, _} -> + {:error, message} + end + end + + @doc """ + Updates a `t:SmartContract.t/0`. + + Has the similar logic as create_smart_contract/1. + Used in cases when you need to update row in DB contains SmartContract, e.g. in case of changing + status `partially verified` to `fully verified` (re-verify). + """ + @spec update_smart_contract(map()) :: {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()} + def update_smart_contract(attrs \\ %{}, external_libraries \\ [], secondary_sources \\ []) do + address_hash = Map.get(attrs, :address_hash) + + query_sources = + from( + source in SmartContractAdditionalSource, + where: source.address_hash == ^address_hash + ) + + _delete_sources = Repo.delete_all(query_sources) + + query = get_smart_contract_query(address_hash) + smart_contract = Repo.one(query) + + smart_contract_changeset = + smart_contract + |> __MODULE__.changeset(attrs) + |> Changeset.put_change(:external_libraries, external_libraries) + + new_contract_additional_source = %SmartContractAdditionalSource{} + + smart_contract_additional_sources_changesets = + if secondary_sources do + secondary_sources + |> Enum.map(fn changeset -> + new_contract_additional_source + |> SmartContractAdditionalSource.changeset(changeset) + end) + else + [] + end + + # Enforce ShareLocks tables order (see docs: sharelocks.md) + insert_contract_query = + Multi.new() + |> Multi.run(:clear_primary_address_names, fn repo, _ -> + AddressName.clear_primary_address_names(repo, address_hash) + end) + |> Multi.update(:smart_contract, smart_contract_changeset) + + insert_contract_query_with_additional_sources = + smart_contract_additional_sources_changesets + |> Enum.with_index() + |> Enum.reduce(insert_contract_query, fn {changeset, index}, multi -> + Multi.insert(multi, "smart_contract_additional_source_#{Integer.to_string(index)}", changeset) + end) + + insert_result = + insert_contract_query_with_additional_sources + |> Repo.transaction() + + AddressName.create_primary_address_name(Repo, Changeset.get_field(smart_contract_changeset, :name), address_hash) + + case insert_result do + {:ok, %{smart_contract: smart_contract}} -> + {:ok, smart_contract} + + {:error, :smart_contract, changeset, _} -> + {:error, changeset} + + {:error, :set_address_verified, message, _} -> + {:error, message} + end + end + + @doc """ + Converts address hash to smart-contract object + """ + @spec address_hash_to_smart_contract_without_twin(Hash.Address.t(), [api?]) :: __MODULE__.t() | nil + def address_hash_to_smart_contract_without_twin(address_hash, options) do + query = get_smart_contract_query(address_hash) + + Chain.select_repo(options).one(query) + end + + @doc """ + Converts address hash to smart-contract object with metadata_from_verified_twin=true + """ + @spec address_hash_to_smart_contract(Hash.Address.t(), [api?]) :: __MODULE__.t() | nil + def address_hash_to_smart_contract(address_hash, options \\ []) do + current_smart_contract = address_hash_to_smart_contract_without_twin(address_hash, options) + + with true <- is_nil(current_smart_contract), + address_verified_twin_contract = + EIP1167.get_implementation_address(address_hash, options) || + get_address_verified_twin_contract(address_hash, options).verified_contract, + false <- is_nil(address_verified_twin_contract) do + address_verified_twin_contract + |> Map.put(:address_hash, address_hash) + |> Map.put(:metadata_from_verified_twin, true) + |> Map.put(:implementation_address_hash, nil) + |> Map.put(:implementation_name, nil) + |> Map.put(:implementation_fetched_at, nil) + else + _ -> + current_smart_contract + end + end + + @doc """ + Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the + `t:Explorer.Chain.Address.t/0` with the provided `hash` and `partially_verified` property is not true. + + Returns `true` if found and `false` otherwise. + """ + @spec verified_with_full_match?(any()) :: boolean() + def verified_with_full_match?(address_hash, options \\ []) + + def verified_with_full_match?(address_hash_str, options) when is_binary(address_hash_str) do + case Chain.string_to_address_hash(address_hash_str) do + {:ok, address_hash} -> + check_verified_with_full_match(address_hash, options) + + _ -> + false + end + end + + def verified_with_full_match?(address_hash, options) do + check_verified_with_full_match(address_hash, options) + end + + @doc """ + Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the + `t:Explorer.Chain.Address.t/0` with the provided `hash`. + + Returns `true` if found and `false` otherwise. + """ + @spec verified?(any()) :: boolean() + def verified?(address_hash_str) when is_binary(address_hash_str) do + case Chain.string_to_address_hash(address_hash_str) do + {:ok, address_hash} -> + verified_smart_contract_exists?(address_hash) + + _ -> + false + end + end + + def verified?(address_hash) do + verified_smart_contract_exists?(address_hash) + end + + @doc """ + Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the + `t:Explorer.Chain.Address.t/0` with the provided `hash`. + + Returns `:ok` if found and `:not_found` otherwise. + """ + @spec check_verified_smart_contract_exists(Hash.Address.t()) :: :ok | :not_found + def check_verified_smart_contract_exists(address_hash) do + address_hash + |> verified_smart_contract_exists?() + |> Chain.boolean_to_check_result() + end + + @doc """ + Gets smart-contract ABI from the DB for the given address hash of smart-contract + """ + @spec get_smart_contract_abi(any(), any()) :: any() + def get_smart_contract_abi(address_hash_string, options \\ []) + + def get_smart_contract_abi(address_hash_string, options) + when not is_nil(address_hash_string) do + with {:ok, implementation_address_hash} <- Chain.string_to_address_hash(address_hash_string), + implementation_smart_contract = + implementation_address_hash + |> __MODULE__.address_hash_to_smart_contract(options), + false <- is_nil(implementation_smart_contract) do + implementation_smart_contract + |> Map.get(:abi) + else + _ -> + [] + end + end + + def get_smart_contract_abi(address_hash_string, _) when is_nil(address_hash_string) do + [] + end + + defp upsert_contract_methods(%Changeset{changes: %{abi: abi}} = changeset) do + ContractMethod.upsert_from_abi(abi, get_field(changeset, :address_hash)) + + changeset + rescue + exception -> + message = Exception.format(:error, exception, __STACKTRACE__) + + Logger.error(fn -> ["Error while upserting contract methods: ", message] end) + + changeset + end + + defp upsert_contract_methods(changeset), do: changeset + + defp error_message(:compilation), do: error_message_with_log("There was an error compiling your contract.") + + defp error_message(:compiler_version), + do: error_message_with_log("Compiler version does not match, please try again.") + + defp error_message(:generated_bytecode), do: error_message_with_log("Bytecode does not match, please try again.") + + defp error_message(:constructor_arguments), + do: error_message_with_log("Constructor arguments do not match, please try again.") + + defp error_message(:name), do: error_message_with_log("Wrong contract name, please try again.") + defp error_message(:json), do: error_message_with_log("Invalid JSON file.") + + defp error_message(:autodetect_constructor_arguments_failed), + do: + error_message_with_log( + "Autodetection of constructor arguments failed. Please try to input constructor arguments manually." + ) + + defp error_message(:no_creation_data), + do: + error_message_with_log( + "The contract creation transaction has not been indexed yet. Please wait a few minutes and try again." + ) + + defp error_message(:unknown_error), do: error_message_with_log("Unable to verify: unknown error.") + + defp error_message(:deployed_bytecode), + do: error_message_with_log("Deployed bytecode does not correspond to contract creation code.") + + defp error_message(:contract_source_code), do: error_message_with_log("Empty contract source code.") + + defp error_message(string) when is_binary(string), do: error_message_with_log(string) + defp error_message(%{"message" => string} = error) when is_map(error), do: error_message_with_log(string) + + defp error_message(error) do + Logger.warn(fn -> ["Unknown verifier error: ", inspect(error)] end) + "There was an error validating your contract, please try again." + end + + defp error_message(:compilation, error_message), + do: error_message_with_log("There was an error compiling your contract: #{error_message}") + + defp error_message_with_log(error_string) do + Logger.error("Smart-contract verification error: #{error_string}") + error_string + end + + defp select_error_field(:no_creation_data), do: :address_hash + defp select_error_field(:compiler_version), do: :compiler_version + + defp select_error_field(constructor_arguments) + when constructor_arguments in [:constructor_arguments, :autodetect_constructor_arguments_failed], + do: :constructor_arguments + + defp select_error_field(:name), do: :name + defp select_error_field(_), do: :contract_source_code + + defp to_address_hash(string) when is_binary(string) do + {:ok, address_hash} = Chain.string_to_address_hash(string) + address_hash + end + + defp to_address_hash(address_hash), do: address_hash + + defp db_implementation_data_converter(nil), do: nil + defp db_implementation_data_converter(string) when is_binary(string), do: string + defp db_implementation_data_converter(other), do: to_string(other) + + defp check_implementation_refetch_necessity(nil), do: true + + defp check_implementation_refetch_necessity(timestamp) do + if Application.get_env(:explorer, :enable_caching_implementation_data_of_proxy) do + now = DateTime.utc_now() + + average_block_time = get_average_block_time() + + fresh_time_distance = + case average_block_time do + 0 -> + Application.get_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy) + + time -> + round(time) + end + + timestamp + |> DateTime.add(fresh_time_distance, :millisecond) + |> DateTime.compare(now) != :gt + else + true + end + end + + defp get_average_block_time do + if Application.get_env(:explorer, :avg_block_time_as_ttl_cached_implementation_data_of_proxy) do + case AverageBlockTime.average_block_time() do + {:error, :disabled} -> + 0 + + duration -> + duration + |> Duration.to_milliseconds() + end + else + 0 + end + end + + @spec verified_smart_contract_exists?(Hash.Address.t()) :: boolean() + defp verified_smart_contract_exists?(address_hash) do + query = get_smart_contract_query(address_hash) + + Repo.exists?(query) + end + + defp set_address_verified(repo, address_hash) do + query = + from( + address in Address, + where: address.hash == ^address_hash + ) + + case repo.update_all(query, set: [verified: true]) do + {1, _} -> {:ok, []} + _ -> {:error, "There was an error annotating that the address has been verified."} + end + end + + defp get_smart_contract_query(address_hash) do + from( + smart_contract in __MODULE__, + where: smart_contract.address_hash == ^address_hash + ) + end + + defp check_verified_with_full_match(address_hash, options) do + smart_contract = address_hash_to_smart_contract_without_twin(address_hash, options) + + if smart_contract, do: !smart_contract.partially_verified, else: false + end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex new file mode 100644 index 000000000000..fc694d8e5630 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -0,0 +1,230 @@ +defmodule Explorer.Chain.SmartContract.Proxy do + @moduledoc """ + Module for proxy smart-contract implementation detection + """ + + alias EthereumJSONRPC.Contract + alias Explorer.Chain.{Hash, SmartContract} + alias Explorer.Chain.SmartContract.Proxy + alias Explorer.Chain.SmartContract.Proxy.{Basic, EIP1167, EIP1822, EIP1967, EIP930, MasterCopy} + + import Explorer.Chain, + only: [ + string_to_address_hash: 1 + ] + + # supported signatures: + # 5c60da1b = keccak256(implementation()) + @implementation_signature "5c60da1b" + # aaf10f42 = keccak256(getImplementation()) + @get_implementation_signature "aaf10f42" + # aaf10f42 = keccak256(getAddress(bytes32)) + @get_address_signature "21f8a721" + + @burn_address_hash_string_32 "0x0000000000000000000000000000000000000000000000000000000000000000" + + @typep api? :: {:api?, true | false} + + defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string_32] + defguard is_burn_signature_or_nil(term) when is_burn_signature(term) or term == nil + + @doc """ + Fetches into DB proxy contract implementation's address and name from different proxy patterns + """ + @spec fetch_implementation_address_hash(Hash.Address.t(), list(), boolean() | nil, [api?]) :: + {String.t() | nil, String.t() | nil} + def fetch_implementation_address_hash(proxy_address_hash, proxy_abi, metadata_from_verified_twin, options) + when not is_nil(proxy_address_hash) and not is_nil(proxy_abi) do + implementation_address_hash_string = get_implementation_address_hash_string(proxy_address_hash, proxy_abi) + + {:ok, burn_address_hash} = string_to_address_hash(SmartContract.burn_address_hash_string()) + + with false <- is_nil(implementation_address_hash_string), + {:ok, implementation_address_hash} <- string_to_address_hash(implementation_address_hash_string), + false <- implementation_address_hash.bytes == burn_address_hash.bytes do + SmartContract.save_implementation_data( + implementation_address_hash_string, + proxy_address_hash, + metadata_from_verified_twin, + options + ) + else + _ -> + {nil, nil} + end + end + + def fetch_implementation_address_hash(_, _, _, _) do + {nil, nil} + end + + @doc """ + Checks if smart-contract is proxy. Returns true/false. + """ + @spec proxy_contract?(SmartContract.t()) :: boolean() + def proxy_contract?(smart_contract) do + {:ok, burn_address_hash} = string_to_address_hash(SmartContract.burn_address_hash_string()) + + if smart_contract.implementation_address_hash && + smart_contract.implementation_address_hash.bytes !== burn_address_hash.bytes do + true + else + {implementation_address_hash_string, _} = SmartContract.get_implementation_address_hash(smart_contract, []) + + with false <- is_nil(implementation_address_hash_string), + {:ok, implementation_address_hash} <- string_to_address_hash(implementation_address_hash_string), + false <- implementation_address_hash.bytes == burn_address_hash.bytes do + true + else + _ -> + false + end + end + end + + @doc """ + Decodes address output into 20 bytes address hash + """ + @spec abi_decode_address_output(any()) :: nil | binary() + def abi_decode_address_output(nil), do: nil + + def abi_decode_address_output("0x"), do: SmartContract.burn_address_hash_string() + + def abi_decode_address_output(address) when is_binary(address) do + if String.length(address) > 42 do + "0x" <> String.slice(address, -40, 40) + else + address + end + end + + def abi_decode_address_output(_), do: nil + + @doc """ + Gets implementation ABI for given proxy smart-contract + """ + @spec get_implementation_abi_from_proxy(any(), any()) :: [map()] + def get_implementation_abi_from_proxy( + %SmartContract{address_hash: proxy_address_hash, abi: abi} = smart_contract, + options + ) + when not is_nil(proxy_address_hash) and not is_nil(abi) do + {implementation_address_hash_string, _name} = SmartContract.get_implementation_address_hash(smart_contract, options) + SmartContract.get_smart_contract_abi(implementation_address_hash_string) + end + + def get_implementation_abi_from_proxy(_, _), do: [] + + @doc """ + Checks if the ABI of the smart-contract follows GnosisSafe proxy pattern + """ + @spec gnosis_safe_contract?([map()]) :: boolean() + def gnosis_safe_contract?(abi) when not is_nil(abi) do + if get_master_copy_pattern(abi), do: true, else: false + end + + def gnosis_safe_contract?(abi) when is_nil(abi), do: false + + @doc """ + Checks if the input of the smart-contract follows master-copy proxy pattern + """ + @spec master_copy_pattern?(map()) :: any() + def master_copy_pattern?(method) do + Map.get(method, "type") == "constructor" && + method + |> Enum.find(fn item -> + case item do + {"inputs", inputs} -> + find_input_by_name(inputs, "_masterCopy") || find_input_by_name(inputs, "_singleton") + + _ -> + false + end + end) + end + + @doc """ + Gets implementation from proxy contract's specific storage + """ + @spec get_implementation_from_storage(Hash.Address.t(), String.t(), any()) :: String.t() | nil + def get_implementation_from_storage(proxy_address_hash, storage_slot, json_rpc_named_arguments) do + case Contract.eth_get_storage_at_request( + proxy_address_hash, + storage_slot, + nil, + json_rpc_named_arguments + ) do + {:ok, empty_address_hash_string} + when is_burn_signature_or_nil(empty_address_hash_string) -> + nil + + {:ok, implementation_logic_address_hash_string} -> + implementation_logic_address_hash_string + + _ -> + nil + end + end + + defp get_implementation_address_hash_string(proxy_address_hash, proxy_abi) do + implementation_method_abi = get_naive_implementation_abi(proxy_abi, "implementation") + + get_implementation_method_abi = get_naive_implementation_abi(proxy_abi, "getImplementation") + + master_copy_method_abi = get_master_copy_pattern(proxy_abi) + + get_address_method_abi = get_naive_implementation_abi(proxy_abi, "getAddress") + + cond do + implementation_method_abi -> + Basic.get_implementation_address(@implementation_signature, proxy_address_hash, proxy_abi) + + get_implementation_method_abi -> + Basic.get_implementation_address(@get_implementation_signature, proxy_address_hash, proxy_abi) + + master_copy_method_abi -> + MasterCopy.get_implementation_address(proxy_address_hash) + + get_address_method_abi -> + EIP930.get_implementation_address(@get_address_signature, proxy_address_hash, proxy_abi) + + true -> + EIP1967.get_implementation_address(proxy_address_hash) || + EIP1167.get_implementation_address(proxy_address_hash) || + EIP1822.get_implementation_address(proxy_address_hash) + end + end + + defp get_naive_implementation_abi(abi, getter_name) do + abi + |> Enum.find(fn method -> + Map.get(method, "name") == getter_name && Map.get(method, "stateMutability") == "view" + end) + end + + defp get_master_copy_pattern(abi) do + abi + |> Enum.find(fn method -> + master_copy_pattern?(method) + end) + end + + def combine_proxy_implementation_abi(smart_contract, options \\ []) + + def combine_proxy_implementation_abi(%SmartContract{abi: abi} = smart_contract, options) when not is_nil(abi) do + implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, options) + + if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi + end + + def combine_proxy_implementation_abi(_, _) do + [] + end + + defp find_input_by_name(inputs, name) do + inputs + |> Enum.find(fn input -> + Map.get(input, "name") == name + end) + end +end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex new file mode 100644 index 000000000000..ed793c662b93 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex @@ -0,0 +1,44 @@ +defmodule Explorer.Chain.SmartContract.Proxy.Basic do + @moduledoc """ + Module for fetching proxy implementation from specific smart-contract getter + """ + + alias Explorer.SmartContract.Reader + + @doc """ + Gets implementation if proxy contract from getter. + """ + @spec get_implementation_address(any(), binary(), any()) :: nil | binary() + def get_implementation_address(signature, proxy_address_hash, abi) do + implementation_address = + case Reader.query_contract( + proxy_address_hash, + abi, + %{ + "#{signature}" => [] + }, + false + ) do + %{^signature => {:ok, [result]}} -> result + _ -> nil + end + + adds_0x_to_address(implementation_address) + end + + @doc """ + Adds 0x to address at the beginning + """ + @spec adds_0x_to_address(nil | binary()) :: nil | binary() + def adds_0x_to_address(nil), do: nil + + def adds_0x_to_address(address) do + if address do + if String.starts_with?(address, "0x") do + address + else + "0x" <> Base.encode16(address, case: :lower) + end + end + end +end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex new file mode 100644 index 000000000000..c76900ece45b --- /dev/null +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex @@ -0,0 +1,54 @@ +defmodule Explorer.Chain.SmartContract.Proxy.EIP1167 do + @moduledoc """ + Module for fetching proxy implementation from https://eips.ethereum.org/EIPS/eip-1167 (Minimal Proxy Contract) + """ + + alias Explorer.Chain + alias Explorer.Chain.{Address, Hash, SmartContract} + + import Ecto.Query, only: [from: 2] + + @doc """ + Get implementation address following EIP-1167 + """ + @spec get_implementation_address(Hash.Address.t(), Keyword.t()) :: SmartContract.t() | nil + def get_implementation_address(address_hash, options \\ []) do + case Chain.select_repo(options).get(Address, address_hash) do + nil -> + nil + + target_address -> + contract_code = target_address.contract_code + + case contract_code do + %Chain.Data{bytes: contract_code_bytes} -> + contract_bytecode = Base.encode16(contract_code_bytes, case: :lower) + + get_proxy_eip_1167(contract_bytecode, options) + + _ -> + nil + end + end + end + + defp get_proxy_eip_1167(contract_bytecode, options) do + case contract_bytecode do + "363d3d373d3d3d363d73" <> <> <> _ -> + template_address = "0x" <> template_address + + query = + from( + smart_contract in SmartContract, + where: smart_contract.address_hash == ^template_address, + select: smart_contract + ) + + query + |> Chain.select_repo(options).one(timeout: 10_000) + + _ -> + nil + end + end +end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1822.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1822.ex new file mode 100644 index 000000000000..887dc50a0655 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1822.ex @@ -0,0 +1,32 @@ +defmodule Explorer.Chain.SmartContract.Proxy.EIP1822 do + @moduledoc """ + Module for fetching proxy implementation from https://eips.ethereum.org/EIPS/eip-1822 Universal Upgradeable Proxy Standard (UUPS) + """ + alias Explorer.Chain.{Hash, SmartContract} + alias Explorer.Chain.SmartContract.Proxy + + @burn_address_hash_string_32 "0x0000000000000000000000000000000000000000000000000000000000000000" + + defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string_32] + defguard is_burn_signature_or_nil(term) when is_burn_signature(term) or term == nil + + # keccak256("PROXIABLE") + @storage_slot_proxiable "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" + + @doc """ + Get implementation address following EIP-1967 + """ + @spec get_implementation_address(Hash.Address.t()) :: SmartContract.t() | nil + def get_implementation_address(proxy_address_hash) do + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + + proxiable_contract_address_hash_string = + Proxy.get_implementation_from_storage( + proxy_address_hash, + @storage_slot_proxiable, + json_rpc_named_arguments + ) + + Proxy.abi_decode_address_output(proxiable_contract_address_hash_string) + end +end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex new file mode 100644 index 000000000000..c6f500492fa2 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex @@ -0,0 +1,101 @@ +defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do + @moduledoc """ + Module for fetching proxy implementation from https://eips.ethereum.org/EIPS/eip-1967 (Proxy Storage Slots) + """ + alias EthereumJSONRPC.Contract + alias Explorer.Chain.{Hash, SmartContract} + alias Explorer.Chain.SmartContract.Proxy + alias Explorer.Chain.SmartContract.Proxy.Basic + + # supported signatures: + # 5c60da1b = keccak256(implementation()) + @implementation_signature "5c60da1b" + + @burn_address_hash_string_32 "0x0000000000000000000000000000000000000000000000000000000000000000" + + defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string_32] + defguard is_burn_signature_or_nil(term) when is_burn_signature(term) or term == nil + + @storage_slot_logic_contract_address "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" + + # to be precise, it is not the part of the EIP-1967 standard, but still uses the same pattern + # changes requested by https://github.com/blockscout/blockscout/issues/5292 + # This is the keccak-256 hash of "org.zeppelinos.proxy.implementation" + @storage_slot_openzeppelin_contract_address "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3" + + @doc """ + Get implementation address following EIP-1967 + """ + @spec get_implementation_address(Hash.Address.t()) :: SmartContract.t() | nil + def get_implementation_address(proxy_address_hash) do + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + + logic_contract_address_hash_string = + Proxy.get_implementation_from_storage( + proxy_address_hash, + @storage_slot_logic_contract_address, + json_rpc_named_arguments + ) + + beacon_contract_address_hash_string = + fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) + + openzeppelin_style_contract_address_hash_string = + Proxy.get_implementation_from_storage( + proxy_address_hash, + @storage_slot_openzeppelin_contract_address, + json_rpc_named_arguments + ) + + implementation_address_hash_string = + logic_contract_address_hash_string || beacon_contract_address_hash_string || + openzeppelin_style_contract_address_hash_string + + Proxy.abi_decode_address_output(implementation_address_hash_string) + end + + # changes requested by https://github.com/blockscout/blockscout/issues/4770 + # for support BeaconProxy pattern + defp fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) do + # https://eips.ethereum.org/EIPS/eip-1967 + storage_slot_beacon_contract_address = "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50" + + implementation_method_abi = [ + %{ + "type" => "function", + "stateMutability" => "view", + "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}], + "name" => "implementation", + "inputs" => [] + } + ] + + case Contract.eth_get_storage_at_request( + proxy_address_hash, + storage_slot_beacon_contract_address, + nil, + json_rpc_named_arguments + ) do + {:ok, empty_address} + when is_burn_signature_or_nil(empty_address) -> + nil + + {:ok, beacon_contract_address} -> + case beacon_contract_address + |> Proxy.abi_decode_address_output() + |> Basic.get_implementation_address( + @implementation_signature, + implementation_method_abi + ) do + <> -> + implementation_address + + _ -> + beacon_contract_address + end + + _ -> + nil + end + end +end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_930.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_930.ex new file mode 100644 index 000000000000..964346df37eb --- /dev/null +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_930.ex @@ -0,0 +1,31 @@ +defmodule Explorer.Chain.SmartContract.Proxy.EIP930 do + @moduledoc """ + Module for fetching proxy implementation from smart-contract getter following https://github.com/ethereum/EIPs/issues/930 + """ + + alias Explorer.Chain.SmartContract.Proxy.Basic + alias Explorer.SmartContract.Reader + + @storage_slot_logic_contract_address "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" + + @doc """ + Gets implementation if proxy contract from getter. + """ + @spec get_implementation_address(any(), binary(), any()) :: nil | binary() + def get_implementation_address(signature, proxy_address_hash, abi) do + implementation_address = + case Reader.query_contract( + proxy_address_hash, + abi, + %{ + "#{signature}" => [@storage_slot_logic_contract_address] + }, + false + ) do + %{^signature => {:ok, [result]}} -> result + _ -> nil + end + + Basic.adds_0x_to_address(implementation_address) + end +end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex new file mode 100644 index 000000000000..fb0df17aaa3c --- /dev/null +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex @@ -0,0 +1,43 @@ +defmodule Explorer.Chain.SmartContract.Proxy.MasterCopy do + @moduledoc """ + Module for fetching master-copy proxy implementation + """ + + alias EthereumJSONRPC.Contract + alias Explorer.Chain.{Hash, SmartContract} + alias Explorer.Chain.SmartContract.Proxy + + @burn_address_hash_string_32 "0x0000000000000000000000000000000000000000000000000000000000000000" + + defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string_32] + + @doc """ + Gets implementation address for proxy contract from master-copy pattern + """ + @spec get_implementation_address(Hash.Address.t()) :: SmartContract.t() | nil + def get_implementation_address(proxy_address_hash) do + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + + master_copy_storage_pointer = "0x0" + + {:ok, implementation_address} = + case Contract.eth_get_storage_at_request( + proxy_address_hash, + master_copy_storage_pointer, + nil, + json_rpc_named_arguments + ) do + {:ok, empty_address} + when is_burn_signature(empty_address) -> + {:ok, "0x"} + + {:ok, logic_contract_address} -> + {:ok, logic_contract_address} + + _ -> + {:ok, nil} + end + + Proxy.abi_decode_address_output(implementation_address) + end +end diff --git a/apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex b/apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex index 850c28f4b678..a05e5f0a733b 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex @@ -8,6 +8,8 @@ defmodule Explorer.Chain.SmartContractAdditionalSource do use Explorer.Schema + import Explorer.Chain, only: [select_repo: 1] + alias Explorer.Chain.{Hash, SmartContract} @typedoc """ @@ -59,5 +61,24 @@ defmodule Explorer.Chain.SmartContractAdditionalSource do add_error(validated, :contract_source_code, error_message(error)) end + @doc """ + Returns all additional sources for the given smart-contract address hash + """ + @spec get_contract_additional_sources(SmartContract.t(), Keyword.t()) :: [__MODULE__.t()] + def get_contract_additional_sources(smart_contract, options) do + if smart_contract do + all_additional_sources_query = + from( + s in __MODULE__, + where: s.address_hash == ^smart_contract.address_hash + ) + + all_additional_sources_query + |> select_repo(options).all() + else + [] + end + end + defp error_message(_), do: "There was an error validating your contract, please try again." end diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 60e9cb3d2c4f..32dc58105f1e 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -31,6 +31,7 @@ defmodule Explorer.Chain.Transaction do Wei } + alias Explorer.Chain.SmartContract.Proxy alias Explorer.Chain.Transaction.{Fork, Status} alias Explorer.Chain.Zkevm.BatchTransaction alias Explorer.SmartContract.SigProviderInterface @@ -792,7 +793,7 @@ defmodule Explorer.Chain.Transaction do if !is_nil(address_hash) && Map.has_key?(full_abi_acc, address_hash) do {full_abi_acc[address_hash], full_abi_acc} else - full_abi = Chain.combine_proxy_implementation_abi(smart_contract, options) + full_abi = Proxy.combine_proxy_implementation_abi(smart_contract, options) {full_abi, Map.put(full_abi_acc, address_hash, full_abi)} end diff --git a/apps/explorer/lib/explorer/etherscan/contracts.ex b/apps/explorer/lib/explorer/etherscan/contracts.ex index 4985220bf64f..de63dc05af36 100644 --- a/apps/explorer/lib/explorer/etherscan/contracts.ex +++ b/apps/explorer/lib/explorer/etherscan/contracts.ex @@ -11,8 +11,10 @@ defmodule Explorer.Etherscan.Contracts do where: 3 ] - alias Explorer.{Chain, Repo} + alias Explorer.Repo alias Explorer.Chain.{Address, Hash, SmartContract} + alias Explorer.Chain.SmartContract.Proxy + alias Explorer.Chain.SmartContract.Proxy.EIP1167 @spec address_hash_to_address_with_source_code(Hash.Address.t()) :: Address.t() | nil def address_hash_to_address_with_source_code(address_hash) do @@ -38,8 +40,8 @@ defmodule Explorer.Etherscan.Contracts do } else address_verified_twin_contract = - Chain.get_minimal_proxy_template(address_hash) || - Chain.get_address_verified_twin_contract(address_hash).verified_contract + EIP1167.get_implementation_address(address_hash) || + SmartContract.get_address_verified_twin_contract(address_hash).verified_contract compose_address_with_smart_contract( address_with_smart_contract, @@ -67,7 +69,7 @@ defmodule Explorer.Etherscan.Contracts do def append_proxy_info(%Address{smart_contract: smart_contract} = address) when not is_nil(smart_contract) do updated_smart_contract = - if SmartContract.proxy_contract?(smart_contract) do + if Proxy.proxy_contract?(smart_contract) do smart_contract |> Map.put(:is_proxy, true) |> Map.put( diff --git a/apps/explorer/lib/explorer/smart_contract/reader.ex b/apps/explorer/lib/explorer/smart_contract/reader.ex index 19b8a86af152..cdfd2ca51c53 100644 --- a/apps/explorer/lib/explorer/smart_contract/reader.ex +++ b/apps/explorer/lib/explorer/smart_contract/reader.ex @@ -7,8 +7,8 @@ defmodule Explorer.SmartContract.Reader do """ alias EthereumJSONRPC.{Contract, Encoder} - alias Explorer.Chain alias Explorer.Chain.{Hash, SmartContract} + alias Explorer.Chain.SmartContract.Proxy alias Explorer.SmartContract.Helper @typedoc """ @@ -92,7 +92,7 @@ defmodule Explorer.SmartContract.Reader do defp prepare_abi(nil, address_hash) do address_hash - |> Chain.address_hash_to_smart_contract() + |> SmartContract.address_hash_to_smart_contract() |> Map.get(:abi) end @@ -222,7 +222,7 @@ defmodule Explorer.SmartContract.Reader do def read_only_functions(nil, _, _), do: [] def read_only_functions_proxy(contract_address_hash, implementation_address_hash_string, from, options \\ []) do - implementation_abi = Chain.get_implementation_abi(implementation_address_hash_string, options) + implementation_abi = SmartContract.get_smart_contract_abi(implementation_address_hash_string, options) case implementation_abi do nil -> @@ -238,7 +238,7 @@ defmodule Explorer.SmartContract.Reader do """ @spec read_functions_required_wallet_proxy(String.t()) :: [%{}] def read_functions_required_wallet_proxy(implementation_address_hash_string) do - implementation_abi = Chain.get_implementation_abi(implementation_address_hash_string) + implementation_abi = SmartContract.get_smart_contract_abi(implementation_address_hash_string) case implementation_abi do nil -> @@ -572,10 +572,10 @@ defmodule Explorer.SmartContract.Reader do end defp get_abi(contract_address_hash, type, options) do - contract = Chain.address_hash_to_smart_contract(contract_address_hash, options) + contract = SmartContract.address_hash_to_smart_contract(contract_address_hash, options) if type == :proxy do - Chain.get_implementation_abi_from_proxy(contract, options) + Proxy.get_implementation_abi_from_proxy(contract, options) else contract.abi end diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex b/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex index cee27ad6c942..b83895a15e46 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex @@ -7,7 +7,6 @@ defmodule Explorer.SmartContract.Solidity.Publisher do import Explorer.SmartContract.Helper, only: [cast_libraries: 1] - alias Explorer.Chain alias Explorer.Chain.SmartContract alias Explorer.SmartContract.{CompilerVersion, Helper} alias Explorer.SmartContract.Solidity.Verifier @@ -200,10 +199,10 @@ defmodule Explorer.SmartContract.Solidity.Publisher do defp create_or_update_smart_contract(address_hash, attrs) do Logger.info("Publish successfully verified Solidity smart-contract #{address_hash} into the DB") - if Chain.smart_contract_verified?(address_hash) do - Chain.update_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources) + if SmartContract.verified?(address_hash) do + SmartContract.update_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources) else - Chain.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources) + SmartContract.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources) end end diff --git a/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex b/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex index e5c38116d06f..7a6087d39063 100644 --- a/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex +++ b/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex @@ -7,7 +7,6 @@ defmodule Explorer.SmartContract.Vyper.Publisher do require Logger - alias Explorer.Chain alias Explorer.Chain.SmartContract alias Explorer.SmartContract.CompilerVersion alias Explorer.SmartContract.Vyper.Verifier @@ -124,7 +123,7 @@ defmodule Explorer.SmartContract.Vyper.Publisher do Logger.info("Publish successfully verified Vyper smart-contract #{address_hash} into the DB") attrs = address_hash |> attributes(params, abi) - Chain.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources) + SmartContract.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources) end defp unverified_smart_contract(address_hash, params, error, error_message, verification_with_files? \\ false) do diff --git a/apps/explorer/lib/explorer/smart_contract/writer.ex b/apps/explorer/lib/explorer/smart_contract/writer.ex index 07b77abd627b..6aef776a1ca5 100644 --- a/apps/explorer/lib/explorer/smart_contract/writer.ex +++ b/apps/explorer/lib/explorer/smart_contract/writer.ex @@ -3,7 +3,6 @@ defmodule Explorer.SmartContract.Writer do Generates smart-contract transactions """ - alias Explorer.Chain alias Explorer.Chain.{Hash, SmartContract} alias Explorer.SmartContract.Helper @@ -21,7 +20,7 @@ defmodule Explorer.SmartContract.Writer do @spec write_functions_proxy(Hash.t() | String.t()) :: [%{}] def write_functions_proxy(implementation_address_hash_string, options \\ []) do - implementation_abi = Chain.get_implementation_abi(implementation_address_hash_string, options) + implementation_abi = SmartContract.get_smart_contract_abi(implementation_address_hash_string, options) case implementation_abi do nil -> diff --git a/apps/explorer/test/explorer/chain/log_test.exs b/apps/explorer/test/explorer/chain/log_test.exs index 1b5df9388e9c..6188903de08a 100644 --- a/apps/explorer/test/explorer/chain/log_test.exs +++ b/apps/explorer/test/explorer/chain/log_test.exs @@ -104,7 +104,7 @@ defmodule Explorer.Chain.LogTest do data: data ) - get_eip1967_implementation() + request_zero_implementations() assert {{:ok, "eb9b3c4c", "WantsPets(string indexed _from_human, uint256 _number, bool indexed _belly)", [ @@ -173,7 +173,7 @@ defmodule Explorer.Chain.LogTest do end end - def get_eip1967_implementation do + def request_zero_implementations do EthereumJSONRPC.Mox |> expect(:json_rpc, fn %{ id: 0, @@ -211,5 +211,17 @@ defmodule Explorer.Chain.LogTest do _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) end end diff --git a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs new file mode 100644 index 000000000000..4c96a032c1e2 --- /dev/null +++ b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs @@ -0,0 +1,384 @@ +defmodule Explorer.Chain.SmartContract.ProxyTest do + use Explorer.DataCase, async: false + import Mox + alias Explorer.Chain.SmartContract + alias Explorer.Chain.SmartContract.Proxy + + setup :verify_on_exit! + setup :set_mox_global + + describe "proxy contracts features" do + @proxy_abi [ + %{ + "type" => "function", + "stateMutability" => "nonpayable", + "payable" => false, + "outputs" => [%{"type" => "bool", "name" => ""}], + "name" => "upgradeTo", + "inputs" => [%{"type" => "address", "name" => "newImplementation"}], + "constant" => false + }, + %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [%{"type" => "uint256", "name" => ""}], + "name" => "version", + "inputs" => [], + "constant" => true + }, + %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [%{"type" => "address", "name" => ""}], + "name" => "implementation", + "inputs" => [], + "constant" => true + }, + %{ + "type" => "function", + "stateMutability" => "nonpayable", + "payable" => false, + "outputs" => [], + "name" => "renounceOwnership", + "inputs" => [], + "constant" => false + }, + %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [%{"type" => "address", "name" => ""}], + "name" => "getOwner", + "inputs" => [], + "constant" => true + }, + %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [%{"type" => "address", "name" => ""}], + "name" => "getProxyStorage", + "inputs" => [], + "constant" => true + }, + %{ + "type" => "function", + "stateMutability" => "nonpayable", + "payable" => false, + "outputs" => [], + "name" => "transferOwnership", + "inputs" => [%{"type" => "address", "name" => "_newOwner"}], + "constant" => false + }, + %{ + "type" => "constructor", + "stateMutability" => "nonpayable", + "payable" => false, + "inputs" => [ + %{"type" => "address", "name" => "_proxyStorage"}, + %{"type" => "address", "name" => "_implementationAddress"} + ] + }, + %{"type" => "fallback", "stateMutability" => "nonpayable", "payable" => false}, + %{ + "type" => "event", + "name" => "Upgraded", + "inputs" => [ + %{"type" => "uint256", "name" => "version", "indexed" => false}, + %{"type" => "address", "name" => "implementation", "indexed" => true} + ], + "anonymous" => false + }, + %{ + "type" => "event", + "name" => "OwnershipRenounced", + "inputs" => [%{"type" => "address", "name" => "previousOwner", "indexed" => true}], + "anonymous" => false + }, + %{ + "type" => "event", + "name" => "OwnershipTransferred", + "inputs" => [ + %{"type" => "address", "name" => "previousOwner", "indexed" => true}, + %{"type" => "address", "name" => "newOwner", "indexed" => true} + ], + "anonymous" => false + } + ] + + @implementation_abi [ + %{ + "constant" => false, + "inputs" => [%{"name" => "x", "type" => "uint256"}], + "name" => "set", + "outputs" => [], + "payable" => false, + "stateMutability" => "nonpayable", + "type" => "function" + }, + %{ + "constant" => true, + "inputs" => [], + "name" => "get", + "outputs" => [%{"name" => "", "type" => "uint256"}], + "payable" => false, + "stateMutability" => "view", + "type" => "function" + } + ] + + # EIP-1967 + EIP-1822 + defp request_zero_implementations do + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + end + + test "combine_proxy_implementation_abi/2 returns empty [] abi if proxy abi is null" do + proxy_contract_address = insert(:contract_address) + + assert Proxy.combine_proxy_implementation_abi(%SmartContract{address_hash: proxy_contract_address.hash, abi: nil}) == + [] + end + + test "combine_proxy_implementation_abi/2 returns [] abi for unverified proxy" do + proxy_contract_address = insert(:contract_address) + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") + + request_zero_implementations() + + assert Proxy.combine_proxy_implementation_abi(smart_contract) == [] + end + + test "combine_proxy_implementation_abi/2 returns proxy abi if implementation is not verified" do + proxy_contract_address = insert(:contract_address) + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") + + assert Proxy.combine_proxy_implementation_abi(smart_contract) == @proxy_abi + end + + test "combine_proxy_implementation_abi/2 returns proxy + implementation abi if implementation is verified" do + proxy_contract_address = insert(:contract_address) + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") + + implementation_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: implementation_contract_address.hash, + abi: @implementation_abi, + contract_code_md5: "123" + ) + + implementation_contract_address_hash_string = + Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, + [ + %{ + id: id, + jsonrpc: "2.0", + result: "0x000000000000000000000000" <> implementation_contract_address_hash_string + } + ]} + end + ) + + combined_abi = Proxy.combine_proxy_implementation_abi(smart_contract) + + assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == false + assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == false + assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == true + assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == true + end + + test "get_implementation_abi_from_proxy/2 returns empty [] abi if proxy abi is null" do + proxy_contract_address = insert(:contract_address) + + assert Proxy.get_implementation_abi_from_proxy( + %SmartContract{address_hash: proxy_contract_address.hash, abi: nil}, + [] + ) == + [] + end + + test "get_implementation_abi_from_proxy/2 returns [] abi for unverified proxy" do + proxy_contract_address = insert(:contract_address) + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") + + request_zero_implementations() + + assert Proxy.combine_proxy_implementation_abi(smart_contract) == [] + end + + test "get_implementation_abi_from_proxy/2 returns [] if implementation is not verified" do + proxy_contract_address = insert(:contract_address) + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") + + assert Proxy.get_implementation_abi_from_proxy(smart_contract, []) == [] + end + + test "get_implementation_abi_from_proxy/2 returns implementation abi if implementation is verified" do + proxy_contract_address = insert(:contract_address) + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") + + implementation_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: implementation_contract_address.hash, + abi: @implementation_abi, + contract_code_md5: "123" + ) + + implementation_contract_address_hash_string = + Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, + [ + %{ + id: id, + jsonrpc: "2.0", + result: "0x000000000000000000000000" <> implementation_contract_address_hash_string + } + ]} + end + ) + + implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, []) + + assert implementation_abi == @implementation_abi + end + + test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern" do + proxy_contract_address = insert(:contract_address) + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") + + implementation_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: implementation_contract_address.hash, + abi: @implementation_abi, + contract_code_md5: "123" + ) + + implementation_contract_address_hash_string = + Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn %{ + id: _id, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x000000000000000000000000" <> implementation_contract_address_hash_string} + end + ) + + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + + implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, []) + + assert implementation_abi == @implementation_abi + end + end +end diff --git a/apps/explorer/test/explorer/chain/smart_contract_test.exs b/apps/explorer/test/explorer/chain/smart_contract_test.exs index aa2b90cec982..01ccba28002e 100644 --- a/apps/explorer/test/explorer/chain/smart_contract_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract_test.exs @@ -3,7 +3,8 @@ defmodule Explorer.Chain.SmartContractTest do import Mox alias Explorer.Chain - alias Explorer.Chain.SmartContract + alias Explorer.Chain.{Address, SmartContract} + alias Explorer.Chain.SmartContract.Proxy doctest Explorer.Chain.SmartContract @@ -19,42 +20,38 @@ defmodule Explorer.Chain.SmartContractTest do refute smart_contract.implementation_fetched_at - # fetch nil implementation and save it to db + # fetch nil implementation and don't save it to db get_eip1967_implementation_zero_addresses() - refute SmartContract.proxy_contract?(smart_contract) + refute Proxy.proxy_contract?(smart_contract) verify!(EthereumJSONRPC.Mox) - assert_empty_implementation(smart_contract.address_hash) - # extract proxy info from db - refute SmartContract.proxy_contract?(smart_contract) - verify!(EthereumJSONRPC.Mox) - assert_empty_implementation(smart_contract.address_hash) + assert_implementation_never_fetched(smart_contract.address_hash) Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0) get_eip1967_implementation_error_response() - refute SmartContract.proxy_contract?(smart_contract) + refute Proxy.proxy_contract?(smart_contract) verify!(EthereumJSONRPC.Mox) get_eip1967_implementation_non_zero_address() - assert SmartContract.proxy_contract?(smart_contract) + assert Proxy.proxy_contract?(smart_contract) verify!(EthereumJSONRPC.Mox) assert_implementation_address(smart_contract.address_hash) get_eip1967_implementation_non_zero_address() - assert SmartContract.proxy_contract?(smart_contract) + assert Proxy.proxy_contract?(smart_contract) verify!(EthereumJSONRPC.Mox) assert_implementation_address(smart_contract.address_hash) Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) - assert SmartContract.proxy_contract?(smart_contract) + assert Proxy.proxy_contract?(smart_contract) Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0) get_eip1967_implementation_non_zero_address() - assert SmartContract.proxy_contract?(smart_contract) + assert Proxy.proxy_contract?(smart_contract) verify!(EthereumJSONRPC.Mox) get_eip1967_implementation_error_response() - assert SmartContract.proxy_contract?(smart_contract) + assert Proxy.proxy_contract?(smart_contract) verify!(EthereumJSONRPC.Mox) end @@ -67,16 +64,13 @@ defmodule Explorer.Chain.SmartContractTest do refute smart_contract.implementation_fetched_at - # fetch nil implementation and save it to db + # fetch nil implementation and don't save it to db get_eip1967_implementation_zero_addresses() assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract) verify!(EthereumJSONRPC.Mox) - assert_empty_implementation(smart_contract.address_hash) + assert_implementation_never_fetched(smart_contract.address_hash) # extract proxy info from db - assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract) - assert_empty_implementation(smart_contract.address_hash) - Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0) string_implementation_address_hash = to_string(implementation_smart_contract.address_hash) @@ -93,6 +87,30 @@ defmodule Explorer.Chain.SmartContractTest do _options -> {:ok, string_implementation_address_hash} end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(smart_contract) @@ -118,23 +136,28 @@ defmodule Explorer.Chain.SmartContractTest do implementation_smart_contract.name ) - contract_1 = Chain.address_hash_to_smart_contract(smart_contract.address_hash) + contract_1 = SmartContract.address_hash_to_smart_contract(smart_contract.address_hash) Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(smart_contract) - contract_2 = Chain.address_hash_to_smart_contract(smart_contract.address_hash) + contract_2 = SmartContract.address_hash_to_smart_contract(smart_contract.address_hash) assert contract_1.implementation_fetched_at == contract_2.implementation_fetched_at && contract_1.updated_at == contract_2.updated_at Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0) get_eip1967_implementation_zero_addresses() - assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract) + + assert {^string_implementation_address_hash, "proxy"} = + SmartContract.get_implementation_address_hash(smart_contract) + verify!(EthereumJSONRPC.Mox) - assert_empty_implementation(smart_contract.address_hash) + + assert contract_1.implementation_fetched_at == contract_2.implementation_fetched_at && + contract_1.updated_at == contract_2.updated_at end test "test get_implementation_address_hash/1 for twins contract" do @@ -143,7 +166,7 @@ defmodule Explorer.Chain.SmartContractTest do smart_contract = insert(:smart_contract) another_address = insert(:contract_address) - twin = Chain.address_hash_to_smart_contract(another_address.hash) + twin = SmartContract.address_hash_to_smart_contract(another_address.hash) implementation_smart_contract = insert(:smart_contract, name: "proxy") Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) @@ -162,18 +185,7 @@ defmodule Explorer.Chain.SmartContractTest do string_implementation_address_hash = to_string(implementation_smart_contract.address_hash) - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, string_implementation_address_hash} - end) + expect_address_in_response(string_implementation_address_hash) assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(twin) @@ -210,18 +222,7 @@ defmodule Explorer.Chain.SmartContractTest do string_implementation_address_hash = to_string(implementation_smart_contract.address_hash) - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, string_implementation_address_hash} - end) + expect_address_in_response(string_implementation_address_hash) assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(twin) @@ -268,18 +269,7 @@ defmodule Explorer.Chain.SmartContractTest do string_implementation_address_hash = to_string(implementation_smart_contract.address_hash) - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, string_implementation_address_hash} - end) + expect_address_in_response(string_implementation_address_hash) assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(twin) @@ -297,6 +287,14 @@ defmodule Explorer.Chain.SmartContractTest do end end + describe "address_hash_to_smart_contract/1" do + test "fetches a smart contract" do + smart_contract = insert(:smart_contract, contract_code_md5: "123") + + assert ^smart_contract = SmartContract.address_hash_to_smart_contract(smart_contract.address_hash) + end + end + def get_eip1967_implementation_zero_addresses do EthereumJSONRPC.Mox |> expect(:json_rpc, fn %{ @@ -335,6 +333,18 @@ defmodule Explorer.Chain.SmartContractTest do _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) end def get_eip1967_implementation_non_zero_address do @@ -350,6 +360,30 @@ defmodule Explorer.Chain.SmartContractTest do _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000001"} end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) end def get_eip1967_implementation_error_response do @@ -366,38 +400,534 @@ defmodule Explorer.Chain.SmartContractTest do _options -> {:error, "error"} end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) end def assert_empty_implementation(address_hash) do - contract = Chain.address_hash_to_smart_contract(address_hash) + contract = SmartContract.address_hash_to_smart_contract(address_hash) assert contract.implementation_fetched_at refute contract.implementation_name refute contract.implementation_address_hash end def assert_implementation_never_fetched(address_hash) do - contract = Chain.address_hash_to_smart_contract(address_hash) + contract = SmartContract.address_hash_to_smart_contract(address_hash) refute contract.implementation_fetched_at refute contract.implementation_name refute contract.implementation_address_hash end def assert_implementation_address(address_hash) do - contract = Chain.address_hash_to_smart_contract(address_hash) + contract = SmartContract.address_hash_to_smart_contract(address_hash) assert contract.implementation_fetched_at assert contract.implementation_address_hash end def assert_implementation_name(address_hash) do - contract = Chain.address_hash_to_smart_contract(address_hash) + contract = SmartContract.address_hash_to_smart_contract(address_hash) assert contract.implementation_fetched_at assert contract.implementation_name end def assert_exact_name_and_address(address_hash, implementation_address_hash, implementation_name) do - contract = Chain.address_hash_to_smart_contract(address_hash) + contract = SmartContract.address_hash_to_smart_contract(address_hash) assert contract.implementation_fetched_at assert contract.implementation_name == implementation_name assert to_string(contract.implementation_address_hash) == to_string(implementation_address_hash) end + + describe "create_smart_contract/1" do + setup do + smart_contract_bytecode = + "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029" + + created_contract_address = + insert( + :address, + hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c", + contract_code: smart_contract_bytecode + ) + + transaction = + :transaction + |> insert() + |> with_block() + + insert( + :internal_transaction_create, + transaction: transaction, + index: 0, + created_contract_address: created_contract_address, + created_contract_code: smart_contract_bytecode, + block_number: transaction.block_number, + block_hash: transaction.block_hash, + block_index: 0, + transaction_index: transaction.index + ) + + valid_attrs = %{ + address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c", + name: "SimpleStorage", + compiler_version: "0.4.23", + optimization: false, + contract_source_code: + "pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }", + abi: [ + %{ + "constant" => false, + "inputs" => [%{"name" => "x", "type" => "uint256"}], + "name" => "set", + "outputs" => [], + "payable" => false, + "stateMutability" => "nonpayable", + "type" => "function" + }, + %{ + "constant" => true, + "inputs" => [], + "name" => "get", + "outputs" => [%{"name" => "", "type" => "uint256"}], + "payable" => false, + "stateMutability" => "view", + "type" => "function" + } + ] + } + + {:ok, valid_attrs: valid_attrs, address: created_contract_address} + end + + test "with valid data creates a smart contract", %{valid_attrs: valid_attrs} do + assert {:ok, %SmartContract{} = smart_contract} = SmartContract.create_smart_contract(valid_attrs) + assert smart_contract.name == "SimpleStorage" + assert smart_contract.compiler_version == "0.4.23" + assert smart_contract.optimization == false + assert smart_contract.contract_source_code != "" + assert smart_contract.abi != "" + + assert Repo.get_by( + Address.Name, + address_hash: smart_contract.address_hash, + name: smart_contract.name, + primary: true + ) + end + + test "clears an existing primary name and sets the new one", %{valid_attrs: valid_attrs, address: address} do + insert(:address_name, address: address, primary: true) + assert {:ok, %SmartContract{} = smart_contract} = SmartContract.create_smart_contract(valid_attrs) + + assert Repo.get_by( + Address.Name, + address_hash: smart_contract.address_hash, + name: smart_contract.name, + primary: true + ) + end + + test "trims whitespace from address name", %{valid_attrs: valid_attrs} do + attrs = %{valid_attrs | name: " SimpleStorage "} + assert {:ok, _} = SmartContract.create_smart_contract(attrs) + assert Repo.get_by(Address.Name, name: "SimpleStorage") + end + + test "sets the address verified field to true", %{valid_attrs: valid_attrs} do + assert {:ok, %SmartContract{} = smart_contract} = SmartContract.create_smart_contract(valid_attrs) + + assert Repo.get_by(Address, hash: smart_contract.address_hash).verified == true + end + end + + describe "update_smart_contract/1" do + setup do + smart_contract_bytecode = + "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029" + + created_contract_address = + insert( + :address, + hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c", + contract_code: smart_contract_bytecode + ) + + transaction = + :transaction + |> insert() + |> with_block() + + insert( + :internal_transaction_create, + transaction: transaction, + index: 0, + created_contract_address: created_contract_address, + created_contract_code: smart_contract_bytecode, + block_number: transaction.block_number, + block_hash: transaction.block_hash, + block_index: 0, + transaction_index: transaction.index + ) + + valid_attrs = %{ + address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c", + name: "SimpleStorage", + compiler_version: "0.4.23", + optimization: false, + contract_source_code: + "pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }", + abi: [ + %{ + "constant" => false, + "inputs" => [%{"name" => "x", "type" => "uint256"}], + "name" => "set", + "outputs" => [], + "payable" => false, + "stateMutability" => "nonpayable", + "type" => "function" + }, + %{ + "constant" => true, + "inputs" => [], + "name" => "get", + "outputs" => [%{"name" => "", "type" => "uint256"}], + "payable" => false, + "stateMutability" => "view", + "type" => "function" + } + ], + partially_verified: true + } + + secondary_sources = [ + %{ + file_name: "storage.sol", + contract_source_code: + "pragma solidity >=0.7.0 <0.9.0;contract Storage {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}", + address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c" + }, + %{ + file_name: "storage_1.sol", + contract_source_code: + "pragma solidity >=0.7.0 <0.9.0;contract Storage_1 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}", + address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c" + } + ] + + changed_sources = [ + %{ + file_name: "storage_2.sol", + contract_source_code: + "pragma solidity >=0.7.0 <0.9.0;contract Storage_2 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}", + address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c" + }, + %{ + file_name: "storage_3.sol", + contract_source_code: + "pragma solidity >=0.7.0 <0.9.0;contract Storage_3 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}", + address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c" + } + ] + + _ = SmartContract.create_smart_contract(valid_attrs, [], secondary_sources) + + {:ok, + valid_attrs: valid_attrs, + address: created_contract_address, + secondary_sources: secondary_sources, + changed_sources: changed_sources} + end + + test "change partially_verified field", %{valid_attrs: valid_attrs, address: address} do + sc_before_call = Repo.get_by(SmartContract, address_hash: address.hash) + assert sc_before_call.name == Map.get(valid_attrs, :name) + assert sc_before_call.partially_verified == Map.get(valid_attrs, :partially_verified) + + assert {:ok, %SmartContract{}} = + SmartContract.update_smart_contract(%{ + address_hash: address.hash, + partially_verified: false, + contract_source_code: "new code" + }) + + sc_after_call = Repo.get_by(SmartContract, address_hash: address.hash) + assert sc_after_call.name == Map.get(valid_attrs, :name) + assert sc_after_call.partially_verified == false + assert sc_after_call.compiler_version == Map.get(valid_attrs, :compiler_version) + assert sc_after_call.optimization == Map.get(valid_attrs, :optimization) + assert sc_after_call.contract_source_code == "new code" + end + + test "check nothing changed", %{valid_attrs: valid_attrs, address: address} do + sc_before_call = Repo.get_by(SmartContract, address_hash: address.hash) + assert sc_before_call.name == Map.get(valid_attrs, :name) + assert sc_before_call.partially_verified == Map.get(valid_attrs, :partially_verified) + + assert {:ok, %SmartContract{}} = SmartContract.update_smart_contract(%{address_hash: address.hash}) + + sc_after_call = Repo.get_by(SmartContract, address_hash: address.hash) + assert sc_after_call.name == Map.get(valid_attrs, :name) + assert sc_after_call.partially_verified == Map.get(valid_attrs, :partially_verified) + assert sc_after_call.compiler_version == Map.get(valid_attrs, :compiler_version) + assert sc_after_call.optimization == Map.get(valid_attrs, :optimization) + assert sc_after_call.contract_source_code == Map.get(valid_attrs, :contract_source_code) + end + + test "check additional sources update", %{ + address: address, + secondary_sources: secondary_sources, + changed_sources: changed_sources + } do + sc_before_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources) + + assert sc_before_call.smart_contract_additional_sources + |> Enum.with_index() + |> Enum.all?(fn {el, ind} -> + {:ok, src} = Enum.fetch(secondary_sources, ind) + + el.file_name == Map.get(src, :file_name) and + el.contract_source_code == Map.get(src, :contract_source_code) + end) + + assert {:ok, %SmartContract{}} = + SmartContract.update_smart_contract(%{address_hash: address.hash}, [], changed_sources) + + sc_after_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources) + + assert sc_after_call.smart_contract_additional_sources + |> Enum.with_index() + |> Enum.all?(fn {el, ind} -> + {:ok, src} = Enum.fetch(changed_sources, ind) + + el.file_name == Map.get(src, :file_name) and + el.contract_source_code == Map.get(src, :contract_source_code) + end) + end + end + + test "get_smart_contract_abi/1 returns empty [] abi if implementation address is null" do + assert SmartContract.get_smart_contract_abi(nil) == [] + end + + test "get_smart_contract_abi/1 returns [] if implementation is not verified" do + implementation_contract_address = insert(:contract_address) + + implementation_contract_address_hash_string = + Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + + assert SmartContract.get_smart_contract_abi("0x" <> implementation_contract_address_hash_string) == [] + end + + @abi [ + %{ + "constant" => false, + "inputs" => [%{"name" => "x", "type" => "uint256"}], + "name" => "set", + "outputs" => [], + "payable" => false, + "stateMutability" => "nonpayable", + "type" => "function" + }, + %{ + "constant" => true, + "inputs" => [], + "name" => "get", + "outputs" => [%{"name" => "", "type" => "uint256"}], + "payable" => false, + "stateMutability" => "view", + "type" => "function" + } + ] + + @proxy_abi [ + %{ + "type" => "function", + "stateMutability" => "nonpayable", + "payable" => false, + "outputs" => [%{"type" => "bool", "name" => ""}], + "name" => "upgradeTo", + "inputs" => [%{"type" => "address", "name" => "newImplementation"}], + "constant" => false + }, + %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [%{"type" => "uint256", "name" => ""}], + "name" => "version", + "inputs" => [], + "constant" => true + }, + %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [%{"type" => "address", "name" => ""}], + "name" => "implementation", + "inputs" => [], + "constant" => true + }, + %{ + "type" => "function", + "stateMutability" => "nonpayable", + "payable" => false, + "outputs" => [], + "name" => "renounceOwnership", + "inputs" => [], + "constant" => false + }, + %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [%{"type" => "address", "name" => ""}], + "name" => "getOwner", + "inputs" => [], + "constant" => true + }, + %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [%{"type" => "address", "name" => ""}], + "name" => "getProxyStorage", + "inputs" => [], + "constant" => true + }, + %{ + "type" => "function", + "stateMutability" => "nonpayable", + "payable" => false, + "outputs" => [], + "name" => "transferOwnership", + "inputs" => [%{"type" => "address", "name" => "_newOwner"}], + "constant" => false + }, + %{ + "type" => "constructor", + "stateMutability" => "nonpayable", + "payable" => false, + "inputs" => [ + %{"type" => "address", "name" => "_proxyStorage"}, + %{"type" => "address", "name" => "_implementationAddress"} + ] + }, + %{"type" => "fallback", "stateMutability" => "nonpayable", "payable" => false}, + %{ + "type" => "event", + "name" => "Upgraded", + "inputs" => [ + %{"type" => "uint256", "name" => "version", "indexed" => false}, + %{"type" => "address", "name" => "implementation", "indexed" => true} + ], + "anonymous" => false + }, + %{ + "type" => "event", + "name" => "OwnershipRenounced", + "inputs" => [%{"type" => "address", "name" => "previousOwner", "indexed" => true}], + "anonymous" => false + }, + %{ + "type" => "event", + "name" => "OwnershipTransferred", + "inputs" => [ + %{"type" => "address", "name" => "previousOwner", "indexed" => true}, + %{"type" => "address", "name" => "newOwner", "indexed" => true} + ], + "anonymous" => false + } + ] + + test "get_smart_contract_abi/1 returns implementation abi if implementation is verified" do + proxy_contract_address = insert(:contract_address) + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") + + implementation_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: implementation_contract_address.hash, + abi: @abi, + contract_code_md5: "123" + ) + + implementation_contract_address_hash_string = + Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + + implementation_abi = SmartContract.get_smart_contract_abi("0x" <> implementation_contract_address_hash_string) + + assert implementation_abi == @abi + end + + defp expect_address_in_response(string_implementation_address_hash) do + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, string_implementation_address_hash} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + end end diff --git a/apps/explorer/test/explorer/chain/transaction_test.exs b/apps/explorer/test/explorer/chain/transaction_test.exs index a517ac27d99e..db2823b47ef6 100644 --- a/apps/explorer/test/explorer/chain/transaction_test.exs +++ b/apps/explorer/test/explorer/chain/transaction_test.exs @@ -265,7 +265,7 @@ defmodule Explorer.Chain.TransactionTest do |> insert() |> Repo.preload(to_address: :smart_contract) - get_eip1967_implementation() + request_zero_implementations() assert {{:ok, "60fe47b1", "set(uint256 x)", [{"x", "uint256", 50}]}, _, _} = Transaction.decoded_input_data(transaction, []) @@ -288,7 +288,7 @@ defmodule Explorer.Chain.TransactionTest do |> insert(to_address: contract.address, input: "0x" <> input_data) |> Repo.preload(to_address: :smart_contract) - get_eip1967_implementation() + request_zero_implementations() assert {{:ok, "60fe47b1", "set(uint256 x)", [{"x", "uint256", 10}]}, _, _} = Transaction.decoded_input_data(transaction, []) @@ -309,7 +309,8 @@ defmodule Explorer.Chain.TransactionTest do end end - def get_eip1967_implementation do + # EIP-1967 + EIP-1822 + defp request_zero_implementations do EthereumJSONRPC.Mox |> expect(:json_rpc, fn %{ id: 0, @@ -347,5 +348,17 @@ defmodule Explorer.Chain.TransactionTest do _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) end end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index d5756d0688b8..77f9030f0f95 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -4036,272 +4036,6 @@ defmodule Explorer.ChainTest do end end - describe "create_smart_contract/1" do - setup do - smart_contract_bytecode = - "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029" - - created_contract_address = - insert( - :address, - hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c", - contract_code: smart_contract_bytecode - ) - - transaction = - :transaction - |> insert() - |> with_block() - - insert( - :internal_transaction_create, - transaction: transaction, - index: 0, - created_contract_address: created_contract_address, - created_contract_code: smart_contract_bytecode, - block_number: transaction.block_number, - block_hash: transaction.block_hash, - block_index: 0, - transaction_index: transaction.index - ) - - valid_attrs = %{ - address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c", - name: "SimpleStorage", - compiler_version: "0.4.23", - optimization: false, - contract_source_code: - "pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }", - abi: [ - %{ - "constant" => false, - "inputs" => [%{"name" => "x", "type" => "uint256"}], - "name" => "set", - "outputs" => [], - "payable" => false, - "stateMutability" => "nonpayable", - "type" => "function" - }, - %{ - "constant" => true, - "inputs" => [], - "name" => "get", - "outputs" => [%{"name" => "", "type" => "uint256"}], - "payable" => false, - "stateMutability" => "view", - "type" => "function" - } - ] - } - - {:ok, valid_attrs: valid_attrs, address: created_contract_address} - end - - test "with valid data creates a smart contract", %{valid_attrs: valid_attrs} do - assert {:ok, %SmartContract{} = smart_contract} = Chain.create_smart_contract(valid_attrs) - assert smart_contract.name == "SimpleStorage" - assert smart_contract.compiler_version == "0.4.23" - assert smart_contract.optimization == false - assert smart_contract.contract_source_code != "" - assert smart_contract.abi != "" - - assert Repo.get_by( - Address.Name, - address_hash: smart_contract.address_hash, - name: smart_contract.name, - primary: true - ) - end - - test "clears an existing primary name and sets the new one", %{valid_attrs: valid_attrs, address: address} do - insert(:address_name, address: address, primary: true) - assert {:ok, %SmartContract{} = smart_contract} = Chain.create_smart_contract(valid_attrs) - - assert Repo.get_by( - Address.Name, - address_hash: smart_contract.address_hash, - name: smart_contract.name, - primary: true - ) - end - - test "trims whitespace from address name", %{valid_attrs: valid_attrs} do - attrs = %{valid_attrs | name: " SimpleStorage "} - assert {:ok, _} = Chain.create_smart_contract(attrs) - assert Repo.get_by(Address.Name, name: "SimpleStorage") - end - - test "sets the address verified field to true", %{valid_attrs: valid_attrs} do - assert {:ok, %SmartContract{} = smart_contract} = Chain.create_smart_contract(valid_attrs) - - assert Repo.get_by(Address, hash: smart_contract.address_hash).verified == true - end - end - - describe "update_smart_contract/1" do - setup do - smart_contract_bytecode = - "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029" - - created_contract_address = - insert( - :address, - hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c", - contract_code: smart_contract_bytecode - ) - - transaction = - :transaction - |> insert() - |> with_block() - - insert( - :internal_transaction_create, - transaction: transaction, - index: 0, - created_contract_address: created_contract_address, - created_contract_code: smart_contract_bytecode, - block_number: transaction.block_number, - block_hash: transaction.block_hash, - block_index: 0, - transaction_index: transaction.index - ) - - valid_attrs = %{ - address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c", - name: "SimpleStorage", - compiler_version: "0.4.23", - optimization: false, - contract_source_code: - "pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }", - abi: [ - %{ - "constant" => false, - "inputs" => [%{"name" => "x", "type" => "uint256"}], - "name" => "set", - "outputs" => [], - "payable" => false, - "stateMutability" => "nonpayable", - "type" => "function" - }, - %{ - "constant" => true, - "inputs" => [], - "name" => "get", - "outputs" => [%{"name" => "", "type" => "uint256"}], - "payable" => false, - "stateMutability" => "view", - "type" => "function" - } - ], - partially_verified: true - } - - secondary_sources = [ - %{ - file_name: "storage.sol", - contract_source_code: - "pragma solidity >=0.7.0 <0.9.0;contract Storage {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}", - address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c" - }, - %{ - file_name: "storage_1.sol", - contract_source_code: - "pragma solidity >=0.7.0 <0.9.0;contract Storage_1 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}", - address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c" - } - ] - - changed_sources = [ - %{ - file_name: "storage_2.sol", - contract_source_code: - "pragma solidity >=0.7.0 <0.9.0;contract Storage_2 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}", - address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c" - }, - %{ - file_name: "storage_3.sol", - contract_source_code: - "pragma solidity >=0.7.0 <0.9.0;contract Storage_3 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}", - address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c" - } - ] - - _ = Chain.create_smart_contract(valid_attrs, [], secondary_sources) - - {:ok, - valid_attrs: valid_attrs, - address: created_contract_address, - secondary_sources: secondary_sources, - changed_sources: changed_sources} - end - - test "change partially_verified field", %{valid_attrs: valid_attrs, address: address} do - sc_before_call = Repo.get_by(SmartContract, address_hash: address.hash) - assert sc_before_call.name == Map.get(valid_attrs, :name) - assert sc_before_call.partially_verified == Map.get(valid_attrs, :partially_verified) - - assert {:ok, %SmartContract{}} = - Chain.update_smart_contract(%{ - address_hash: address.hash, - partially_verified: false, - contract_source_code: "new code" - }) - - sc_after_call = Repo.get_by(SmartContract, address_hash: address.hash) - assert sc_after_call.name == Map.get(valid_attrs, :name) - assert sc_after_call.partially_verified == false - assert sc_after_call.compiler_version == Map.get(valid_attrs, :compiler_version) - assert sc_after_call.optimization == Map.get(valid_attrs, :optimization) - assert sc_after_call.contract_source_code == "new code" - end - - test "check nothing changed", %{valid_attrs: valid_attrs, address: address} do - sc_before_call = Repo.get_by(SmartContract, address_hash: address.hash) - assert sc_before_call.name == Map.get(valid_attrs, :name) - assert sc_before_call.partially_verified == Map.get(valid_attrs, :partially_verified) - - assert {:ok, %SmartContract{}} = Chain.update_smart_contract(%{address_hash: address.hash}) - - sc_after_call = Repo.get_by(SmartContract, address_hash: address.hash) - assert sc_after_call.name == Map.get(valid_attrs, :name) - assert sc_after_call.partially_verified == Map.get(valid_attrs, :partially_verified) - assert sc_after_call.compiler_version == Map.get(valid_attrs, :compiler_version) - assert sc_after_call.optimization == Map.get(valid_attrs, :optimization) - assert sc_after_call.contract_source_code == Map.get(valid_attrs, :contract_source_code) - end - - test "check additional sources update", %{ - address: address, - secondary_sources: secondary_sources, - changed_sources: changed_sources - } do - sc_before_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources) - - assert sc_before_call.smart_contract_additional_sources - |> Enum.with_index() - |> Enum.all?(fn {el, ind} -> - {:ok, src} = Enum.fetch(secondary_sources, ind) - - el.file_name == Map.get(src, :file_name) and - el.contract_source_code == Map.get(src, :contract_source_code) - end) - - assert {:ok, %SmartContract{}} = Chain.update_smart_contract(%{address_hash: address.hash}, [], changed_sources) - - sc_after_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources) - - assert sc_after_call.smart_contract_additional_sources - |> Enum.with_index() - |> Enum.all?(fn {el, ind} -> - {:ok, src} = Enum.fetch(changed_sources, ind) - - el.file_name == Map.get(src, :file_name) and - el.contract_source_code == Map.get(src, :contract_source_code) - end) - end - end - describe "stream_unfetched_balances/2" do test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <> "does not return `t:Explorer.Chain.Block.t/0` `miner_hash`" do @@ -4816,14 +4550,6 @@ defmodule Explorer.ChainTest do assert Chain.circulating_supply() == ProofOfAuthority.circulating() end - describe "address_hash_to_smart_contract/1" do - test "fetches a smart contract" do - smart_contract = insert(:smart_contract, contract_code_md5: "123") - - assert ^smart_contract = Chain.address_hash_to_smart_contract(smart_contract.address_hash) - end - end - describe "token_from_address_hash/1" do test "with valid hash" do token = insert(:token) @@ -5797,373 +5523,4 @@ defmodule Explorer.ChainTest do assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(search: contract_name) end end - - describe "proxy contracts features" do - @proxy_abi [ - %{ - "type" => "function", - "stateMutability" => "nonpayable", - "payable" => false, - "outputs" => [%{"type" => "bool", "name" => ""}], - "name" => "upgradeTo", - "inputs" => [%{"type" => "address", "name" => "newImplementation"}], - "constant" => false - }, - %{ - "type" => "function", - "stateMutability" => "view", - "payable" => false, - "outputs" => [%{"type" => "uint256", "name" => ""}], - "name" => "version", - "inputs" => [], - "constant" => true - }, - %{ - "type" => "function", - "stateMutability" => "view", - "payable" => false, - "outputs" => [%{"type" => "address", "name" => ""}], - "name" => "implementation", - "inputs" => [], - "constant" => true - }, - %{ - "type" => "function", - "stateMutability" => "nonpayable", - "payable" => false, - "outputs" => [], - "name" => "renounceOwnership", - "inputs" => [], - "constant" => false - }, - %{ - "type" => "function", - "stateMutability" => "view", - "payable" => false, - "outputs" => [%{"type" => "address", "name" => ""}], - "name" => "getOwner", - "inputs" => [], - "constant" => true - }, - %{ - "type" => "function", - "stateMutability" => "view", - "payable" => false, - "outputs" => [%{"type" => "address", "name" => ""}], - "name" => "getProxyStorage", - "inputs" => [], - "constant" => true - }, - %{ - "type" => "function", - "stateMutability" => "nonpayable", - "payable" => false, - "outputs" => [], - "name" => "transferOwnership", - "inputs" => [%{"type" => "address", "name" => "_newOwner"}], - "constant" => false - }, - %{ - "type" => "constructor", - "stateMutability" => "nonpayable", - "payable" => false, - "inputs" => [ - %{"type" => "address", "name" => "_proxyStorage"}, - %{"type" => "address", "name" => "_implementationAddress"} - ] - }, - %{"type" => "fallback", "stateMutability" => "nonpayable", "payable" => false}, - %{ - "type" => "event", - "name" => "Upgraded", - "inputs" => [ - %{"type" => "uint256", "name" => "version", "indexed" => false}, - %{"type" => "address", "name" => "implementation", "indexed" => true} - ], - "anonymous" => false - }, - %{ - "type" => "event", - "name" => "OwnershipRenounced", - "inputs" => [%{"type" => "address", "name" => "previousOwner", "indexed" => true}], - "anonymous" => false - }, - %{ - "type" => "event", - "name" => "OwnershipTransferred", - "inputs" => [ - %{"type" => "address", "name" => "previousOwner", "indexed" => true}, - %{"type" => "address", "name" => "newOwner", "indexed" => true} - ], - "anonymous" => false - } - ] - - @implementation_abi [ - %{ - "constant" => false, - "inputs" => [%{"name" => "x", "type" => "uint256"}], - "name" => "set", - "outputs" => [], - "payable" => false, - "stateMutability" => "nonpayable", - "type" => "function" - }, - %{ - "constant" => true, - "inputs" => [], - "name" => "get", - "outputs" => [%{"name" => "", "type" => "uint256"}], - "payable" => false, - "stateMutability" => "view", - "type" => "function" - } - ] - - test "combine_proxy_implementation_abi/2 returns empty [] abi if proxy abi is null" do - proxy_contract_address = insert(:contract_address) - - assert Chain.combine_proxy_implementation_abi(%SmartContract{address_hash: proxy_contract_address.hash, abi: nil}) == - [] - end - - test "combine_proxy_implementation_abi/2 returns [] abi for unverified proxy" do - proxy_contract_address = insert(:contract_address) - - smart_contract = - insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") - - get_eip1967_implementation() - - assert Chain.combine_proxy_implementation_abi(smart_contract) == [] - end - - test "combine_proxy_implementation_abi/2 returns proxy abi if implementation is not verified" do - proxy_contract_address = insert(:contract_address) - - smart_contract = - insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") - - assert Chain.combine_proxy_implementation_abi(smart_contract) == @proxy_abi - end - - test "combine_proxy_implementation_abi/2 returns proxy + implementation abi if implementation is verified" do - proxy_contract_address = insert(:contract_address) - - smart_contract = - insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") - - implementation_contract_address = insert(:contract_address) - - insert(:smart_contract, - address_hash: implementation_contract_address.hash, - abi: @implementation_abi, - contract_code_md5: "123" - ) - - implementation_contract_address_hash_string = - Base.encode16(implementation_contract_address.hash.bytes, case: :lower) - - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> - {:ok, - [ - %{ - id: id, - jsonrpc: "2.0", - result: "0x000000000000000000000000" <> implementation_contract_address_hash_string - } - ]} - end - ) - - combined_abi = Chain.combine_proxy_implementation_abi(smart_contract) - - assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == false - assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == false - assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == true - assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == true - end - - test "get_implementation_abi_from_proxy/2 returns empty [] abi if proxy abi is null" do - proxy_contract_address = insert(:contract_address) - - assert Chain.get_implementation_abi_from_proxy( - %SmartContract{address_hash: proxy_contract_address.hash, abi: nil}, - [] - ) == - [] - end - - test "get_implementation_abi_from_proxy/2 returns [] abi for unverified proxy" do - proxy_contract_address = insert(:contract_address) - - smart_contract = - insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") - - get_eip1967_implementation() - - assert Chain.combine_proxy_implementation_abi(smart_contract) == [] - end - - test "get_implementation_abi_from_proxy/2 returns [] if implementation is not verified" do - proxy_contract_address = insert(:contract_address) - - smart_contract = - insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") - - assert Chain.get_implementation_abi_from_proxy(smart_contract, []) == [] - end - - test "get_implementation_abi_from_proxy/2 returns implementation abi if implementation is verified" do - proxy_contract_address = insert(:contract_address) - - smart_contract = - insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") - - implementation_contract_address = insert(:contract_address) - - insert(:smart_contract, - address_hash: implementation_contract_address.hash, - abi: @implementation_abi, - contract_code_md5: "123" - ) - - implementation_contract_address_hash_string = - Base.encode16(implementation_contract_address.hash.bytes, case: :lower) - - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> - {:ok, - [ - %{ - id: id, - jsonrpc: "2.0", - result: "0x000000000000000000000000" <> implementation_contract_address_hash_string - } - ]} - end - ) - - implementation_abi = Chain.get_implementation_abi_from_proxy(smart_contract, []) - - assert implementation_abi == @implementation_abi - end - - test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern" do - proxy_contract_address = insert(:contract_address) - - smart_contract = - insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") - - implementation_contract_address = insert(:contract_address) - - insert(:smart_contract, - address_hash: implementation_contract_address.hash, - abi: @implementation_abi, - contract_code_md5: "123" - ) - - implementation_contract_address_hash_string = - Base.encode16(implementation_contract_address.hash.bytes, case: :lower) - - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn %{ - id: _id, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x000000000000000000000000" <> implementation_contract_address_hash_string} - end - ) - - implementation_abi = Chain.get_implementation_abi_from_proxy(smart_contract, []) - - assert implementation_abi == @implementation_abi - end - - test "get_implementation_abi/1 returns empty [] abi if implementation address is null" do - assert Chain.get_implementation_abi(nil) == [] - end - - test "get_implementation_abi/1 returns [] if implementation is not verified" do - implementation_contract_address = insert(:contract_address) - - implementation_contract_address_hash_string = - Base.encode16(implementation_contract_address.hash.bytes, case: :lower) - - assert Chain.get_implementation_abi("0x" <> implementation_contract_address_hash_string) == [] - end - - test "get_implementation_abi/1 returns implementation abi if implementation is verified" do - proxy_contract_address = insert(:contract_address) - insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") - - implementation_contract_address = insert(:contract_address) - - insert(:smart_contract, - address_hash: implementation_contract_address.hash, - abi: @implementation_abi, - contract_code_md5: "123" - ) - - implementation_contract_address_hash_string = - Base.encode16(implementation_contract_address.hash.bytes, case: :lower) - - implementation_abi = Chain.get_implementation_abi("0x" <> implementation_contract_address_hash_string) - - assert implementation_abi == @implementation_abi - end - end - - def get_eip1967_implementation do - EthereumJSONRPC.Mox - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - end end diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 7ceebb1009b5..713880e401d2 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -33,7 +33,6 @@ defmodule Indexer.Block.Fetcher do alias Indexer.Transform.{ AddressCoinBalances, - AddressCoinBalancesDaily, Addresses, AddressTokenBalances, MintTransfers, diff --git a/cspell.json b/cspell.json index 2ef306d4e72d..95077caf8ceb 100644 --- a/cspell.json +++ b/cspell.json @@ -346,6 +346,7 @@ "prederive", "prederived", "progressbar", + "proxiable", "psql", "purrstige", "qdai", @@ -490,6 +491,7 @@ "upserts", "urijs", "Utqn", + "UUPS", "valign", "valuemax", "valuemin", From e3f8734e0123cf7e8dce7b458b9eb6b84780cf7d Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 14 Nov 2023 11:17:08 +0300 Subject: [PATCH 039/607] Change some specs, add missing spec and docs --- .../lib/explorer/chain/smart_contract.ex | 33 +++++++++++++------ .../chain/smart_contract/proxy/eip_1822.ex | 5 --- .../smart_contract_additional_sources.ex | 2 +- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index ea91857a1a83..2d5a04863623 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -23,6 +23,7 @@ defmodule Explorer.Chain.SmartContract do DecompiledSmartContract, Hash, InternalTransaction, + SmartContract, SmartContractAdditionalSource, Transaction } @@ -243,7 +244,7 @@ defmodule Explorer.Chain.SmartContract do * `verified_via_eth_bytecode_db` - whether contract automatically verified via eth-bytecode-db or not. """ - @type t :: %Explorer.Chain.SmartContract{ + @type t :: %SmartContract{ name: String.t(), compiler_version: String.t(), optimization: boolean, @@ -683,7 +684,7 @@ defmodule Explorer.Chain.SmartContract do @doc """ Composes address object for smart-contract """ - @spec compose_smart_contract(map(), Explorer.Chain.Hash.t(), any()) :: map() + @spec compose_smart_contract(map(), Hash.t(), any()) :: map() def compose_smart_contract(address_result, hash, options) do address_verified_twin_contract = EIP1167.get_implementation_address(hash, options) || @@ -709,6 +710,10 @@ defmodule Explorer.Chain.SmartContract do Finds metadata for verification of a contract from verified twins: contracts with the same bytecode which were verified previously, returns a single t:SmartContract.t/0 """ + @spec get_address_verified_twin_contract(Hash.t() | String.t(), any()) :: %{ + :verified_contract => any(), + :additional_sources => SmartContractAdditionalSource.t() | nil + } def get_address_verified_twin_contract(hash, options \\ []) def get_address_verified_twin_contract(hash, options) when is_binary(hash) do @@ -718,7 +723,7 @@ defmodule Explorer.Chain.SmartContract do end end - def get_address_verified_twin_contract(%Explorer.Chain.Hash{} = address_hash, options) do + def get_address_verified_twin_contract(%Hash{} = address_hash, options) do with target_address <- Chain.select_repo(options).get(Address, address_hash), false <- is_nil(target_address) do verified_contract_twin = get_verified_twin_contract(target_address, options) @@ -736,7 +741,11 @@ defmodule Explorer.Chain.SmartContract do end end - def get_verified_twin_contract(%Explorer.Chain.Address{} = target_address, options \\ []) do + @doc """ + Returns verified smart-contract with the same bytecode of the given smart-contract + """ + @spec get_verified_twin_contract(Address.t(), any()) :: SmartContract.t() | nil + def get_verified_twin_contract(%Address{} = target_address, options \\ []) do case target_address do %{contract_code: %Chain.Data{bytes: contract_code_bytes}} -> target_address_hash = target_address.hash @@ -760,6 +769,10 @@ defmodule Explorer.Chain.SmartContract do end end + @doc """ + Returns address or smart_contract object with parsed constructor_arguments + """ + @spec check_and_update_constructor_args(any()) :: any() def check_and_update_constructor_args( %__MODULE__{address_hash: address_hash, constructor_arguments: nil, verified_via_sourcify: true} = smart_contract @@ -793,7 +806,7 @@ defmodule Explorer.Chain.SmartContract do @doc """ Adds verified metadata from bytecode twin smart-contract to the given smart-contract """ - @spec add_twin_info_to_contract(map(), Chain.Hash.t(), Chain.Hash.t()) :: map() + @spec add_twin_info_to_contract(map(), Chain.Hash.t(), Chain.Hash.t() | nil) :: map() def add_twin_info_to_contract(address_result, address_verified_twin_contract, _hash) when is_nil(address_verified_twin_contract), do: address_result @@ -817,7 +830,7 @@ defmodule Explorer.Chain.SmartContract do As part of inserting a new smart contract, an additional record is inserted for naming the address for reference. """ - @spec create_smart_contract(map()) :: {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()} + @spec create_smart_contract(map(), list(), list()) :: {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()} def create_smart_contract(attrs \\ %{}, external_libraries \\ [], secondary_sources \\ []) do new_contract = %__MODULE__{} @@ -886,7 +899,7 @@ defmodule Explorer.Chain.SmartContract do Used in cases when you need to update row in DB contains SmartContract, e.g. in case of changing status `partially verified` to `fully verified` (re-verify). """ - @spec update_smart_contract(map()) :: {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()} + @spec update_smart_contract(map(), list(), list()) :: {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()} def update_smart_contract(attrs \\ %{}, external_libraries \\ [], secondary_sources \\ []) do address_hash = Map.get(attrs, :address_hash) @@ -992,7 +1005,7 @@ defmodule Explorer.Chain.SmartContract do Returns `true` if found and `false` otherwise. """ - @spec verified_with_full_match?(any()) :: boolean() + @spec verified_with_full_match?(Hash.Address.t() | String.t()) :: boolean() def verified_with_full_match?(address_hash, options \\ []) def verified_with_full_match?(address_hash_str, options) when is_binary(address_hash_str) do @@ -1015,7 +1028,7 @@ defmodule Explorer.Chain.SmartContract do Returns `true` if found and `false` otherwise. """ - @spec verified?(any()) :: boolean() + @spec verified?(Hash.Address.t() | String.t()) :: boolean() def verified?(address_hash_str) when is_binary(address_hash_str) do case Chain.string_to_address_hash(address_hash_str) do {:ok, address_hash} -> @@ -1046,7 +1059,7 @@ defmodule Explorer.Chain.SmartContract do @doc """ Gets smart-contract ABI from the DB for the given address hash of smart-contract """ - @spec get_smart_contract_abi(any(), any()) :: any() + @spec get_smart_contract_abi(String.t(), any()) :: any() def get_smart_contract_abi(address_hash_string, options \\ []) def get_smart_contract_abi(address_hash_string, options) diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1822.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1822.ex index 887dc50a0655..c3da53ed8ea1 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1822.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1822.ex @@ -5,11 +5,6 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1822 do alias Explorer.Chain.{Hash, SmartContract} alias Explorer.Chain.SmartContract.Proxy - @burn_address_hash_string_32 "0x0000000000000000000000000000000000000000000000000000000000000000" - - defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string_32] - defguard is_burn_signature_or_nil(term) when is_burn_signature(term) or term == nil - # keccak256("PROXIABLE") @storage_slot_proxiable "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" diff --git a/apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex b/apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex index a05e5f0a733b..bdf2581becd0 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex @@ -64,7 +64,7 @@ defmodule Explorer.Chain.SmartContractAdditionalSource do @doc """ Returns all additional sources for the given smart-contract address hash """ - @spec get_contract_additional_sources(SmartContract.t(), Keyword.t()) :: [__MODULE__.t()] + @spec get_contract_additional_sources(SmartContract.t() | nil, Keyword.t()) :: [__MODULE__.t()] def get_contract_additional_sources(smart_contract, options) do if smart_contract do all_additional_sources_query = From 8ebcb7dae7139df18963dfe32d83056c9529f26c Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 14 Nov 2023 11:56:17 +0300 Subject: [PATCH 040/607] Define smart-contract guards in a single place --- apps/explorer/lib/explorer/chain/smart_contract.ex | 1 + apps/explorer/lib/explorer/chain/smart_contract/proxy.ex | 7 ++----- .../lib/explorer/chain/smart_contract/proxy/eip_1967.ex | 7 ++----- .../lib/explorer/chain/smart_contract/proxy/master_copy.ex | 4 +--- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index 2d5a04863623..f3c69decd3f8 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -42,6 +42,7 @@ defmodule Explorer.Chain.SmartContract do @burn_address_hash_string_32 "0x0000000000000000000000000000000000000000000000000000000000000000" defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string_32] + defguard is_burn_signature_or_nil(term) when is_burn_signature(term) or term == nil defguard is_burn_signature_extended(term) when is_burn_signature(term) or term == @burn_address_hash_string @doc """ diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex index fc694d8e5630..5fd7489ccef4 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -13,6 +13,8 @@ defmodule Explorer.Chain.SmartContract.Proxy do string_to_address_hash: 1 ] + import Explorer.Chain.SmartContract, only: [is_burn_signature_or_nil: 1] + # supported signatures: # 5c60da1b = keccak256(implementation()) @implementation_signature "5c60da1b" @@ -21,13 +23,8 @@ defmodule Explorer.Chain.SmartContract.Proxy do # aaf10f42 = keccak256(getAddress(bytes32)) @get_address_signature "21f8a721" - @burn_address_hash_string_32 "0x0000000000000000000000000000000000000000000000000000000000000000" - @typep api? :: {:api?, true | false} - defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string_32] - defguard is_burn_signature_or_nil(term) when is_burn_signature(term) or term == nil - @doc """ Fetches into DB proxy contract implementation's address and name from different proxy patterns """ diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex index c6f500492fa2..125f97be55be 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex @@ -7,15 +7,12 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do alias Explorer.Chain.SmartContract.Proxy alias Explorer.Chain.SmartContract.Proxy.Basic + import Explorer.Chain.SmartContract, only: [is_burn_signature_or_nil: 1] + # supported signatures: # 5c60da1b = keccak256(implementation()) @implementation_signature "5c60da1b" - @burn_address_hash_string_32 "0x0000000000000000000000000000000000000000000000000000000000000000" - - defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string_32] - defguard is_burn_signature_or_nil(term) when is_burn_signature(term) or term == nil - @storage_slot_logic_contract_address "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" # to be precise, it is not the part of the EIP-1967 standard, but still uses the same pattern diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex index fb0df17aaa3c..70966ef4bba5 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex @@ -7,9 +7,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.MasterCopy do alias Explorer.Chain.{Hash, SmartContract} alias Explorer.Chain.SmartContract.Proxy - @burn_address_hash_string_32 "0x0000000000000000000000000000000000000000000000000000000000000000" - - defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string_32] + import Explorer.Chain.SmartContract, only: [is_burn_signature: 1] @doc """ Gets implementation address for proxy contract from master-copy pattern From 284b49dbf5ced38f908e2d3c9e10013002d95423 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 14 Nov 2023 12:47:14 +0300 Subject: [PATCH 041/607] Fix tests after rebasing --- .../api/v2/address_controller_test.exs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index 411803430cd0..93ed6e65f722 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -2681,5 +2681,29 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000001"} end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) end end From f004e55767d5e71afbc016ed8aefd80af7601130 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Wed, 15 Nov 2023 15:08:10 +0300 Subject: [PATCH 042/607] Add setup :verify_on_exit! to all tests using json_rpc mox --- .../controllers/address_read_contract_controller_test.exs | 2 ++ .../controllers/address_read_proxy_controller_test.exs | 2 ++ .../controllers/address_write_contract_controller_test.exs | 2 ++ .../controllers/address_write_proxy_controller_test.exs | 2 ++ .../controllers/api/rpc/contract_controller_test.exs | 2 ++ .../controllers/api/rpc/transaction_controller_test.exs | 2 ++ .../controllers/api/v2/address_controller_test.exs | 2 ++ .../controllers/api/v2/smart_contract_controller_test.exs | 2 ++ .../controllers/tokens/read_contract_controller_test.exs | 2 ++ .../transaction_internal_transaction_controller_test.exs | 2 ++ .../controllers/transaction_log_controller_test.exs | 2 ++ .../controllers/transaction_token_transfer_controller_test.exs | 2 ++ .../test/block_scout_web/features/viewing_transactions_test.exs | 2 ++ apps/ethereum_jsonrpc/test/ethereum_jsonrpc/contract_test.exs | 2 ++ apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs | 2 ++ apps/explorer/test/explorer/chain/address_test.exs | 2 ++ .../test/explorer/chain/cache/gas_price_oracle_test.exs | 2 ++ apps/explorer/test/explorer/chain/log_test.exs | 2 ++ apps/explorer/test/explorer/chain/supply/rsk_test.exs | 2 ++ apps/explorer/test/explorer/chain/transaction_test.exs | 2 ++ apps/explorer/test/explorer/chain_spec/geth/importer_test.exs | 2 ++ apps/explorer/test/explorer/chain_spec/parity/importer_test.exs | 2 ++ 22 files changed, 44 insertions(+) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs index 8537ecd4e7a3..37f279b32562 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs @@ -7,6 +7,8 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do import Mox + setup :verify_on_exit! + describe "GET index/3" do setup :set_mox_global diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs index 1b49b35d3979..14b42c6ae55f 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs @@ -7,6 +7,8 @@ defmodule BlockScoutWeb.AddressReadProxyControllerTest do import Mox + setup :verify_on_exit! + describe "GET index/3" do setup :set_mox_global diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs index 30caf25e7273..beb197b8b592 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs @@ -9,6 +9,8 @@ defmodule BlockScoutWeb.AddressWriteContractControllerTest do import Mox + setup :verify_on_exit! + describe "GET index/3" do setup :set_mox_global diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs index 7225e06e7b57..377f8f1a3881 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs @@ -7,6 +7,8 @@ defmodule BlockScoutWeb.AddressWriteProxyControllerTest do import Mox + setup :verify_on_exit! + describe "GET index/3" do setup :set_mox_global diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs index f715149a9961..89122b8369b4 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs @@ -4,6 +4,8 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do import Mox + setup :verify_on_exit! + def prepare_contracts do insert(:contract_address) {:ok, dt_1, _} = DateTime.from_iso8601("2022-09-20 10:00:00Z") diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs index e9e08b6a7127..4a828099ce6e 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs @@ -5,6 +5,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do @moduletag capture_log: true + setup :verify_on_exit! + describe "gettxreceiptstatus" do test "with missing txhash", %{conn: conn} do params = %{ diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index 93ed6e65f722..186fd19b745f 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -27,6 +27,8 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do setup :set_mox_global + setup :verify_on_exit! + describe "/addresses/{address_hash}" do test "get 404 on non existing address", %{conn: conn} do address = build(:address) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index 87e2c0455dca..c6c713b86aeb 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -11,6 +11,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do setup :set_mox_from_context + setup :verify_on_exit! + describe "/smart-contracts/{address_hash}" do test "get 404 on non existing SC", %{conn: conn} do address = build(:address) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/tokens/read_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/tokens/read_contract_controller_test.exs index 589d53a71664..7a03cfba35f7 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/tokens/read_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/read_contract_controller_test.exs @@ -3,6 +3,8 @@ defmodule BlockScoutWeb.Tokens.ContractControllerTest do import Mox + setup :verify_on_exit! + describe "GET index/3" do test "with invalid address hash", %{conn: conn} do conn = get(conn, token_read_contract_path(BlockScoutWeb.Endpoint, :index, "invalid_address")) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs index 4969db368530..cd02afca2abb 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs @@ -8,6 +8,8 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do alias Explorer.Chain.InternalTransaction alias Explorer.ExchangeRates.Token + setup :verify_on_exit! + describe "GET index/3" do test "with missing transaction", %{conn: conn} do hash = transaction_hash() diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs index fc9ced3bba5f..8fd02a2bd08f 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs @@ -8,6 +8,8 @@ defmodule BlockScoutWeb.TransactionLogControllerTest do alias Explorer.Chain.Address alias Explorer.ExchangeRates.Token + setup :verify_on_exit! + describe "GET index/2" do test "with invalid transaction hash", %{conn: conn} do conn = get(conn, transaction_log_path(conn, :index, "invalid_transaction_string")) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs index 273a53286d62..1a4dd9544731 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs @@ -7,6 +7,8 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do alias Explorer.ExchangeRates.Token + setup :verify_on_exit! + describe "GET index/3" do test "load token transfers", %{conn: conn} do EthereumJSONRPC.Mox diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs index 2d6eb7158305..3048d057cdb7 100644 --- a/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs @@ -10,6 +10,8 @@ defmodule BlockScoutWeb.ViewingTransactionsTest do setup :set_mox_global + setup :verify_on_exit! + setup do block = insert(:block, %{ diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/contract_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/contract_test.exs index ef2f58ed4a37..76100d469b3c 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/contract_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/contract_test.exs @@ -5,6 +5,8 @@ defmodule EthereumJSONRPC.ContractTest do import Mox + setup :verify_on_exit! + describe "execute_contract_functions/3" do test "executes the functions with and without the block_number, returns results in order" do json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs index 9f3620039ae2..853a5643ae02 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs @@ -7,6 +7,8 @@ defmodule EthereumJSONRPC.GethTest do @moduletag :no_nethermind + setup :verify_on_exit! + describe "fetch_internal_transactions/2" do # Infura Mainnet does not support debug_traceTransaction, so this cannot be tested expect in Mox setup do diff --git a/apps/explorer/test/explorer/chain/address_test.exs b/apps/explorer/test/explorer/chain/address_test.exs index 213913c766a8..5b01b27ade3d 100644 --- a/apps/explorer/test/explorer/chain/address_test.exs +++ b/apps/explorer/test/explorer/chain/address_test.exs @@ -6,6 +6,8 @@ defmodule Explorer.Chain.AddressTest do alias Explorer.Chain.Address alias Explorer.Repo + setup :verify_on_exit! + describe "changeset/2" do test "with valid attributes" do params = params_for(:address) diff --git a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs index d7004d11d0ed..55678c045ac2 100644 --- a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs +++ b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs @@ -44,6 +44,8 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do "uncles" => [] } + setup :verify_on_exit! + describe "get_average_gas_price/4" do test "returns nil percentile values if no blocks in the DB" do expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) diff --git a/apps/explorer/test/explorer/chain/log_test.exs b/apps/explorer/test/explorer/chain/log_test.exs index 6188903de08a..6842a6b979fb 100644 --- a/apps/explorer/test/explorer/chain/log_test.exs +++ b/apps/explorer/test/explorer/chain/log_test.exs @@ -11,6 +11,8 @@ defmodule Explorer.Chain.LogTest do doctest Log + setup :verify_on_exit! + describe "changeset/2" do test "accepts valid attributes" do params = diff --git a/apps/explorer/test/explorer/chain/supply/rsk_test.exs b/apps/explorer/test/explorer/chain/supply/rsk_test.exs index a2e59294d221..5592953d5f3e 100644 --- a/apps/explorer/test/explorer/chain/supply/rsk_test.exs +++ b/apps/explorer/test/explorer/chain/supply/rsk_test.exs @@ -9,6 +9,8 @@ defmodule Explorer.Chain.Supply.RSKTest do @coin_address "0x0000000000000000000000000000000001000006" @mult 1_000_000_000_000_000_000 + setup :verify_on_exit! + test "total is 21_000_000" do assert Decimal.equal?(RSK.total(), Decimal.new(21_000_000)) end diff --git a/apps/explorer/test/explorer/chain/transaction_test.exs b/apps/explorer/test/explorer/chain/transaction_test.exs index db2823b47ef6..d204d2822325 100644 --- a/apps/explorer/test/explorer/chain/transaction_test.exs +++ b/apps/explorer/test/explorer/chain/transaction_test.exs @@ -8,6 +8,8 @@ defmodule Explorer.Chain.TransactionTest do doctest Transaction + setup :verify_on_exit! + describe "changeset/2" do test "with valid attributes" do assert %Changeset{valid?: true} = diff --git a/apps/explorer/test/explorer/chain_spec/geth/importer_test.exs b/apps/explorer/test/explorer/chain_spec/geth/importer_test.exs index a2c66455df52..e8aa80eac1ea 100644 --- a/apps/explorer/test/explorer/chain_spec/geth/importer_test.exs +++ b/apps/explorer/test/explorer/chain_spec/geth/importer_test.exs @@ -11,6 +11,8 @@ defmodule Explorer.ChainSpec.Geth.ImporterTest do setup :set_mox_global + setup :verify_on_exit! + @geth_genesis "#{File.cwd!()}/test/support/fixture/chain_spec/qdai_genesis.json" |> File.read!() |> Jason.decode!() diff --git a/apps/explorer/test/explorer/chain_spec/parity/importer_test.exs b/apps/explorer/test/explorer/chain_spec/parity/importer_test.exs index 3f74ea550b67..e610fd661dcd 100644 --- a/apps/explorer/test/explorer/chain_spec/parity/importer_test.exs +++ b/apps/explorer/test/explorer/chain_spec/parity/importer_test.exs @@ -12,6 +12,8 @@ defmodule Explorer.ChainSpec.Parity.ImporterTest do setup :set_mox_global + setup :verify_on_exit! + @chain_spec "#{File.cwd!()}/test/support/fixture/chain_spec/foundation.json" |> File.read!() |> Jason.decode!() From d419fd1461cce05a8f1afd96827fdf5a49007b2d Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 15 Nov 2023 15:20:35 +0300 Subject: [PATCH 043/607] Process reviewer comment --- .../lib/block_scout_web/views/address_view.ex | 4 ++-- .../lib/explorer/chain/address/name.ex | 7 ++----- .../lib/explorer/chain/smart_contract.ex | 2 +- .../lib/explorer/chain/smart_contract/proxy.ex | 18 +++++++++--------- .../chain/smart_contract/proxy/basic.ex | 7 ++++--- .../chain/smart_contract/proxy/eip_1167.ex | 11 +++++++++++ .../chain/smart_contract/proxy/eip_1822.ex | 8 ++++---- .../chain/smart_contract/proxy/eip_1967.ex | 10 +++++----- .../chain/smart_contract/proxy/eip_930.ex | 7 ++++--- .../chain/smart_contract/proxy/master_copy.ex | 8 ++++---- 10 files changed, 46 insertions(+), 36 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex index 3884c86db8dd..af46f31ca25c 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex @@ -265,8 +265,8 @@ defmodule BlockScoutWeb.AddressView do def smart_contract_is_proxy?(address, options \\ []) - def smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{} = smart_contract}, _options) do - Proxy.proxy_contract?(smart_contract) + def smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{} = smart_contract}, options) do + Proxy.proxy_contract?(smart_contract, options) end def smart_contract_is_proxy?(%Address{smart_contract: _}, _), do: false diff --git a/apps/explorer/lib/explorer/chain/address/name.ex b/apps/explorer/lib/explorer/chain/address/name.ex index 91382eff6648..2072ca3114e8 100644 --- a/apps/explorer/lib/explorer/chain/address/name.ex +++ b/apps/explorer/lib/explorer/chain/address/name.ex @@ -63,10 +63,7 @@ defmodule Explorer.Chain.Address.Name do lock: "FOR NO KEY UPDATE" ) - repo.update_all( - from(n in __MODULE__, join: s in subquery(query), on: n.address_hash == s.address_hash and n.name == s.name), - set: [primary: false] - ) + repo.update_all(query, set: [primary: false]) {:ok, []} end @@ -84,7 +81,7 @@ defmodule Explorer.Chain.Address.Name do } %__MODULE__{} - |> __MODULE__.changeset(params) + |> changeset(params) |> repo.insert(on_conflict: :nothing, conflict_target: [:address_hash, :name]) end diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index f3c69decd3f8..3773ed94640f 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -1068,7 +1068,7 @@ defmodule Explorer.Chain.SmartContract do with {:ok, implementation_address_hash} <- Chain.string_to_address_hash(address_hash_string), implementation_smart_contract = implementation_address_hash - |> __MODULE__.address_hash_to_smart_contract(options), + |> address_hash_to_smart_contract(options), false <- is_nil(implementation_smart_contract) do implementation_smart_contract |> Map.get(:abi) diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex index 5fd7489ccef4..87481d89d7b8 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -58,15 +58,15 @@ defmodule Explorer.Chain.SmartContract.Proxy do @doc """ Checks if smart-contract is proxy. Returns true/false. """ - @spec proxy_contract?(SmartContract.t()) :: boolean() - def proxy_contract?(smart_contract) do + @spec proxy_contract?(SmartContract.t(), any()) :: boolean() + def proxy_contract?(smart_contract, options \\ []) do {:ok, burn_address_hash} = string_to_address_hash(SmartContract.burn_address_hash_string()) if smart_contract.implementation_address_hash && smart_contract.implementation_address_hash.bytes !== burn_address_hash.bytes do true else - {implementation_address_hash_string, _} = SmartContract.get_implementation_address_hash(smart_contract, []) + {implementation_address_hash_string, _} = SmartContract.get_implementation_address_hash(smart_contract, options) with false <- is_nil(implementation_address_hash_string), {:ok, implementation_address_hash} <- string_to_address_hash(implementation_address_hash_string), @@ -174,21 +174,21 @@ defmodule Explorer.Chain.SmartContract.Proxy do cond do implementation_method_abi -> - Basic.get_implementation_address(@implementation_signature, proxy_address_hash, proxy_abi) + Basic.get_implementation_address_hash_string(@implementation_signature, proxy_address_hash, proxy_abi) get_implementation_method_abi -> - Basic.get_implementation_address(@get_implementation_signature, proxy_address_hash, proxy_abi) + Basic.get_implementation_address_hash_string(@get_implementation_signature, proxy_address_hash, proxy_abi) master_copy_method_abi -> - MasterCopy.get_implementation_address(proxy_address_hash) + MasterCopy.get_implementation_address_hash_string(proxy_address_hash) get_address_method_abi -> - EIP930.get_implementation_address(@get_address_signature, proxy_address_hash, proxy_abi) + EIP930.get_implementation_address_hash_string(@get_address_signature, proxy_address_hash, proxy_abi) true -> - EIP1967.get_implementation_address(proxy_address_hash) || + EIP1967.get_implementation_address_hash_string(proxy_address_hash) || EIP1167.get_implementation_address(proxy_address_hash) || - EIP1822.get_implementation_address(proxy_address_hash) + EIP1822.get_implementation_address_hash_string(proxy_address_hash) end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex index ed793c662b93..dc4f305900ee 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex @@ -3,13 +3,14 @@ defmodule Explorer.Chain.SmartContract.Proxy.Basic do Module for fetching proxy implementation from specific smart-contract getter """ + alias Explorer.Chain.SmartContract alias Explorer.SmartContract.Reader @doc """ - Gets implementation if proxy contract from getter. + Gets implementation hash string of proxy contract from getter. """ - @spec get_implementation_address(any(), binary(), any()) :: nil | binary() - def get_implementation_address(signature, proxy_address_hash, abi) do + @spec get_implementation_address_hash_string(binary, binary, SmartContract.abi()) :: nil | binary + def get_implementation_address_hash_string(signature, proxy_address_hash, abi) do implementation_address = case Reader.query_contract( proxy_address_hash, diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex index c76900ece45b..b7b185612018 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex @@ -5,6 +5,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1167 do alias Explorer.Chain alias Explorer.Chain.{Address, Hash, SmartContract} + alias Explorer.Chain.SmartContract.Proxy import Ecto.Query, only: [from: 2] @@ -32,6 +33,16 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1167 do end end + @doc """ + Get implementation address hash string following EIP-1167 + """ + @spec get_implementation_address_hash_string(Hash.Address.t(), Keyword.t()) :: SmartContract.t() | nil + def get_implementation_address_hash_string(address_hash, options \\ []) do + get_implementation_address(address_hash, options) + + Proxy.abi_decode_address_output(address_hash) + end + defp get_proxy_eip_1167(contract_bytecode, options) do case contract_bytecode do "363d3d373d3d3d363d73" <> <> <> _ -> diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1822.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1822.ex index c3da53ed8ea1..be89f8080f5c 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1822.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1822.ex @@ -2,17 +2,17 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1822 do @moduledoc """ Module for fetching proxy implementation from https://eips.ethereum.org/EIPS/eip-1822 Universal Upgradeable Proxy Standard (UUPS) """ - alias Explorer.Chain.{Hash, SmartContract} + alias Explorer.Chain.Hash alias Explorer.Chain.SmartContract.Proxy # keccak256("PROXIABLE") @storage_slot_proxiable "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" @doc """ - Get implementation address following EIP-1967 + Get implementation address hash string following EIP-1822 """ - @spec get_implementation_address(Hash.Address.t()) :: SmartContract.t() | nil - def get_implementation_address(proxy_address_hash) do + @spec get_implementation_address_hash_string(Hash.Address.t()) :: nil | binary + def get_implementation_address_hash_string(proxy_address_hash) do json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) proxiable_contract_address_hash_string = diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex index 125f97be55be..e94cfdf3b00e 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do Module for fetching proxy implementation from https://eips.ethereum.org/EIPS/eip-1967 (Proxy Storage Slots) """ alias EthereumJSONRPC.Contract - alias Explorer.Chain.{Hash, SmartContract} + alias Explorer.Chain.Hash alias Explorer.Chain.SmartContract.Proxy alias Explorer.Chain.SmartContract.Proxy.Basic @@ -21,10 +21,10 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do @storage_slot_openzeppelin_contract_address "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3" @doc """ - Get implementation address following EIP-1967 + Get implementation address hash string following EIP-1967 """ - @spec get_implementation_address(Hash.Address.t()) :: SmartContract.t() | nil - def get_implementation_address(proxy_address_hash) do + @spec get_implementation_address_hash_string(Hash.Address.t()) :: nil | binary + def get_implementation_address_hash_string(proxy_address_hash) do json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) logic_contract_address_hash_string = @@ -80,7 +80,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do {:ok, beacon_contract_address} -> case beacon_contract_address |> Proxy.abi_decode_address_output() - |> Basic.get_implementation_address( + |> Basic.get_implementation_address_hash_string( @implementation_signature, implementation_method_abi ) do diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_930.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_930.ex index 964346df37eb..600128191aed 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_930.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_930.ex @@ -3,16 +3,17 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP930 do Module for fetching proxy implementation from smart-contract getter following https://github.com/ethereum/EIPs/issues/930 """ + alias Explorer.Chain.SmartContract alias Explorer.Chain.SmartContract.Proxy.Basic alias Explorer.SmartContract.Reader @storage_slot_logic_contract_address "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" @doc """ - Gets implementation if proxy contract from getter. + Gets implementation hash string of proxy contract from getter. """ - @spec get_implementation_address(any(), binary(), any()) :: nil | binary() - def get_implementation_address(signature, proxy_address_hash, abi) do + @spec get_implementation_address_hash_string(binary, binary, SmartContract.abi()) :: nil | binary + def get_implementation_address_hash_string(signature, proxy_address_hash, abi) do implementation_address = case Reader.query_contract( proxy_address_hash, diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex index 70966ef4bba5..97b927981392 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex @@ -4,16 +4,16 @@ defmodule Explorer.Chain.SmartContract.Proxy.MasterCopy do """ alias EthereumJSONRPC.Contract - alias Explorer.Chain.{Hash, SmartContract} + alias Explorer.Chain.Hash alias Explorer.Chain.SmartContract.Proxy import Explorer.Chain.SmartContract, only: [is_burn_signature: 1] @doc """ - Gets implementation address for proxy contract from master-copy pattern + Gets implementation address hash string for proxy contract from master-copy pattern """ - @spec get_implementation_address(Hash.Address.t()) :: SmartContract.t() | nil - def get_implementation_address(proxy_address_hash) do + @spec get_implementation_address_hash_string(Hash.Address.t()) :: nil | binary + def get_implementation_address_hash_string(proxy_address_hash) do json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) master_copy_storage_pointer = "0x0" From fff31fedfbe50ae900ad036963ad88806b83649d Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 15 Nov 2023 15:42:05 +0300 Subject: [PATCH 044/607] Process last reviewer comment --- .../lib/explorer/chain/address/name.ex | 5 +++- .../chain/smart_contract/proxy/eip_1967.ex | 24 +++++++------------ 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/address/name.ex b/apps/explorer/lib/explorer/chain/address/name.ex index 2072ca3114e8..533ef2878911 100644 --- a/apps/explorer/lib/explorer/chain/address/name.ex +++ b/apps/explorer/lib/explorer/chain/address/name.ex @@ -63,7 +63,10 @@ defmodule Explorer.Chain.Address.Name do lock: "FOR NO KEY UPDATE" ) - repo.update_all(query, set: [primary: false]) + repo.update_all( + from(n in __MODULE__, join: s in subquery(query), on: n.address_hash == s.address_hash and n.name == s.name), + set: [primary: false] + ) {:ok, []} end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex index e94cfdf3b00e..aea3fa39be04 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex @@ -27,26 +27,18 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do def get_implementation_address_hash_string(proxy_address_hash) do json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) - logic_contract_address_hash_string = + implementation_address_hash_string = Proxy.get_implementation_from_storage( proxy_address_hash, @storage_slot_logic_contract_address, json_rpc_named_arguments - ) - - beacon_contract_address_hash_string = - fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) - - openzeppelin_style_contract_address_hash_string = - Proxy.get_implementation_from_storage( - proxy_address_hash, - @storage_slot_openzeppelin_contract_address, - json_rpc_named_arguments - ) - - implementation_address_hash_string = - logic_contract_address_hash_string || beacon_contract_address_hash_string || - openzeppelin_style_contract_address_hash_string + ) || + fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) || + Proxy.get_implementation_from_storage( + proxy_address_hash, + @storage_slot_openzeppelin_contract_address, + json_rpc_named_arguments + ) Proxy.abi_decode_address_output(implementation_address_hash_string) end From f8782d04ce3af08e2914b0290e46c91ea2efd72e Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Wed, 15 Nov 2023 18:19:14 +0300 Subject: [PATCH 045/607] Fix tests --- .../api/v2/address_controller_test.exs | 4 +- .../api/v2/smart_contract_controller_test.exs | 26 +++++----- .../smart_contract_controller_test.exs | 52 +++++++++---------- ...n_internal_transaction_controller_test.exs | 7 --- .../transaction_log_controller_test.exs | 12 ----- ...saction_token_transfer_controller_test.exs | 13 ----- .../features/viewing_transactions_test.exs | 2 - .../explorer/chain/smart_contract/proxy.ex | 2 +- .../chain/smart_contract/proxy/eip_1167.ex | 49 +++++++++-------- .../chain/cache/gas_price_oracle_test.exs | 2 - .../chain/smart_contract/proxy_test.exs | 26 ---------- .../explorer/chain/smart_contract_test.exs | 12 ++--- 12 files changed, 72 insertions(+), 135 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index 186fd19b745f..d194e233177b 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -2681,7 +2681,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do ] }, _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000001"} + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) |> expect(:json_rpc, fn %{ id: 0, @@ -2705,7 +2705,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do ] }, _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000001"} end) end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index c6c713b86aeb..067ae4b08c7f 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -1922,8 +1922,6 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} end) - request_zero_implementations() - expect( EthereumJSONRPC.Mox, :json_rpc, @@ -2034,8 +2032,6 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} end) - request_zero_implementations() - expect( EthereumJSONRPC.Mox, :json_rpc, @@ -2121,8 +2117,6 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} end) - request_zero_implementations() - expect( EthereumJSONRPC.Mox, :json_rpc, @@ -2192,8 +2186,6 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} end) - request_zero_implementations() - expect( EthereumJSONRPC.Mox, :json_rpc, @@ -2262,8 +2254,6 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} end) - request_zero_implementations() - expect( EthereumJSONRPC.Mox, :json_rpc, @@ -2356,8 +2346,6 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} end) - request_zero_implementations() - contract = insert(:smart_contract) request = get(conn, "/api/v2/smart-contracts/#{contract.address_hash}/methods-write-proxy") @@ -2477,6 +2465,18 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do defp request_zero_implementations do EthereumJSONRPC.Mox + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) |> expect(:json_rpc, fn %{ id: 0, method: "eth_getStorageAt", @@ -2499,7 +2499,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do ] }, _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000001"} end) end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs index eb8c1973ef98..aec5f0d1d327 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs @@ -122,8 +122,6 @@ defmodule BlockScoutWeb.SmartContractControllerTest do ) blockchain_get_implementation_mock() - blockchain_get_implementation_mock_empty() - blockchain_get_implementation_mock_empty() path = smart_contract_path(BlockScoutWeb.Endpoint, :index, @@ -161,8 +159,6 @@ defmodule BlockScoutWeb.SmartContractControllerTest do ) blockchain_get_implementation_mock_2() - blockchain_get_implementation_mock_empty() - blockchain_get_implementation_mock_empty() path = smart_contract_path(BlockScoutWeb.Endpoint, :index, @@ -280,32 +276,32 @@ defmodule BlockScoutWeb.SmartContractControllerTest do end defp blockchain_get_implementation_mock do - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn %{id: _, method: _, params: [_, _, _]}, _options -> - {:ok, "0xcebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"} - end - ) - end - - defp blockchain_get_implementation_mock_empty do - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn %{id: _, method: _, params: [_, _, _]}, _options -> - {:ok, "0x"} - end - ) + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0xcebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"} + end) end defp blockchain_get_implementation_mock_2 do - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn %{id: _, method: _, params: [_, _, _]}, _options -> - {:ok, "0x000000000000000000000000cebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"} - end - ) + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x000000000000000000000000cebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"} + end) end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs index cd02afca2abb..5354d84b3563 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs @@ -8,8 +8,6 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do alias Explorer.Chain.InternalTransaction alias Explorer.ExchangeRates.Token - setup :verify_on_exit! - describe "GET index/3" do test "with missing transaction", %{conn: conn} do hash = transaction_hash() @@ -77,11 +75,6 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do end test "includes USD exchange rate value for address in assigns", %{conn: conn} do - EthereumJSONRPC.Mox - |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options -> - {:ok, "100"} - end) - transaction = insert(:transaction) conn = get(conn, transaction_internal_transaction_path(BlockScoutWeb.Endpoint, :index, transaction.hash)) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs index 8fd02a2bd08f..d6f9654d7631 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs @@ -8,8 +8,6 @@ defmodule BlockScoutWeb.TransactionLogControllerTest do alias Explorer.Chain.Address alias Explorer.ExchangeRates.Token - setup :verify_on_exit! - describe "GET index/2" do test "with invalid transaction hash", %{conn: conn} do conn = get(conn, transaction_log_path(conn, :index, "invalid_transaction_string")) @@ -159,11 +157,6 @@ defmodule BlockScoutWeb.TransactionLogControllerTest do end test "includes USD exchange rate value for address in assigns", %{conn: conn} do - EthereumJSONRPC.Mox - |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options -> - {:ok, "100"} - end) - transaction = insert(:transaction) conn = get(conn, transaction_log_path(BlockScoutWeb.Endpoint, :index, transaction.hash)) @@ -172,11 +165,6 @@ defmodule BlockScoutWeb.TransactionLogControllerTest do end test "loads for transactions that created a contract", %{conn: conn} do - EthereumJSONRPC.Mox - |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options -> - {:ok, "100"} - end) - contract_address = insert(:contract_address) transaction = diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs index 1a4dd9544731..398d84dd8acd 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs @@ -11,11 +11,6 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do describe "GET index/3" do test "load token transfers", %{conn: conn} do - EthereumJSONRPC.Mox - |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options -> - {:ok, "100"} - end) - transaction = insert(:transaction) token_transfer = insert(:token_transfer, transaction: transaction) @@ -73,11 +68,6 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do end test "includes USD exchange rate value for address in assigns", %{conn: conn} do - EthereumJSONRPC.Mox - |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options -> - {:ok, "100"} - end) - transaction = insert(:transaction) conn = get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, transaction.hash)) @@ -215,9 +205,6 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) - |> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options -> - {:ok, "100"} - end) transaction = insert(:transaction_to_verified_contract) diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs index 3048d057cdb7..2d6eb7158305 100644 --- a/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs @@ -10,8 +10,6 @@ defmodule BlockScoutWeb.ViewingTransactionsTest do setup :set_mox_global - setup :verify_on_exit! - setup do block = insert(:block, %{ diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex index 87481d89d7b8..3a541ab8e7ba 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -187,7 +187,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do true -> EIP1967.get_implementation_address_hash_string(proxy_address_hash) || - EIP1167.get_implementation_address(proxy_address_hash) || + EIP1167.get_implementation_address_hash_string(proxy_address_hash) || EIP1822.get_implementation_address_hash_string(proxy_address_hash) end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex index b7b185612018..9bf1bd70e4e1 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex @@ -14,6 +14,16 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1167 do """ @spec get_implementation_address(Hash.Address.t(), Keyword.t()) :: SmartContract.t() | nil def get_implementation_address(address_hash, options \\ []) do + address_hash + |> get_implementation_address_hash_string(options) + |> implementation_to_smart_contract(options) + end + + @doc """ + Get implementation address hash string following EIP-1167 + """ + @spec get_implementation_address_hash_string(Hash.Address.t(), Keyword.t()) :: String.t() | nil + def get_implementation_address_hash_string(address_hash, options \\ []) do case Chain.select_repo(options).get(Address, address_hash) do nil -> nil @@ -25,7 +35,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1167 do %Chain.Data{bytes: contract_code_bytes} -> contract_bytecode = Base.encode16(contract_code_bytes, case: :lower) - get_proxy_eip_1167(contract_bytecode, options) + contract_bytecode |> get_proxy_eip_1167() |> Proxy.abi_decode_address_output() _ -> nil @@ -33,33 +43,26 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1167 do end end - @doc """ - Get implementation address hash string following EIP-1167 - """ - @spec get_implementation_address_hash_string(Hash.Address.t(), Keyword.t()) :: SmartContract.t() | nil - def get_implementation_address_hash_string(address_hash, options \\ []) do - get_implementation_address(address_hash, options) - - Proxy.abi_decode_address_output(address_hash) - end - - defp get_proxy_eip_1167(contract_bytecode, options) do + defp get_proxy_eip_1167(contract_bytecode) do case contract_bytecode do "363d3d373d3d3d363d73" <> <> <> _ -> - template_address = "0x" <> template_address - - query = - from( - smart_contract in SmartContract, - where: smart_contract.address_hash == ^template_address, - select: smart_contract - ) - - query - |> Chain.select_repo(options).one(timeout: 10_000) + "0x" <> template_address _ -> nil end end + + defp implementation_to_smart_contract(nil, _options), do: nil + + defp implementation_to_smart_contract(address_hash, options) do + query = + from( + smart_contract in SmartContract, + where: smart_contract.address_hash == ^address_hash + ) + + query + |> Chain.select_repo(options).one(timeout: 10_000) + end end diff --git a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs index 55678c045ac2..d7004d11d0ed 100644 --- a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs +++ b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs @@ -44,8 +44,6 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do "uncles" => [] } - setup :verify_on_exit! - describe "get_average_gas_price/4" do test "returns nil percentile values if no blocks in the DB" do expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) diff --git a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs index 4c96a032c1e2..d636e8967cd9 100644 --- a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs @@ -350,32 +350,6 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do end ) - EthereumJSONRPC.Mox - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, []) assert implementation_abi == @implementation_abi diff --git a/apps/explorer/test/explorer/chain/smart_contract_test.exs b/apps/explorer/test/explorer/chain/smart_contract_test.exs index 01ccba28002e..631cffe3724d 100644 --- a/apps/explorer/test/explorer/chain/smart_contract_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract_test.exs @@ -85,7 +85,7 @@ defmodule Explorer.Chain.SmartContractTest do ] }, _options -> - {:ok, string_implementation_address_hash} + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) |> expect(:json_rpc, fn %{ id: 0, @@ -109,7 +109,7 @@ defmodule Explorer.Chain.SmartContractTest do ] }, _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + {:ok, string_implementation_address_hash} end) assert {^string_implementation_address_hash, "proxy"} = @@ -358,7 +358,7 @@ defmodule Explorer.Chain.SmartContractTest do ] }, _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000001"} + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) |> expect(:json_rpc, fn %{ id: 0, @@ -382,7 +382,7 @@ defmodule Explorer.Chain.SmartContractTest do ] }, _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000001"} end) end @@ -903,7 +903,7 @@ defmodule Explorer.Chain.SmartContractTest do ] }, _options -> - {:ok, string_implementation_address_hash} + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) |> expect(:json_rpc, fn %{ id: 0, @@ -927,7 +927,7 @@ defmodule Explorer.Chain.SmartContractTest do ] }, _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + {:ok, string_implementation_address_hash} end) end end From f22c4b50d55c7383c1b7d08d9020089c10d64063 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Fri, 10 Nov 2023 12:08:13 +0300 Subject: [PATCH 046/607] Add testing for all CHAIN_TYPEs --- .github/workflows/config.yml | 73 +++++++++++++++---- apps/explorer/lib/explorer/chain/block.ex | 2 +- ...4094744_add_rootstock_fields_to_blocks.exs | 2 +- .../lib/indexer/fetcher/rootstock_data.ex | 8 +- 4 files changed, 64 insertions(+), 21 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 0b7346dc7b11..2d84bccc474e 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -27,11 +27,23 @@ on: env: MIX_ENV: test - OTP_VERSION: '25.2.1' - ELIXIR_VERSION: '1.14.5' - ACCOUNT_AUTH0_DOMAIN: 'blockscoutcom.us.auth0.com' + OTP_VERSION: "25.2.1" + ELIXIR_VERSION: "1.14.5" + ACCOUNT_AUTH0_DOMAIN: "blockscoutcom.us.auth0.com" jobs: + matrix-builder: + name: Build matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - id: set-matrix + run: | + echo "matrix=$matrixStringifiedObject" >> $GITHUB_OUTPUT + env: + matrixStringifiedObject: '{"chain-type": ["ethereum", "polygon_edge", "polygon_zkevm", "rsk", "suave", "stability"]}' + build-and-cache: name: Build and Cache deps runs-on: ubuntu-latest @@ -142,10 +154,16 @@ jobs: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" - run: mix format --check-formatted + dialyzer: + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }} name: Dialyzer static analysis runs-on: ubuntu-latest - needs: build-and-cache + needs: + - build-and-cache + - matrix-builder steps: - uses: actions/checkout@v4 - uses: erlef/setup-beam@v1 @@ -169,18 +187,22 @@ jobs: id: dialyzer-cache with: path: priv/plts - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-mixlockhash_25-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-${{ matrix.chain-type }}-dialyzer-mixlockhash_25-${{ hashFiles('mix.lock') }} restore-keys: | - ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-" + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-${{ matrix.chain-type }}-dialyzer-" - name: Conditionally build Dialyzer Cache if: steps.dialyzer-cache.output.cache-hit != 'true' run: | mkdir -p priv/plts mix dialyzer --plt + env: + CHAIN_TYPE: ${{ matrix.chain-type }} - name: Run Dialyzer run: mix dialyzer --halt-exit-status + env: + CHAIN_TYPE: ${{ matrix.chain-type }} gettext: name: Missing translation keys check @@ -370,9 +392,14 @@ jobs: working-directory: apps/block_scout_web/assets test_nethermind_mox_ethereum_jsonrpc: + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }} name: EthereumJSONRPC Tests runs-on: ubuntu-latest - needs: build-and-cache + needs: + - build-and-cache + - matrix-builder services: postgres: image: postgres @@ -423,10 +450,16 @@ jobs: PGUSER: postgres ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.Mox" ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" + CHAIN_TYPE: "${{ matrix.chain-type }}" test_nethermind_mox_explorer: + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }} name: Explorer Tests runs-on: ubuntu-latest - needs: build-and-cache + needs: + - build-and-cache + - matrix-builder services: postgres: image: postgres @@ -488,10 +521,16 @@ jobs: PGUSER: postgres ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.Mox" ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" + CHAIN_TYPE: "${{ matrix.chain-type }}" test_nethermind_mox_indexer: + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }} name: Indexer Tests runs-on: ubuntu-latest - needs: build-and-cache + needs: + - build-and-cache + - matrix-builder services: postgres: image: postgres @@ -529,7 +568,6 @@ jobs: restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" - - run: ./bin/install_chrome_headless.sh - name: mix test --exclude no_nethermind @@ -546,15 +584,20 @@ jobs: PGUSER: postgres ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.Mox" ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" - + CHAIN_TYPE: "${{ matrix.chain-type }}" test_nethermind_mox_block_scout_web: + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }} name: Blockscout Web Tests runs-on: ubuntu-latest - needs: build-and-cache + needs: + - build-and-cache + - matrix-builder services: redis_db: - image: 'redis:alpine' - ports: + image: "redis:alpine" + ports: - 6379:6379 postgres: @@ -593,7 +636,6 @@ jobs: restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" - - name: Restore Explorer NPM Cache uses: actions/cache@v2 id: explorer-npm-cache @@ -638,3 +680,4 @@ jobs: ACCOUNT_ENABLED: "true" ACCOUNT_REDIS_URL: "redis://localhost:6379" SOURCIFY_INTEGRATION_ENABLED: "true" + CHAIN_TYPE: "${{ matrix.chain-type }}" diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index b2fa9d97f2a4..b41941fdc91f 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -11,7 +11,7 @@ defmodule Explorer.Chain.Block do alias Explorer.Chain.Block.{Reward, SecondDegreeRelation} @optional_attrs ~w(size refetch_needed total_difficulty difficulty base_fee_per_gas)a - |> (&(case Application.compile_env(:explorer, :chain_type) == "rsk" do + |> (&(case Application.compile_env(:explorer, :chain_type) do "rsk" -> &1 ++ ~w(minimum_gas_price bitcoin_merged_mining_header bitcoin_merged_mining_coinbase_transaction bitcoin_merged_mining_merkle_proof hash_for_merged_mining)a diff --git a/apps/explorer/priv/rsk/migrations/20230724094744_add_rootstock_fields_to_blocks.exs b/apps/explorer/priv/rsk/migrations/20230724094744_add_rootstock_fields_to_blocks.exs index a5ebc7c614b5..40bbbc79793b 100644 --- a/apps/explorer/priv/rsk/migrations/20230724094744_add_rootstock_fields_to_blocks.exs +++ b/apps/explorer/priv/rsk/migrations/20230724094744_add_rootstock_fields_to_blocks.exs @@ -1,4 +1,4 @@ -defmodule Explorer.Repo.Migrations.AddRootstockFieldsToBlocks do +defmodule Explorer.Repo.RSK.Migrations.AddRootstockFieldsToBlocks do use Ecto.Migration def change do diff --git a/apps/indexer/lib/indexer/fetcher/rootstock_data.ex b/apps/indexer/lib/indexer/fetcher/rootstock_data.ex index dc6b0761be64..09b683e6563a 100644 --- a/apps/indexer/lib/indexer/fetcher/rootstock_data.ex +++ b/apps/indexer/lib/indexer/fetcher/rootstock_data.ex @@ -55,11 +55,11 @@ defmodule Indexer.Fetcher.RootstockData do state = %__MODULE__{ blocks_to_fetch: nil, - interval: Application.get_env(:indexer, __MODULE__)[:interval] || @interval, + interval: opts[:interval] || Application.get_env(:indexer, __MODULE__)[:interval], json_rpc_named_arguments: json_rpc_named_arguments, - batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @batch_size, - max_concurrency: Application.get_env(:indexer, __MODULE__)[:max_concurrency] || @concurrency, - db_batch_size: Application.get_env(:indexer, __MODULE__)[:db_batch_size] || @db_batch_size + batch_size: opts[:batch_size] || Application.get_env(:indexer, __MODULE__)[:batch_size], + max_concurrency: opts[:max_concurrency] || Application.get_env(:indexer, __MODULE__)[:max_concurrency], + db_batch_size: opts[:db_batch_size] || Application.get_env(:indexer, __MODULE__)[:db_batch_size] } Process.send_after(self(), :fetch_rootstock_data, state.interval) From cb181284e3723cd4ea241778974df1f6fb08bd34 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 15 Nov 2023 19:54:54 +0300 Subject: [PATCH 047/607] use get_smart_contract_query --- .../lib/explorer/chain/smart_contract.ex | 18 +++++++++++------- .../chain/smart_contract/proxy/eip_1167.ex | 11 ++--------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index 3773ed94640f..edf9dda5441e 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -1082,6 +1082,17 @@ defmodule Explorer.Chain.SmartContract do [] end + @doc """ + Gets smart-contract by address hash + """ + @spec get_smart_contract_query(Hash.Address.t() | binary) :: Ecto.Query.t() + def get_smart_contract_query(address_hash) do + from( + smart_contract in __MODULE__, + where: smart_contract.address_hash == ^address_hash + ) + end + defp upsert_contract_methods(%Changeset{changes: %{abi: abi}} = changeset) do ContractMethod.upsert_from_abi(abi, get_field(changeset, :address_hash)) @@ -1226,13 +1237,6 @@ defmodule Explorer.Chain.SmartContract do end end - defp get_smart_contract_query(address_hash) do - from( - smart_contract in __MODULE__, - where: smart_contract.address_hash == ^address_hash - ) - end - defp check_verified_with_full_match(address_hash, options) do smart_contract = address_hash_to_smart_contract_without_twin(address_hash, options) diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex index 9bf1bd70e4e1..5095c6b17c5e 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex @@ -7,8 +7,6 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1167 do alias Explorer.Chain.{Address, Hash, SmartContract} alias Explorer.Chain.SmartContract.Proxy - import Ecto.Query, only: [from: 2] - @doc """ Get implementation address following EIP-1167 """ @@ -56,13 +54,8 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1167 do defp implementation_to_smart_contract(nil, _options), do: nil defp implementation_to_smart_contract(address_hash, options) do - query = - from( - smart_contract in SmartContract, - where: smart_contract.address_hash == ^address_hash - ) - - query + address_hash + |> SmartContract.get_smart_contract_query() |> Chain.select_repo(options).one(timeout: 10_000) end end From e188d83aeccf01f06bdecbc4b73ad892d90ec18c Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 15 Nov 2023 21:03:07 +0300 Subject: [PATCH 048/607] Remove unused imports --- .../api/v2/smart_contract_controller_test.exs | 40 ------------------- .../transaction_log_controller_test.exs | 2 - 2 files changed, 42 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index 067ae4b08c7f..2164911f25f6 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -2462,44 +2462,4 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "solidity" end end - - defp request_zero_implementations do - EthereumJSONRPC.Mox - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000001"} - end) - end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs index d6f9654d7631..57f8933c243c 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs @@ -1,8 +1,6 @@ defmodule BlockScoutWeb.TransactionLogControllerTest do use BlockScoutWeb.ConnCase - import Mox - import BlockScoutWeb.WebRouter.Helpers, only: [transaction_log_path: 3] alias Explorer.Chain.Address From 451a1ad4b9a05467bec2b80b66d1609ebd41f044 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 15 Nov 2023 21:30:02 +0300 Subject: [PATCH 049/607] Return filling of implementation_fetched_at --- .../explorer/chain/smart_contract/proxy.ex | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex index 3a541ab8e7ba..7c086516059a 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -34,21 +34,12 @@ defmodule Explorer.Chain.SmartContract.Proxy do when not is_nil(proxy_address_hash) and not is_nil(proxy_abi) do implementation_address_hash_string = get_implementation_address_hash_string(proxy_address_hash, proxy_abi) - {:ok, burn_address_hash} = string_to_address_hash(SmartContract.burn_address_hash_string()) - - with false <- is_nil(implementation_address_hash_string), - {:ok, implementation_address_hash} <- string_to_address_hash(implementation_address_hash_string), - false <- implementation_address_hash.bytes == burn_address_hash.bytes do - SmartContract.save_implementation_data( - implementation_address_hash_string, - proxy_address_hash, - metadata_from_verified_twin, - options - ) - else - _ -> - {nil, nil} - end + SmartContract.save_implementation_data( + implementation_address_hash_string, + proxy_address_hash, + metadata_from_verified_twin, + options + ) end def fetch_implementation_address_hash(_, _, _, _) do From 615da7f62fb0cbc9f16192b5d6f2bb683ef2d7c0 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 15 Nov 2023 21:34:40 +0300 Subject: [PATCH 050/607] Clear cache in GA --- .github/workflows/config.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index b286c178f161..0d77db10e9c4 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -55,7 +55,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps- @@ -113,7 +113,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -137,7 +137,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -160,7 +160,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -200,7 +200,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -226,7 +226,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -255,7 +255,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -303,7 +303,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -349,7 +349,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -406,7 +406,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -460,7 +460,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -525,7 +525,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -589,7 +589,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" From bfa96e7c3e14282a5645212ffa58293476217218 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 15 Nov 2023 22:17:38 +0300 Subject: [PATCH 051/607] Remove unused import Mox --- .../transaction_internal_transaction_controller_test.exs | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs index 5354d84b3563..71deea5dbf43 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs @@ -1,8 +1,6 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do use BlockScoutWeb.ConnCase - import Mox - import BlockScoutWeb.WebRouter.Helpers, only: [transaction_internal_transaction_path: 3] alias Explorer.Chain.InternalTransaction From 9bdee6e1905b1ba3375723e861a3b1c916374ee7 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Thu, 16 Nov 2023 18:51:39 +0600 Subject: [PATCH 052/607] Add MainPageRealtimeEventHandler --- CHANGELOG.md | 1 + .../lib/block_scout_web/application.ex | 3 +- .../main_page_realtime_event_handler.ex | 29 +++++++++++++++++++ .../block_scout_web/realtime_event_handler.ex | 4 --- 4 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 apps/block_scout_web/lib/block_scout_web/main_page_realtime_event_handler.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index 60d07b29f92a..5eca3033373e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#8848](https://github.com/blockscout/blockscout/pull/8848) - Add MainPageRealtimeEventHandler - [#8795](https://github.com/blockscout/blockscout/pull/8795) - Disable catchup indexer by env - [#8768](https://github.com/blockscout/blockscout/pull/8768) - Add possibility to search tokens by address hash - [#8750](https://github.com/blockscout/blockscout/pull/8750) - Support new eth-bytecode-db request metadata fields diff --git a/apps/block_scout_web/lib/block_scout_web/application.ex b/apps/block_scout_web/lib/block_scout_web/application.ex index 2d84cc8b101b..cb46a885761c 100644 --- a/apps/block_scout_web/lib/block_scout_web/application.ex +++ b/apps/block_scout_web/lib/block_scout_web/application.ex @@ -8,7 +8,7 @@ defmodule BlockScoutWeb.Application do alias BlockScoutWeb.API.APILogger alias BlockScoutWeb.Counters.{BlocksIndexedCounter, InternalTransactionsIndexedCounter} alias BlockScoutWeb.{Endpoint, Prometheus} - alias BlockScoutWeb.RealtimeEventHandler + alias BlockScoutWeb.{MainPageRealtimeEventHandler, RealtimeEventHandler} def start(_type, _args) do import Supervisor @@ -34,6 +34,7 @@ defmodule BlockScoutWeb.Application do {Phoenix.PubSub, name: BlockScoutWeb.PubSub}, child_spec(Endpoint, []), {Absinthe.Subscription, Endpoint}, + {MainPageRealtimeEventHandler, name: MainPageRealtimeEventHandler}, {RealtimeEventHandler, name: RealtimeEventHandler}, {BlocksIndexedCounter, name: BlocksIndexedCounter}, {InternalTransactionsIndexedCounter, name: InternalTransactionsIndexedCounter} diff --git a/apps/block_scout_web/lib/block_scout_web/main_page_realtime_event_handler.ex b/apps/block_scout_web/lib/block_scout_web/main_page_realtime_event_handler.ex new file mode 100644 index 000000000000..8c34538f4751 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/main_page_realtime_event_handler.ex @@ -0,0 +1,29 @@ +defmodule BlockScoutWeb.MainPageRealtimeEventHandler do + @moduledoc """ + Subscribing process for main page broadcast events from realtime. + """ + + use GenServer + + alias BlockScoutWeb.Notifier + alias Explorer.Chain.Events.Subscriber + alias Explorer.Counters.Helper + + def start_link(_) do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + @impl true + def init([]) do + Helper.create_cache_table(:last_broadcasted_block) + Subscriber.to(:blocks, :realtime) + Subscriber.to(:transactions, :realtime) + {:ok, []} + end + + @impl true + def handle_info(event, state) do + Notifier.handle_event(event) + {:noreply, state} + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex index e7b590876380..3f89f6762fd3 100644 --- a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex +++ b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex @@ -7,7 +7,6 @@ defmodule BlockScoutWeb.RealtimeEventHandler do alias BlockScoutWeb.Notifier alias Explorer.Chain.Events.Subscriber - alias Explorer.Counters.Helper def start_link(_) do GenServer.start_link(__MODULE__, [], name: __MODULE__) @@ -15,15 +14,12 @@ defmodule BlockScoutWeb.RealtimeEventHandler do @impl true def init([]) do - Helper.create_cache_table(:last_broadcasted_block) Subscriber.to(:address_coin_balances, :realtime) Subscriber.to(:addresses, :realtime) Subscriber.to(:block_rewards, :realtime) - Subscriber.to(:blocks, :realtime) Subscriber.to(:internal_transactions, :realtime) Subscriber.to(:internal_transactions, :on_demand) Subscriber.to(:token_transfers, :realtime) - Subscriber.to(:transactions, :realtime) Subscriber.to(:addresses, :on_demand) Subscriber.to(:address_coin_balances, :on_demand) Subscriber.to(:address_current_token_balances, :on_demand) From 4c412aa0236b2061f8c8161769e4899ef040f06d Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 16 Nov 2023 18:48:27 +0300 Subject: [PATCH 053/607] Use SmartContract.burn_address_hash_string() in reward.ex --- apps/explorer/lib/explorer/chain/block/reward.ex | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/block/reward.ex b/apps/explorer/lib/explorer/chain/block/reward.ex index 92897cd80848..0ec4f05e881e 100644 --- a/apps/explorer/lib/explorer/chain/block/reward.ex +++ b/apps/explorer/lib/explorer/chain/block/reward.ex @@ -10,12 +10,11 @@ defmodule Explorer.Chain.Block.Reward do alias Explorer.Chain.Block.Reward.AddressType alias Explorer.Chain.{Address, Block, Hash, Validator, Wei} alias Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand + alias Explorer.Chain.SmartContract alias Explorer.SmartContract.Reader @required_attrs ~w(address_hash address_type block_hash reward)a - @burn_address_hash_string "0x0000000000000000000000000000000000000000" - @get_payout_by_mining_abi %{ "type" => "function", "stateMutability" => "view", @@ -218,7 +217,7 @@ defmodule Explorer.Chain.Block.Reward do payout_key_hash = call_contract(keys_manager_contract_address, @get_payout_by_mining_abi, get_payout_by_mining_params) - if payout_key_hash == burn_address_hash_string() do + if payout_key_hash == SmartContract.burn_address_hash_string() do mining_key else choose_key(payout_key_hash, mining_key) @@ -248,14 +247,10 @@ defmodule Explorer.Chain.Block.Reward do case Reader.query_contract(address, abi, params, false) do %{^method_id => {:ok, [result]}} -> result - _ -> burn_address_hash_string() + _ -> SmartContract.burn_address_hash_string() end end - defp burn_address_hash_string do - @burn_address_hash_string - end - defp join_associations(query) do query |> preload(:address) From abaab828dd65823305f2088b304be2f907f3ebb8 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Wed, 15 Nov 2023 21:25:18 +0300 Subject: [PATCH 054/607] Complete account migration to v2 path --- CHANGELOG.md | 1 + .../lib/block_scout_web/api_router.ex | 67 +----- .../api/{v1 => v2}/authenticate_controller.ex | 2 +- .../api/{v1 => v2}/email_controller.ex | 2 +- .../api/{v1 => v2}/fallback_controller.ex | 4 +- .../account/api/{v1 => v2}/tags_controller.ex | 2 +- .../account/api/{v1 => v2}/user_controller.ex | 4 +- .../templates/layout/app.html.eex | 2 +- .../account/api/{v1 => v2}/account_view.ex | 2 +- .../views/account/api/{v1 => v2}/tags_view.ex | 2 +- .../views/account/api/{v1 => v2}/user_view.ex | 4 +- .../api/{v1 => v2}/user_controller_test.exs | 211 +++++++++--------- .../api/v2/smart_contract_controller_test.exs | 6 +- 13 files changed, 125 insertions(+), 184 deletions(-) rename apps/block_scout_web/lib/block_scout_web/controllers/account/api/{v1 => v2}/authenticate_controller.ex (93%) rename apps/block_scout_web/lib/block_scout_web/controllers/account/api/{v1 => v2}/email_controller.ex (97%) rename apps/block_scout_web/lib/block_scout_web/controllers/account/api/{v1 => v2}/fallback_controller.ex (96%) rename apps/block_scout_web/lib/block_scout_web/controllers/account/api/{v1 => v2}/tags_controller.ex (98%) rename apps/block_scout_web/lib/block_scout_web/controllers/account/api/{v1 => v2}/user_controller.ex (99%) rename apps/block_scout_web/lib/block_scout_web/views/account/api/{v1 => v2}/account_view.ex (65%) rename apps/block_scout_web/lib/block_scout_web/views/account/api/{v1 => v2}/tags_view.ex (92%) rename apps/block_scout_web/lib/block_scout_web/views/account/api/{v1 => v2}/user_view.ex (98%) rename apps/block_scout_web/test/block_scout_web/controllers/account/api/{v1 => v2}/user_controller_test.exs (86%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 109b2901d7de..85447850e000 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ ### Chore +- [#8843](https://github.com/blockscout/blockscout/pull/8843) - Remove /api/account/v1 path - [#8832](https://github.com/blockscout/blockscout/pull/8832) - Log more details in regards 413 error - [#8807](https://github.com/blockscout/blockscout/pull/8807) - Smart-contract proxy detection refactoring - [#8802](https://github.com/blockscout/blockscout/pull/8802) - Enable API v2 by default diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index dee973786cfe..a133568f62e5 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -46,74 +46,9 @@ defmodule BlockScoutWeb.ApiRouter do plug(RateLimit) end - alias BlockScoutWeb.Account.Api.V1.{AuthenticateController, EmailController, TagsController, UserController} + alias BlockScoutWeb.Account.Api.V2.{AuthenticateController, EmailController, TagsController, UserController} alias BlockScoutWeb.API.V2 - # TODO: Remove /account/v1 paths - scope "/account/v1", as: :account_v1 do - pipe_through(:api) - pipe_through(:account_api) - - get("/authenticate", AuthenticateController, :authenticate_get) - post("/authenticate", AuthenticateController, :authenticate_post) - - get("/get_csrf", UserController, :get_csrf) - - scope "/email" do - get("/resend", EmailController, :resend_email) - end - - scope "/user" do - get("/info", UserController, :info) - - get("/watchlist", UserController, :watchlist_old) - delete("/watchlist/:id", UserController, :delete_watchlist) - post("/watchlist", UserController, :create_watchlist) - put("/watchlist/:id", UserController, :update_watchlist) - - get("/api_keys", UserController, :api_keys) - delete("/api_keys/:api_key", UserController, :delete_api_key) - post("/api_keys", UserController, :create_api_key) - put("/api_keys/:api_key", UserController, :update_api_key) - - get("/custom_abis", UserController, :custom_abis) - delete("/custom_abis/:id", UserController, :delete_custom_abi) - post("/custom_abis", UserController, :create_custom_abi) - put("/custom_abis/:id", UserController, :update_custom_abi) - - get("/public_tags", UserController, :public_tags_requests) - delete("/public_tags/:id", UserController, :delete_public_tags_request) - post("/public_tags", UserController, :create_public_tags_request) - put("/public_tags/:id", UserController, :update_public_tags_request) - - scope "/tags" do - get("/address/", UserController, :tags_address_old) - get("/address/:id", UserController, :tags_address) - delete("/address/:id", UserController, :delete_tag_address) - post("/address/", UserController, :create_tag_address) - put("/address/:id", UserController, :update_tag_address) - - get("/transaction/", UserController, :tags_transaction_old) - get("/transaction/:id", UserController, :tags_transaction) - delete("/transaction/:id", UserController, :delete_tag_transaction) - post("/transaction/", UserController, :create_tag_transaction) - put("/transaction/:id", UserController, :update_tag_transaction) - end - end - end - - # TODO: Remove /account/v1 paths - scope "/account/v1" do - pipe_through(:api) - pipe_through(:account_api) - - scope "/tags" do - get("/address/:address_hash", TagsController, :tags_address) - - get("/transaction/:transaction_hash", TagsController, :tags_transaction) - end - end - scope "/account/v2", as: :account_v2 do pipe_through(:api) pipe_through(:account_api) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/authenticate_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/authenticate_controller.ex similarity index 93% rename from apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/authenticate_controller.ex rename to apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/authenticate_controller.ex index 0c16034d0217..2358fdc2cef6 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/authenticate_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/authenticate_controller.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.Account.Api.V1.AuthenticateController do +defmodule BlockScoutWeb.Account.Api.V2.AuthenticateController do use BlockScoutWeb, :controller import BlockScoutWeb.Account.AuthController, only: [current_user: 1] diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/email_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/email_controller.ex similarity index 97% rename from apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/email_controller.ex rename to apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/email_controller.ex index 9e79c2d33645..f93315779bb6 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/email_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/email_controller.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.Account.Api.V1.EmailController do +defmodule BlockScoutWeb.Account.Api.V2.EmailController do use BlockScoutWeb, :controller alias BlockScoutWeb.Models.UserFromAuth diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/fallback_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/fallback_controller.ex similarity index 96% rename from apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/fallback_controller.ex rename to apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/fallback_controller.ex index 14d44fffb3c0..a4821b23e41f 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/fallback_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/fallback_controller.ex @@ -1,7 +1,7 @@ -defmodule BlockScoutWeb.Account.Api.V1.FallbackController do +defmodule BlockScoutWeb.Account.Api.V2.FallbackController do use Phoenix.Controller - alias BlockScoutWeb.Account.Api.V1.UserView + alias BlockScoutWeb.Account.Api.V2.UserView alias Ecto.Changeset def call(conn, {:identity, _}) do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/tags_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/tags_controller.ex similarity index 98% rename from apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/tags_controller.ex rename to apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/tags_controller.ex index 3549024f905c..18765be70c45 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/tags_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/tags_controller.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.Account.Api.V1.TagsController do +defmodule BlockScoutWeb.Account.Api.V2.TagsController do use BlockScoutWeb, :controller import BlockScoutWeb.Account.AuthController, only: [current_user: 1] diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/user_controller.ex similarity index 99% rename from apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex rename to apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/user_controller.ex index 724378cc69d6..12e9e0ab3056 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/user_controller.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.Account.Api.V1.UserController do +defmodule BlockScoutWeb.Account.Api.V2.UserController do use BlockScoutWeb, :controller import BlockScoutWeb.Account.AuthController, only: [current_user: 1] @@ -21,7 +21,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserController do alias Explorer.{Chain, Market, PagingOptions, Repo} alias Plug.CSRFProtection - action_fallback(BlockScoutWeb.Account.Api.V1.FallbackController) + action_fallback(BlockScoutWeb.Account.Api.V2.FallbackController) @ok_message "OK" @token_balances_amount 150 diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex index d07b77ca9e14..2481992ea493 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex @@ -94,7 +94,7 @@ <% session = Explorer.Account.enabled?() && Plug.Conn.get_session(@conn, :current_user) %> <%= render BlockScoutWeb.LayoutView, "_topnav.html", current_user: session, conn: @conn %> <%= if session && !session[:email_verified] do %> - + <% else %> <% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/account_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/account_view.ex similarity index 65% rename from apps/block_scout_web/lib/block_scout_web/views/account/api/v1/account_view.ex rename to apps/block_scout_web/lib/block_scout_web/views/account/api/v2/account_view.ex index 0e3a65e61653..632b3109501f 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/account_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/account_view.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.Account.Api.V1.AccountView do +defmodule BlockScoutWeb.Account.Api.V2.AccountView do def render("message.json", %{message: message}) do %{ "message" => message diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/tags_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/tags_view.ex similarity index 92% rename from apps/block_scout_web/lib/block_scout_web/views/account/api/v1/tags_view.ex rename to apps/block_scout_web/lib/block_scout_web/views/account/api/v2/tags_view.ex index d97e35f9145b..80670000d2e6 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/tags_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/tags_view.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.Account.Api.V1.TagsView do +defmodule BlockScoutWeb.Account.Api.V2.TagsView do def render("address_tags.json", %{tags_map: tags_map}) do tags_map end diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/user_view.ex similarity index 98% rename from apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex rename to apps/block_scout_web/lib/block_scout_web/views/account/api/v2/user_view.ex index 26c8d7c8295b..96974a909218 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/user_view.ex @@ -1,5 +1,5 @@ -defmodule BlockScoutWeb.Account.Api.V1.UserView do - alias BlockScoutWeb.Account.Api.V1.AccountView +defmodule BlockScoutWeb.Account.Api.V2.UserView do + alias BlockScoutWeb.Account.Api.V2.AccountView alias BlockScoutWeb.API.V2.Helper alias Ecto.Changeset alias Explorer.Chain diff --git a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v2/user_controller_test.exs similarity index 86% rename from apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs rename to apps/block_scout_web/test/block_scout_web/controllers/account/api/v2/user_controller_test.exs index f32b5727e727..a4bc4dffe4a9 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v2/user_controller_test.exs @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do +defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do use BlockScoutWeb.ConnCase alias Explorer.Account.{ @@ -19,11 +19,11 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do {:ok, user: user, conn: Plug.Test.init_test_session(conn, current_user: user)} end - describe "Test account/api/v1/user" do + describe "Test account/api/v2/user" do test "get user info", %{conn: conn, user: user} do result_conn = conn - |> get("/api/account/v1/user/info") + |> get("/api/account/v2/user/info") |> doc(description: "Get info about user") assert json_response(result_conn, 200) == %{ @@ -37,7 +37,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do test "post private address tag", %{conn: conn} do tag_address_response = conn - |> post("/api/account/v1/user/tags/address", %{ + |> post("/api/account/v2/user/tags/address", %{ "address_hash" => "0x3e9ac8f16c92bc4f093357933b5befbf1e16987b", "name" => "MyName" }) @@ -45,7 +45,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do |> json_response(200) conn - |> get("/api/account/v1/tags/address/0x3e9ac8f16c92bc4f093357933b5befbf1e16987b") + |> get("/api/account/v2/tags/address/0x3e9ac8f16c92bc4f093357933b5befbf1e16987b") |> doc(description: "Get tags for address") |> json_response(200) @@ -69,11 +69,11 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do end assert conn - |> post("/api/account/v1/user/tags/address", build(:tag_address)) + |> post("/api/account/v2/user/tags/address", build(:tag_address)) |> json_response(200) assert conn - |> post("/api/account/v1/user/tags/address", build(:tag_address)) + |> post("/api/account/v2/user/tags/address", build(:tag_address)) |> json_response(422) Application.put_env(:explorer, Explorer.Account, old_env) @@ -103,12 +103,12 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do tag_address_response = conn - |> post("/api/account/v1/user/tags/address", address_tag) + |> post("/api/account/v2/user/tags/address", address_tag) |> json_response(200) _response = conn - |> get("/api/account/v1/user/tags/address") + |> get("/api/account/v2/user/tags/address") |> json_response(200) == [tag_address_response] assert tag_address_response["address_hash"] == address_tag["address_hash"] @@ -119,7 +119,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do new_tag_address_response = conn - |> put("/api/account/v1/user/tags/address/#{tag_address_response["id"]}", new_address_tag) + |> put("/api/account/v2/user/tags/address/#{tag_address_response["id"]}", new_address_tag) |> doc(description: "Edit private address tag") |> json_response(200) @@ -137,7 +137,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do Enum.map(zipped, fn {addr, name} -> id = (conn - |> post("/api/account/v1/user/tags/address", %{ + |> post("/api/account/v2/user/tags/address", %{ "address_hash" => addr, "name" => name }) @@ -164,7 +164,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert Enum.all?(created, fn {addr, map_tag, _} -> response = conn - |> get("/api/account/v1/tags/address/#{addr}") + |> get("/api/account/v2/tags/address/#{addr}") |> json_response(200) response["personal_tags"] == [map_tag] @@ -172,9 +172,10 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do response = conn - |> get("/api/account/v1/user/tags/address") + |> get("/api/account/v2/user/tags/address") |> doc(description: "Get private addresses tags") |> json_response(200) + |> Map.get("items") assert Enum.all?(created, fn {_, _, map} -> map in response end) end @@ -188,7 +189,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do Enum.map(zipped, fn {addr, name} -> id = (conn - |> post("/api/account/v1/user/tags/address", %{ + |> post("/api/account/v2/user/tags/address", %{ "address_hash" => addr, "name" => name }) @@ -215,7 +216,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert Enum.all?(created, fn {addr, map_tag, _} -> response = conn - |> get("/api/account/v1/tags/address/#{addr}") + |> get("/api/account/v2/tags/address/#{addr}") |> json_response(200) response["personal_tags"] == [map_tag] @@ -223,32 +224,31 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do response = conn - |> get("/api/account/v1/user/tags/address") + |> get("/api/account/v2/user/tags/address") |> json_response(200) + |> Map.get("items") assert Enum.all?(created, fn {_, _, map} -> map in response end) {_, _, %{"id" => id}} = Enum.at(created, 0) assert conn - |> delete("/api/account/v1/user/tags/address/#{id}") + |> delete("/api/account/v2/user/tags/address/#{id}") |> doc("Delete private address tag") |> json_response(200) == %{"message" => "OK"} assert Enum.all?(Enum.drop(created, 1), fn {_, _, %{"id" => id}} -> conn - |> delete("/api/account/v1/user/tags/address/#{id}") + |> delete("/api/account/v2/user/tags/address/#{id}") |> json_response(200) == %{"message" => "OK"} end) - assert conn - |> get("/api/account/v1/user/tags/address") - |> json_response(200) == [] + assert conn |> get("/api/account/v2/user/tags/address") |> json_response(200) |> Map.get("items") == [] assert Enum.all?(created, fn {addr, _, _} -> response = conn - |> get("/api/account/v1/tags/address/#{addr}") + |> get("/api/account/v2/tags/address/#{addr}") |> json_response(200) response["personal_tags"] == [] @@ -260,7 +260,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do tx_hash = to_string(insert(:transaction).hash) assert conn - |> post("/api/account/v1/user/tags/transaction", %{ + |> post("/api/account/v2/user/tags/transaction", %{ "transaction_hash" => tx_hash_non_existing, "name" => "MyName" }) @@ -269,7 +269,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do tag_transaction_response = conn - |> post("/api/account/v1/user/tags/transaction", %{ + |> post("/api/account/v2/user/tags/transaction", %{ "transaction_hash" => tx_hash, "name" => "MyName" }) @@ -277,7 +277,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do |> json_response(200) conn - |> get("/api/account/v1/tags/transaction/#{tx_hash}") + |> get("/api/account/v2/tags/transaction/#{tx_hash}") |> doc(description: "Get tags for transaction") |> json_response(200) @@ -301,11 +301,11 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do end assert conn - |> post("/api/account/v1/user/tags/transaction", build(:tag_transaction)) + |> post("/api/account/v2/user/tags/transaction", build(:tag_transaction)) |> json_response(200) assert conn - |> post("/api/account/v1/user/tags/transaction", build(:tag_transaction)) + |> post("/api/account/v2/user/tags/transaction", build(:tag_transaction)) |> json_response(422) Application.put_env(:explorer, Explorer.Account, old_env) @@ -335,12 +335,12 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do tag_response = conn - |> post("/api/account/v1/user/tags/transaction", tx_tag) + |> post("/api/account/v2/user/tags/transaction", tx_tag) |> json_response(200) _response = conn - |> get("/api/account/v1/user/tags/transaction") + |> get("/api/account/v2/user/tags/transaction") |> json_response(200) == [tag_response] assert tag_response["address_hash"] == tx_tag["address_hash"] @@ -351,7 +351,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do new_tag_response = conn - |> put("/api/account/v1/user/tags/transaction/#{tag_response["id"]}", new_tx_tag) + |> put("/api/account/v2/user/tags/transaction/#{tag_response["id"]}", new_tx_tag) |> doc(description: "Edit private transaction tag") |> json_response(200) @@ -369,7 +369,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do Enum.map(zipped, fn {tx_hash, name} -> id = (conn - |> post("/api/account/v1/user/tags/transaction", %{ + |> post("/api/account/v2/user/tags/transaction", %{ "transaction_hash" => tx_hash, "name" => name }) @@ -381,7 +381,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert Enum.all?(created, fn {tx_hash, map_tag, _} -> response = conn - |> get("/api/account/v1/tags/transaction/#{tx_hash}") + |> get("/api/account/v2/tags/transaction/#{tx_hash}") |> json_response(200) response["personal_tx_tag"] == map_tag @@ -389,9 +389,10 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do response = conn - |> get("/api/account/v1/user/tags/transaction") + |> get("/api/account/v2/user/tags/transaction") |> doc(description: "Get private transactions tags") |> json_response(200) + |> Map.get("items") assert Enum.all?(created, fn {_, _, map} -> map in response end) end @@ -405,7 +406,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do Enum.map(zipped, fn {tx_hash, name} -> id = (conn - |> post("/api/account/v1/user/tags/transaction", %{ + |> post("/api/account/v2/user/tags/transaction", %{ "transaction_hash" => tx_hash, "name" => name }) @@ -417,15 +418,15 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert Enum.all?(created, fn {tx_hash, map_tag, _} -> response = conn - |> get("/api/account/v1/tags/transaction/#{tx_hash}") + |> get("/api/account/v2/tags/transaction/#{tx_hash}") |> json_response(200) response["personal_tx_tag"] == map_tag end) - response = + %{"items" => response, "next_page_params" => nil} = conn - |> get("/api/account/v1/user/tags/transaction") + |> get("/api/account/v2/user/tags/transaction") |> json_response(200) assert Enum.all?(created, fn {_, _, map} -> map in response end) @@ -433,24 +434,24 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do {_, _, %{"id" => id}} = Enum.at(created, 0) assert conn - |> delete("/api/account/v1/user/tags/transaction/#{id}") + |> delete("/api/account/v2/user/tags/transaction/#{id}") |> doc("Delete private transaction tag") |> json_response(200) == %{"message" => "OK"} assert Enum.all?(Enum.drop(created, 1), fn {_, _, %{"id" => id}} -> conn - |> delete("/api/account/v1/user/tags/transaction/#{id}") + |> delete("/api/account/v2/user/tags/transaction/#{id}") |> json_response(200) == %{"message" => "OK"} end) assert conn - |> get("/api/account/v1/user/tags/transaction") - |> json_response(200) == [] + |> get("/api/account/v2/user/tags/transaction") + |> json_response(200) == %{"items" => [], "next_page_params" => nil} assert Enum.all?(created, fn {addr, _, _} -> response = conn - |> get("/api/account/v1/tags/transaction/#{addr}") + |> get("/api/account/v2/tags/transaction/#{addr}") |> json_response(200) response["personal_tx_tag"] == nil @@ -463,7 +464,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do post_watchlist_address_response = conn |> post( - "/api/account/v1/user/watchlist", + "/api/account/v2/user/watchlist", watchlist_address_map ) |> doc(description: "Add address to watch list") @@ -474,7 +475,8 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert post_watchlist_address_response["notification_methods"] == watchlist_address_map["notification_methods"] assert post_watchlist_address_response["address_hash"] == watchlist_address_map["address_hash"] - get_watchlist_address_response = conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(0) + get_watchlist_address_response = + conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") |> Enum.at(0) assert get_watchlist_address_response["notification_settings"] == watchlist_address_map["notification_settings"] assert get_watchlist_address_response["name"] == watchlist_address_map["name"] @@ -487,20 +489,21 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do post_watchlist_address_response_1 = conn |> post( - "/api/account/v1/user/watchlist", + "/api/account/v2/user/watchlist", watchlist_address_map_1 ) |> json_response(200) get_watchlist_address_response_1_0 = conn - |> get("/api/account/v1/user/watchlist") + |> get("/api/account/v2/user/watchlist") |> doc(description: "Get addresses from watchlists") |> json_response(200) + |> Map.get("items") |> Enum.at(1) get_watchlist_address_response_1_1 = - conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(0) + conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") |> Enum.at(0) assert get_watchlist_address_response_1_0 == get_watchlist_address_response @@ -531,11 +534,11 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do end assert conn - |> post("/api/account/v1/user/watchlist", build(:watchlist_address)) + |> post("/api/account/v2/user/watchlist", build(:watchlist_address)) |> json_response(200) assert conn - |> post("/api/account/v1/user/watchlist", build(:watchlist_address)) + |> post("/api/account/v2/user/watchlist", build(:watchlist_address)) |> json_response(422) Application.put_env(:explorer, Explorer.Account, old_env) @@ -566,7 +569,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do post_watchlist_address_response = conn |> post( - "/api/account/v1/user/watchlist", + "/api/account/v2/user/watchlist", watchlist_address_map ) |> json_response(200) @@ -576,7 +579,8 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert post_watchlist_address_response["notification_methods"] == watchlist_address_map["notification_methods"] assert post_watchlist_address_response["address_hash"] == watchlist_address_map["address_hash"] - get_watchlist_address_response = conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(0) + get_watchlist_address_response = + conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") |> Enum.at(0) assert get_watchlist_address_response["notification_settings"] == watchlist_address_map["notification_settings"] assert get_watchlist_address_response["name"] == watchlist_address_map["name"] @@ -589,16 +593,16 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do post_watchlist_address_response_1 = conn |> post( - "/api/account/v1/user/watchlist", + "/api/account/v2/user/watchlist", watchlist_address_map_1 ) |> json_response(200) get_watchlist_address_response_1_0 = - conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(1) + conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") |> Enum.at(1) get_watchlist_address_response_1_1 = - conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(0) + conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") |> Enum.at(0) assert get_watchlist_address_response_1_0 == get_watchlist_address_response @@ -614,15 +618,15 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert get_watchlist_address_response_1_1["id"] == post_watchlist_address_response_1["id"] assert conn - |> delete("/api/account/v1/user/watchlist/#{get_watchlist_address_response_1_1["id"]}") + |> delete("/api/account/v2/user/watchlist/#{get_watchlist_address_response_1_1["id"]}") |> doc(description: "Delete address from watchlist by id") |> json_response(200) == %{"message" => "OK"} assert conn - |> delete("/api/account/v1/user/watchlist/#{get_watchlist_address_response_1_0["id"]}") + |> delete("/api/account/v2/user/watchlist/#{get_watchlist_address_response_1_0["id"]}") |> json_response(200) == %{"message" => "OK"} - assert conn |> get("/api/account/v1/user/watchlist") |> json_response(200) == [] + assert conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") == [] end test "put watchlist address", %{conn: conn} do @@ -631,7 +635,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do post_watchlist_address_response = conn |> post( - "/api/account/v1/user/watchlist", + "/api/account/v2/user/watchlist", watchlist_address_map ) |> json_response(200) @@ -641,7 +645,8 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert post_watchlist_address_response["notification_methods"] == watchlist_address_map["notification_methods"] assert post_watchlist_address_response["address_hash"] == watchlist_address_map["address_hash"] - get_watchlist_address_response = conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(0) + get_watchlist_address_response = + conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") |> Enum.at(0) assert get_watchlist_address_response["notification_settings"] == watchlist_address_map["notification_settings"] assert get_watchlist_address_response["name"] == watchlist_address_map["name"] @@ -654,7 +659,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do put_watchlist_address_response = conn |> put( - "/api/account/v1/user/watchlist/#{post_watchlist_address_response["id"]}", + "/api/account/v2/user/watchlist/#{post_watchlist_address_response["id"]}", new_watchlist_address_map ) |> doc(description: "Edit watchlist address") @@ -675,7 +680,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do post_watchlist_address_response = conn |> post( - "/api/account/v1/user/watchlist", + "/api/account/v2/user/watchlist", watchlist_address_map ) |> json_response(200) @@ -687,7 +692,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert conn |> post( - "/api/account/v1/user/watchlist", + "/api/account/v2/user/watchlist", watchlist_address_map ) |> doc(description: "Example of error on creating watchlist address") @@ -698,14 +703,14 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do post_watchlist_address_response_1 = conn |> post( - "/api/account/v1/user/watchlist", + "/api/account/v2/user/watchlist", new_watchlist_address_map ) |> json_response(200) assert conn |> put( - "/api/account/v1/user/watchlist/#{post_watchlist_address_response_1["id"]}", + "/api/account/v2/user/watchlist/#{post_watchlist_address_response_1["id"]}", watchlist_address_map ) |> doc(description: "Example of error on editing watchlist address") @@ -717,7 +722,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do conn |> post( - "/api/account/v1/user/watchlist", + "/api/account/v2/user/watchlist", watchlist_address_map ) |> json_response(200) @@ -726,7 +731,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do conn |> post( - "/api/account/v1/user/watchlist", + "/api/account/v2/user/watchlist", watchlist_address_map_1 ) |> json_response(200) @@ -761,7 +766,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do |> Enum.sort(fn x1, x2 -> Decimal.compare(x1, x2) in [:gt, :eq] end) |> Enum.take(150) - [wa2, wa1] = conn |> get("/api/account/v1/user/watchlist") |> json_response(200) + [wa2, wa1] = conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") assert wa1["tokens_fiat_value"] |> Decimal.new() |> Decimal.round(13) == values |> Enum.reduce(Decimal.new(0), fn x, acc -> Decimal.add(x, acc) end) |> Decimal.round(13) @@ -781,7 +786,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do conn |> post( - "/api/account/v1/user/watchlist", + "/api/account/v2/user/watchlist", watchlist_address_map ) |> json_response(200) @@ -808,7 +813,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do token_contract_address_hash: token.contract_address_hash ) - [wa1] = conn |> get("/api/account/v1/user/watchlist") |> json_response(200) + [wa1] = conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") assert wa1["tokens_fiat_value"] |> Decimal.new() |> Decimal.round(13) == values |> Enum.reduce(Decimal.new(0), fn x, acc -> Decimal.add(x, acc) end) |> Decimal.round(13) @@ -821,7 +826,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do post_api_key_response = conn |> post( - "/api/account/v1/user/api_keys", + "/api/account/v2/user/api_keys", %{"name" => "test"} ) |> doc(description: "Add api key") @@ -835,7 +840,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do Enum.each(0..2, fn _x -> conn |> post( - "/api/account/v1/user/api_keys", + "/api/account/v2/user/api_keys", %{"name" => "test"} ) |> json_response(200) @@ -843,14 +848,14 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert conn |> post( - "/api/account/v1/user/api_keys", + "/api/account/v2/user/api_keys", %{"name" => "test"} ) |> doc(description: "Example of error on creating api key") |> json_response(422) == %{"errors" => %{"name" => ["Max 3 keys per account"]}} assert conn - |> get("/api/account/v1/user/api_keys") + |> get("/api/account/v2/user/api_keys") |> doc(description: "Get api keys list") |> json_response(200) |> Enum.count() == 3 @@ -860,7 +865,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do post_api_key_response = conn |> post( - "/api/account/v1/user/api_keys", + "/api/account/v2/user/api_keys", %{"name" => "test"} ) |> json_response(200) @@ -871,7 +876,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do put_api_key_response = conn |> put( - "/api/account/v1/user/api_keys/#{post_api_key_response["api_key"]}", + "/api/account/v2/user/api_keys/#{post_api_key_response["api_key"]}", %{"name" => "test_1"} ) |> doc(description: "Edit api key") @@ -881,7 +886,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert put_api_key_response["name"] == "test_1" assert conn - |> get("/api/account/v1/user/api_keys") + |> get("/api/account/v2/user/api_keys") |> json_response(200) == [put_api_key_response] end @@ -889,7 +894,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do post_api_key_response = conn |> post( - "/api/account/v1/user/api_keys", + "/api/account/v2/user/api_keys", %{"name" => "test"} ) |> json_response(200) @@ -898,17 +903,17 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert post_api_key_response["api_key"] assert conn - |> get("/api/account/v1/user/api_keys") + |> get("/api/account/v2/user/api_keys") |> json_response(200) |> Enum.count() == 1 assert conn - |> delete("/api/account/v1/user/api_keys/#{post_api_key_response["api_key"]}") + |> delete("/api/account/v2/user/api_keys/#{post_api_key_response["api_key"]}") |> doc(description: "Delete api key") |> json_response(200) == %{"message" => "OK"} assert conn - |> get("/api/account/v1/user/api_keys") + |> get("/api/account/v2/user/api_keys") |> json_response(200) == [] end @@ -918,7 +923,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do post_custom_abi_response = conn |> post( - "/api/account/v1/user/custom_abis", + "/api/account/v2/user/custom_abis", custom_abi ) |> doc(description: "Add custom abi") @@ -934,7 +939,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do Enum.each(0..14, fn _x -> conn |> post( - "/api/account/v1/user/custom_abis", + "/api/account/v2/user/custom_abis", build(:custom_abi) ) |> json_response(200) @@ -942,14 +947,14 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert conn |> post( - "/api/account/v1/user/custom_abis", + "/api/account/v2/user/custom_abis", build(:custom_abi) ) |> doc(description: "Example of error on creating custom abi") |> json_response(422) == %{"errors" => %{"name" => ["Max 15 ABIs per account"]}} assert conn - |> get("/api/account/v1/user/custom_abis") + |> get("/api/account/v2/user/custom_abis") |> doc(description: "Get custom abis list") |> json_response(200) |> Enum.count() == 15 @@ -961,7 +966,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do post_custom_abi_response = conn |> post( - "/api/account/v1/user/custom_abis", + "/api/account/v2/user/custom_abis", custom_abi ) |> json_response(200) @@ -976,7 +981,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do put_custom_abi_response = conn |> put( - "/api/account/v1/user/custom_abis/#{post_custom_abi_response["id"]}", + "/api/account/v2/user/custom_abis/#{post_custom_abi_response["id"]}", custom_abi_1 ) |> doc(description: "Edit custom abi") @@ -988,7 +993,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert put_custom_abi_response["abi"] == custom_abi_1["abi"] assert conn - |> get("/api/account/v1/user/custom_abis") + |> get("/api/account/v2/user/custom_abis") |> json_response(200) == [put_custom_abi_response] end @@ -998,7 +1003,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do post_custom_abi_response = conn |> post( - "/api/account/v1/user/custom_abis", + "/api/account/v2/user/custom_abis", custom_abi ) |> json_response(200) @@ -1007,17 +1012,17 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert post_custom_abi_response["id"] assert conn - |> get("/api/account/v1/user/custom_abis") + |> get("/api/account/v2/user/custom_abis") |> json_response(200) |> Enum.count() == 1 assert conn - |> delete("/api/account/v1/user/custom_abis/#{post_custom_abi_response["id"]}") + |> delete("/api/account/v2/user/custom_abis/#{post_custom_abi_response["id"]}") |> doc(description: "Delete custom abi") |> json_response(200) == %{"message" => "OK"} assert conn - |> get("/api/account/v1/user/custom_abis") + |> get("/api/account/v2/user/custom_abis") |> json_response(200) == [] end end @@ -1029,7 +1034,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do post_public_tags_request_response = conn |> post( - "/api/account/v1/user/public_tags", + "/api/account/v2/user/public_tags", public_tags_request ) |> doc(description: "Submit request to add a public tag") @@ -1052,7 +1057,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do post_public_tags_request_response = conn |> post( - "/api/account/v1/user/public_tags", + "/api/account/v2/user/public_tags", public_tags_request ) |> json_response(200) @@ -1068,7 +1073,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert post_public_tags_request_response["id"] assert conn - |> get("/api/account/v1/user/public_tags") + |> get("/api/account/v2/user/public_tags") |> json_response(200) |> Enum.map(&convert_date/1) == [post_public_tags_request_response] @@ -1084,7 +1089,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do response = conn |> post( - "/api/account/v1/user/public_tags", + "/api/account/v2/user/public_tags", request ) |> json_response(200) @@ -1104,7 +1109,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do |> Enum.reverse() assert conn - |> get("/api/account/v1/user/public_tags") + |> get("/api/account/v2/user/public_tags") |> doc(description: "Get list of requests to add a public tag") |> json_response(200) |> Enum.map(&convert_date/1) == final_list @@ -1112,18 +1117,18 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do %{"id" => id} = Enum.at(final_list, 0) assert conn - |> delete("/api/account/v1/user/public_tags/#{id}", %{"remove_reason" => "reason"}) + |> delete("/api/account/v2/user/public_tags/#{id}", %{"remove_reason" => "reason"}) |> doc(description: "Delete public tags request") |> json_response(200) == %{"message" => "OK"} Enum.each(Enum.drop(final_list, 1), fn request -> assert conn - |> delete("/api/account/v1/user/public_tags/#{request["id"]}", %{"remove_reason" => "reason"}) + |> delete("/api/account/v2/user/public_tags/#{request["id"]}", %{"remove_reason" => "reason"}) |> json_response(200) == %{"message" => "OK"} end) assert conn - |> get("/api/account/v1/user/public_tags") + |> get("/api/account/v2/user/public_tags") |> json_response(200) == [] end @@ -1133,7 +1138,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do post_public_tags_request_response = conn |> post( - "/api/account/v1/user/public_tags", + "/api/account/v2/user/public_tags", public_tags_request ) |> json_response(200) @@ -1149,7 +1154,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert post_public_tags_request_response["id"] assert conn - |> get("/api/account/v1/user/public_tags") + |> get("/api/account/v2/user/public_tags") |> json_response(200) |> Enum.map(&convert_date/1) == [post_public_tags_request_response] @@ -1160,7 +1165,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do put_public_tags_request_response = conn |> put( - "/api/account/v1/user/public_tags/#{post_public_tags_request_response["id"]}", + "/api/account/v2/user/public_tags/#{post_public_tags_request_response["id"]}", new_public_tags_request ) |> doc(description: "Edit request to add a public tag") @@ -1177,7 +1182,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do assert put_public_tags_request_response["id"] == post_public_tags_request_response["id"] assert conn - |> get("/api/account/v1/user/public_tags") + |> get("/api/account/v2/user/public_tags") |> json_response(200) |> Enum.map(&convert_date/1) == [put_public_tags_request_response] diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index 2164911f25f6..3f05fee8283b 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -1698,7 +1698,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do conn |> post( - "/api/account/v1/user/custom_abis", + "/api/account/v2/user/custom_abis", custom_abi ) @@ -1750,7 +1750,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do conn |> post( - "/api/account/v1/user/custom_abis", + "/api/account/v2/user/custom_abis", custom_abi ) @@ -1817,7 +1817,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do conn |> post( - "/api/account/v1/user/custom_abis", + "/api/account/v2/user/custom_abis", custom_abi ) From 5615d01e46d96acf270ed67ea7c34ece340fc9d4 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Mon, 13 Nov 2023 18:41:10 +0300 Subject: [PATCH 055/607] Add new events to addresses channel: eth_bytecode_db_lookup_started and smart_contract_was_not_verified --- .../lib/block_scout_web/notifier.ex | 22 ++- .../block_scout_web/realtime_event_handler.ex | 2 + .../api/v2/smart_contract_controller_test.exs | 129 +++++++++++++++++- apps/explorer/config/test.exs | 6 +- .../lib/explorer/chain/events/publisher.ex | 2 +- .../lib/explorer/chain/events/subscriber.ex | 2 +- ...ook_up_smart_contract_sources_on_demand.ex | 3 + 7 files changed, 151 insertions(+), 15 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/notifier.ex b/apps/block_scout_web/lib/block_scout_web/notifier.ex index cb1cdf70be02..e340c4351df3 100644 --- a/apps/block_scout_web/lib/block_scout_web/notifier.ex +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -232,9 +232,16 @@ defmodule BlockScoutWeb.Notifier do Endpoint.broadcast("addresses:#{to_string(address_hash)}", "changed_bytecode", %{}) end - def handle_event({:chain_event, :smart_contract_was_verified, :on_demand, [address_hash]}) do - log_broadcast_smart_contract_was_verified(address_hash) - Endpoint.broadcast("addresses:#{to_string(address_hash)}", "smart_contract_was_verified", %{}) + def handle_event({:chain_event, :smart_contract_was_verified = event, :on_demand, [address_hash]}) do + broadcast_automatic_verification_events(event, address_hash) + end + + def handle_event({:chain_event, :smart_contract_was_not_verified = event, :on_demand, [address_hash]}) do + broadcast_automatic_verification_events(event, address_hash) + end + + def handle_event({:chain_event, :eth_bytecode_db_lookup_started = event, :on_demand, [address_hash]}) do + broadcast_automatic_verification_events(event, address_hash) end def handle_event({:chain_event, :address_current_token_balances, :on_demand, address_current_token_balances}) do @@ -505,7 +512,12 @@ defmodule BlockScoutWeb.Notifier do Logger.info("Broadcast smart-contract #{address_hash} verification results") end - defp log_broadcast_smart_contract_was_verified(address_hash) do - Logger.info("Broadcast smart-contract #{address_hash} was verified") + defp log_broadcast_smart_contract_event(address_hash, event) do + Logger.info("Broadcast smart-contract #{address_hash}: #{event}") + end + + defp broadcast_automatic_verification_events(event, address_hash) do + log_broadcast_smart_contract_event(address_hash, event) + Endpoint.broadcast("addresses:#{to_string(address_hash)}", to_string(event), %{}) end end diff --git a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex index 3f89f6762fd3..7d029f17f885 100644 --- a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex +++ b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex @@ -28,6 +28,8 @@ defmodule BlockScoutWeb.RealtimeEventHandler do Subscriber.to(:token_total_supply, :on_demand) Subscriber.to(:changed_bytecode, :on_demand) Subscriber.to(:smart_contract_was_verified, :on_demand) + Subscriber.to(:smart_contract_was_not_verified, :on_demand) + Subscriber.to(:eth_bytecode_db_lookup_started, :on_demand) Subscriber.to(:zkevm_confirmed_batches, :realtime) # Does not come from the indexer Subscriber.to(:exchange_rate) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index 2164911f25f6..ad6eabf1f113 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -1,5 +1,5 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do - use BlockScoutWeb.ConnCase + use BlockScoutWeb.ConnCase, async: false use BlockScoutWeb.ChannelCase, async: false import Mox @@ -308,6 +308,16 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do end describe "/smart-contracts/{address_hash} <> eth_bytecode_db" do + setup do + old_interval_env = Application.get_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand) + + :ok + + on_exit(fn -> + Application.put_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand, old_interval_env) + end) + end + test "automatically verify contract", %{conn: conn} do {:ok, pid} = Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand.start_link([]) old_chain_id = Application.get_env(:block_scout_web, :chain_id) @@ -321,7 +331,9 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, service_url: "http://localhost:#{bypass.port}", - enabled: true + enabled: true, + type: "eth_bytecode_db", + eth_bytecode_db?: true ) address = insert(:contract_address) @@ -346,6 +358,13 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}") + assert_receive %Phoenix.Socket.Message{ + payload: %{}, + event: "eth_bytecode_db_lookup_started", + topic: ^topic + }, + :timer.seconds(1) + assert_receive %Phoenix.Socket.Message{ payload: %{}, event: "smart_contract_was_verified", @@ -391,7 +410,9 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, service_url: "http://localhost:#{bypass.port}", - enabled: true + enabled: true, + type: "eth_bytecode_db", + eth_bytecode_db?: true ) address = insert(:contract_address) @@ -416,6 +437,13 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}") + assert_receive %Phoenix.Socket.Message{ + payload: %{}, + event: "eth_bytecode_db_lookup_started", + topic: ^topic + }, + :timer.seconds(1) + assert_receive %Phoenix.Socket.Message{ payload: %{}, event: "smart_contract_was_verified", @@ -508,7 +536,9 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, service_url: "http://localhost:#{bypass.port}", - enabled: true + enabled: true, + type: "eth_bytecode_db", + eth_bytecode_db?: true ) address = insert(:contract_address) @@ -533,6 +563,13 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}") + assert_receive %Phoenix.Socket.Message{ + payload: %{}, + event: "eth_bytecode_db_lookup_started", + topic: ^topic + }, + :timer.seconds(1) + assert_receive %Phoenix.Socket.Message{ payload: %{}, event: "smart_contract_was_verified", @@ -582,7 +619,9 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, service_url: "http://localhost:#{bypass.port}", - enabled: true + enabled: true, + type: "eth_bytecode_db", + eth_bytecode_db?: true ) address = insert(:contract_address) @@ -607,6 +646,13 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}") + assert_receive %Phoenix.Socket.Message{ + payload: %{}, + event: "eth_bytecode_db_lookup_started", + topic: ^topic + }, + :timer.seconds(1) + assert_receive %Phoenix.Socket.Message{ payload: %{}, event: "smart_contract_was_verified", @@ -673,6 +719,12 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do bypass = Bypass.open() address = insert(:contract_address) + topic = "addresses:#{address.hash}" + + {:ok, _reply, _socket} = + BlockScoutWeb.UserSocketV2 + |> socket("no_id", %{}) + |> subscribe_and_join(topic) insert(:transaction, created_contract_address_hash: address.hash, @@ -685,7 +737,9 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, service_url: "http://localhost:#{bypass.port}", - enabled: true + enabled: true, + type: "eth_bytecode_db", + eth_bytecode_db?: true ) old_interval_env = Application.get_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand) @@ -698,6 +752,20 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do _request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}") + assert_receive %Phoenix.Socket.Message{ + payload: %{}, + event: "eth_bytecode_db_lookup_started", + topic: ^topic + }, + :timer.seconds(1) + + assert_receive %Phoenix.Socket.Message{ + payload: %{}, + event: "smart_contract_was_not_verified", + topic: ^topic + }, + :timer.seconds(1) + :timer.sleep(10) Bypass.expect_once(bypass, "POST", "/api/v2/bytecodes/sources_search", fn conn -> @@ -706,6 +774,20 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do _request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}") + assert_receive %Phoenix.Socket.Message{ + payload: %{}, + event: "eth_bytecode_db_lookup_started", + topic: ^topic + }, + :timer.seconds(1) + + assert_receive %Phoenix.Socket.Message{ + payload: %{}, + event: "smart_contract_was_not_verified", + topic: ^topic + }, + :timer.seconds(1) + :timer.sleep(10) Bypass.expect_once(bypass, "POST", "/api/v2/bytecodes/sources_search", fn conn -> @@ -714,12 +796,47 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do _request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}") + assert_receive %Phoenix.Socket.Message{ + payload: %{}, + event: "eth_bytecode_db_lookup_started", + topic: ^topic + }, + :timer.seconds(1) + + assert_receive %Phoenix.Socket.Message{ + payload: %{}, + event: "smart_contract_was_not_verified", + topic: ^topic + }, + :timer.seconds(1) + :timer.sleep(10) Application.put_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand, fetch_interval: 10000) _request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}") + refute_receive %Phoenix.Socket.Message{ + payload: %{}, + event: "eth_bytecode_db_lookup_started", + topic: ^topic + }, + :timer.seconds(1) + + refute_receive %Phoenix.Socket.Message{ + payload: %{}, + event: "smart_contract_was_not_verified", + topic: ^topic + }, + :timer.seconds(1) + + refute_receive %Phoenix.Socket.Message{ + payload: %{}, + event: "smart_contract_was_verified", + topic: ^topic + }, + :timer.seconds(1) + Application.put_env(:block_scout_web, :chain_id, old_chain_id) Application.put_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand, old_interval_env) Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, old_env) diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index f47e2413959b..955f9df84267 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -12,7 +12,8 @@ config :explorer, Explorer.Repo, ownership_timeout: :timer.minutes(7), timeout: :timer.seconds(60), queue_target: 1000, - migration_lock: nil + migration_lock: nil, + log: false # Configure API database config :explorer, Explorer.Repo.Replica1, @@ -26,7 +27,8 @@ config :explorer, Explorer.Repo.Replica1, enable_caching_implementation_data_of_proxy: true, avg_block_time_as_ttl_cached_implementation_data_of_proxy: false, fallback_ttl_cached_implementation_data_of_proxy: :timer.seconds(20), - implementation_data_fetching_timeout: :timer.seconds(20) + implementation_data_fetching_timeout: :timer.seconds(20), + log: false # Configure API database config :explorer, Explorer.Repo.Account, diff --git a/apps/explorer/lib/explorer/chain/events/publisher.ex b/apps/explorer/lib/explorer/chain/events/publisher.ex index 21b8d168af92..3dca04f31f73 100644 --- a/apps/explorer/lib/explorer/chain/events/publisher.ex +++ b/apps/explorer/lib/explorer/chain/events/publisher.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.Events.Publisher do Publishes events related to the Chain context. """ - @allowed_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number polygon_edge_reorg_block token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches)a + @allowed_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number polygon_edge_reorg_block token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches eth_bytecode_db_lookup_started smart_contract_was_not_verified)a def broadcast(_data, false), do: :ok diff --git a/apps/explorer/lib/explorer/chain/events/subscriber.ex b/apps/explorer/lib/explorer/chain/events/subscriber.ex index 9d049758ec56..f2aa49f61fe3 100644 --- a/apps/explorer/lib/explorer/chain/events/subscriber.ex +++ b/apps/explorer/lib/explorer/chain/events/subscriber.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.Events.Subscriber do Subscribes to events related to the Chain context. """ - @allowed_broadcast_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number polygon_edge_reorg_block token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches)a + @allowed_broadcast_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number polygon_edge_reorg_block token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches eth_bytecode_db_lookup_started smart_contract_was_not_verified)a @allowed_broadcast_types ~w(catchup realtime on_demand contract_verification_result)a diff --git a/apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex b/apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex index 1e6bd9e6eb68..145b77368abb 100644 --- a/apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex +++ b/apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex @@ -34,6 +34,8 @@ defmodule Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand do end defp fetch_sources(address, only_full?) do + Publisher.broadcast(%{eth_bytecode_db_lookup_started: [address.hash]}, :on_demand) + creation_tx_input = contract_creation_input(address.hash) with {:ok, %{"sourceType" => type, "matchType" => match_type} = source} <- @@ -45,6 +47,7 @@ defmodule Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand do Publisher.broadcast(%{smart_contract_was_verified: [address.hash]}, :on_demand) else _ -> + Publisher.broadcast(%{smart_contract_was_not_verified: [address.hash]}, :on_demand) false end end From d30d741215a2c21ea5d05ee989d01b4e2a3242bb Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Mon, 13 Nov 2023 18:44:19 +0300 Subject: [PATCH 056/607] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 109b2901d7de..83089bc61523 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - [#8848](https://github.com/blockscout/blockscout/pull/8848) - Add MainPageRealtimeEventHandler +- [#8821](https://github.com/blockscout/blockscout/pull/8821) - Add new events to addresses channel: `eth_bytecode_db_lookup_started` and `smart_contract_was_not_verified` - [#8795](https://github.com/blockscout/blockscout/pull/8795) - Disable catchup indexer by env - [#8768](https://github.com/blockscout/blockscout/pull/8768) - Add possibility to search tokens by address hash - [#8750](https://github.com/blockscout/blockscout/pull/8750) - Support new eth-bytecode-db request metadata fields From a26cdffa5fe6d87992a0cda68c008b2c9440cfab Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 17 Nov 2023 12:07:36 +0300 Subject: [PATCH 057/607] all transactions count at top addresses page --- CHANGELOG.md | 1 + .../controllers/address_controller.ex | 4 +- .../controllers/api/v2/address_controller.ex | 2 +- .../templates/address/_tile.html.eex | 2 +- .../templates/robots/sitemap.xml.eex | 2 +- .../views/api/v2/address_view.ex | 8 ++- apps/block_scout_web/priv/gettext/default.pot | 6 +- .../priv/gettext/en/LC_MESSAGES/default.po | 6 +- .../api/v2/address_controller_test.exs | 4 +- apps/explorer/lib/explorer/chain.ex | 69 ------------------ apps/explorer/lib/explorer/chain/address.ex | 68 +++++++++++++++++- .../test/explorer/chain/address_test.exs | 70 +++++++++++++++++++ apps/explorer/test/explorer/chain_test.exs | 70 ------------------- 13 files changed, 152 insertions(+), 160 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 109b2901d7de..39f99df87095 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Fixes +- [#8855](https://github.com/blockscout/blockscout/pull/8855) - All transactions count at top addresses page - [#8836](https://github.com/blockscout/blockscout/pull/8836) - Safe token update - [#8814](https://github.com/blockscout/blockscout/pull/8814) - Improve performance for EOA addresses in `/api/v2/addresses/{address_hash}` - [#8813](https://github.com/blockscout/blockscout/pull/8813) - Force verify twin contracts on `/api/v2/import/smart-contracts/{address_hash}` diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex index c352cb8e783b..3e50dff92ba7 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex @@ -16,7 +16,7 @@ defmodule BlockScoutWeb.AddressController do alias Explorer.{Chain, Market} alias Explorer.Chain.Address.Counters - alias Explorer.Chain.Wei + alias Explorer.Chain.{Address, Wei} alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -24,7 +24,7 @@ defmodule BlockScoutWeb.AddressController do addresses = params |> paging_options() - |> Chain.list_top_addresses() + |> Address.list_top_addresses() {addresses_page, next_page} = split_list_by_page(addresses) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex index 6d3c767a49e7..b3008a00f5ba 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -383,7 +383,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do params |> paging_options() |> Keyword.merge(@api_true) - |> Chain.list_top_addresses() + |> Address.list_top_addresses() |> split_list_by_page() next_page_params = next_page_params(next_page, addresses, params) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex index 368aa9f91e75..04cefd6cde58 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex @@ -28,7 +28,7 @@ <%= @tx_count %> - <%= gettext "Transactions sent" %> + <%= gettext "Transactions" %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/robots/sitemap.xml.eex b/apps/block_scout_web/lib/block_scout_web/templates/robots/sitemap.xml.eex index 0c5334f21aa4..9f3cbc669802 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/robots/sitemap.xml.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/robots/sitemap.xml.eex @@ -11,7 +11,7 @@ <% end %> - <% addresses = Chain.list_top_addresses(params) %> + <% addresses = Address.list_top_addresses(params) %> <%= for {address, _} <- addresses do %> <%= host %>/address/<%= to_string(address) %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex index 33a3cc601a7f..fec26f5d2325 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex @@ -67,10 +67,14 @@ defmodule BlockScoutWeb.API.V2.AddressView do %{"items" => Enum.map(nft_collections, &prepare_nft_collection(&1)), "next_page_params" => next_page_params} end - def prepare_address({address, nonce}) do + @spec prepare_address( + {atom() | %{:fetched_coin_balance => any(), :hash => any(), optional(any()) => any()}, any()} + | Explorer.Chain.Address.t() + ) :: %{optional(:coin_balance) => any(), optional(:tx_count) => binary(), optional(<<_::32, _::_*8>>) => any()} + def prepare_address({address, tx_count}) do nil |> Helper.address_with_info(address, address.hash, true) - |> Map.put(:tx_count, to_string(nonce)) + |> Map.put(:tx_count, to_string(tx_count)) |> Map.put(:coin_balance, if(address.fetched_coin_balance, do: address.fetched_coin_balance.value)) end diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index aed1dc8afd75..d1902773008b 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -3091,6 +3091,7 @@ msgid "Transaction type, introduced in EIP-2718." msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:7 +#: lib/block_scout_web/templates/address/_tile.html.eex:31 #: lib/block_scout_web/templates/address/overview.html.eex:186 #: lib/block_scout_web/templates/address/overview.html.eex:192 #: lib/block_scout_web/templates/address/overview.html.eex:200 @@ -3109,11 +3110,6 @@ msgstr "" msgid "Transactions and address of creation." msgstr "" -#: lib/block_scout_web/templates/address/_tile.html.eex:31 -#, elixir-autogen, elixir-format -msgid "Transactions sent" -msgstr "" - #: lib/block_scout_web/templates/address/overview.html.eex:213 #: lib/block_scout_web/templates/address/overview.html.eex:219 #: lib/block_scout_web/templates/address/overview.html.eex:227 diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 4ed272ce9984..c3c3d7628b7a 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -3091,6 +3091,7 @@ msgid "Transaction type, introduced in EIP-2718." msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:7 +#: lib/block_scout_web/templates/address/_tile.html.eex:31 #: lib/block_scout_web/templates/address/overview.html.eex:186 #: lib/block_scout_web/templates/address/overview.html.eex:192 #: lib/block_scout_web/templates/address/overview.html.eex:200 @@ -3109,11 +3110,6 @@ msgstr "" msgid "Transactions and address of creation." msgstr "" -#: lib/block_scout_web/templates/address/_tile.html.eex:31 -#, elixir-autogen, elixir-format -msgid "Transactions sent" -msgstr "" - #: lib/block_scout_web/templates/address/overview.html.eex:213 #: lib/block_scout_web/templates/address/overview.html.eex:219 #: lib/block_scout_web/templates/address/overview.html.eex:227 diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index d194e233177b..967c9cee5f1a 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -1708,7 +1708,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do end test "check nil", %{conn: conn} do - address = insert(:address, nonce: 1, fetched_coin_balance: 1) + address = insert(:address, transactions_count: 2, fetched_coin_balance: 1) request = get(conn, "/api/v2/addresses") @@ -2477,7 +2477,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do defp compare_item(%Address{} = address, json) do assert Address.checksum(address.hash) == json["hash"] - assert to_string(address.nonce + 1) == json["tx_count"] + assert to_string(address.transactions_count) == json["tx_count"] end defp compare_item(%Transaction{} = transaction, json) do diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 5717a4997e20..63d5935a3e6f 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -68,7 +68,6 @@ defmodule Explorer.Chain do alias Explorer.Chain.Block.{EmissionReward, Reward} alias Explorer.Chain.Cache.{ - Accounts, BlockNumber, Blocks, ContractsCounter, @@ -1986,64 +1985,6 @@ defmodule Explorer.Chain do |> Enum.into(%{}) end - @doc """ - Lists the top `t:Explorer.Chain.Address.t/0`'s' in descending order based on coin balance and address hash. - - """ - @spec list_top_addresses :: [{Address.t(), non_neg_integer()}] - def list_top_addresses(options \\ []) do - paging_options = Keyword.get(options, :paging_options, @default_paging_options) - - if is_nil(paging_options.key) do - paging_options.page_size - |> Accounts.take_enough() - |> case do - nil -> - get_addresses(options) - - accounts -> - Enum.map( - accounts, - &{&1, - if is_nil(&1.nonce) do - 0 - else - &1.nonce + 1 - end} - ) - end - else - fetch_top_addresses(options) - end - end - - defp get_addresses(options) do - accounts_with_n = fetch_top_addresses(options) - - accounts_with_n - |> Enum.map(fn {address, _n} -> address end) - |> Accounts.update() - - accounts_with_n - end - - defp fetch_top_addresses(options) do - paging_options = Keyword.get(options, :paging_options, @default_paging_options) - - base_query = - from(a in Address, - where: a.fetched_coin_balance > ^0, - order_by: [desc: a.fetched_coin_balance, asc: a.hash], - preload: [:names, :smart_contract], - select: {a, fragment("coalesce(1 + ?, 0)", a.nonce)} - ) - - base_query - |> page_addresses(paging_options) - |> limit(^paging_options.page_size) - |> select_repo(options).all() - end - @doc """ Lists the top `t:Explorer.Chain.Token.t/0`'s'. @@ -3689,16 +3630,6 @@ defmodule Explorer.Chain do end) end - defp page_addresses(query, %PagingOptions{key: nil}), do: query - - defp page_addresses(query, %PagingOptions{key: {coin_balance, hash}}) do - from(address in query, - where: - (address.fetched_coin_balance == ^coin_balance and address.hash > ^hash) or - address.fetched_coin_balance < ^coin_balance - ) - end - defp page_blocks(query, %PagingOptions{key: nil}), do: query defp page_blocks(query, %PagingOptions{key: {block_number}}) do diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index 5803f3573227..e8ea2300590a 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -8,7 +8,7 @@ defmodule Explorer.Chain.Address do use Explorer.Schema alias Ecto.Changeset - alias Explorer.Chain + alias Explorer.{Chain, PagingOptions} alias Explorer.Chain.{ Address, @@ -25,7 +25,7 @@ defmodule Explorer.Chain.Address do Withdrawal } - alias Explorer.Chain.Cache.NetVersion + alias Explorer.Chain.Cache.{Accounts, NetVersion} @optional_attrs ~w(contract_code fetched_coin_balance fetched_coin_balance_block_number nonce decompiled verified gas_used transactions_count token_transfers_count)a @required_attrs ~w(hash)a @@ -304,4 +304,68 @@ defmodule Explorer.Chain.Address do @for.checksum(address) end end + + @default_paging_options %PagingOptions{page_size: 50} + @doc """ + Lists the top `t:Explorer.Chain.Address.t/0`'s' in descending order based on coin balance and address hash. + + """ + @spec list_top_addresses :: [{Address.t(), non_neg_integer()}] + def list_top_addresses(options \\ []) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + + if is_nil(paging_options.key) do + paging_options.page_size + |> Accounts.take_enough() + |> case do + nil -> + get_addresses(options) + + accounts -> + Enum.map( + accounts, + &{&1, &1.transactions_count || 0} + ) + end + else + fetch_top_addresses(options) + end + end + + defp get_addresses(options) do + accounts_with_n = fetch_top_addresses(options) + + accounts_with_n + |> Enum.map(fn {address, _n} -> address end) + |> Accounts.update() + + accounts_with_n + end + + defp fetch_top_addresses(options) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + + base_query = + from(a in Address, + where: a.fetched_coin_balance > ^0, + order_by: [desc: a.fetched_coin_balance, asc: a.hash], + preload: [:names, :smart_contract], + select: {a, a.transactions_count} + ) + + base_query + |> page_addresses(paging_options) + |> limit(^paging_options.page_size) + |> Chain.select_repo(options).all() + end + + defp page_addresses(query, %PagingOptions{key: nil}), do: query + + defp page_addresses(query, %PagingOptions{key: {coin_balance, hash}}) do + from(address in query, + where: + (address.fetched_coin_balance == ^coin_balance and address.hash > ^hash) or + address.fetched_coin_balance < ^coin_balance + ) + end end diff --git a/apps/explorer/test/explorer/chain/address_test.exs b/apps/explorer/test/explorer/chain/address_test.exs index 5b01b27ade3d..c6ca32494652 100644 --- a/apps/explorer/test/explorer/chain/address_test.exs +++ b/apps/explorer/test/explorer/chain/address_test.exs @@ -63,4 +63,74 @@ defmodule Explorer.Chain.AddressTest do assert str("0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb") == "0xD1220A0Cf47c7B9BE7a2e6ba89F429762E7B9adB" end end + + describe "list_top_addresses/0" do + test "without addresses with balance > 0" do + insert(:address, fetched_coin_balance: 0) + assert [] = Address.list_top_addresses() + end + + test "with top addresses in order" do + address_hashes = + 4..1 + |> Enum.map(&insert(:address, fetched_coin_balance: &1)) + |> Enum.map(& &1.hash) + + assert address_hashes == + Address.list_top_addresses() + |> Enum.map(fn {address, _transaction_count} -> address end) + |> Enum.map(& &1.hash) + end + + # flaky test + # test "with top addresses in order with matching value" do + # test_hashes = + # 4..0 + # |> Enum.map(&Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, &1)) + # |> Enum.map(&elem(&1, 1)) + + # tail = + # 4..1 + # |> Enum.map(&insert(:address, fetched_coin_balance: &1, hash: Enum.fetch!(test_hashes, &1 - 1))) + # |> Enum.map(& &1.hash) + + # first_result_hash = + # :address + # |> insert(fetched_coin_balance: 4, hash: Enum.fetch!(test_hashes, 4)) + # |> Map.fetch!(:hash) + + # assert [first_result_hash | tail] == + # Address.list_top_addresses() + # |> Enum.map(fn {address, _transaction_count} -> address end) + # |> Enum.map(& &1.hash) + # end + + # flaky test + # test "paginates addresses" do + # test_hashes = + # 4..0 + # |> Enum.map(&Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, &1)) + # |> Enum.map(&elem(&1, 1)) + + # result = + # 4..1 + # |> Enum.map(&insert(:address, fetched_coin_balance: &1, hash: Enum.fetch!(test_hashes, &1 - 1))) + # |> Enum.map(& &1.hash) + + # options = [paging_options: %PagingOptions{page_size: 1}] + + # [{top_address, _}] = Chain.list_top_addresses(options) + # assert top_address.hash == List.first(result) + + # tail_options = [ + # paging_options: %PagingOptions{key: {top_address.fetched_coin_balance.value, top_address.hash}, page_size: 3} + # ] + + # tail_result = tail_options |> Address.list_top_addresses() |> Enum.map(fn {address, _} -> address.hash end) + + # [_ | expected_tail] = result + + # assert tail_result == expected_tail + # end + end end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 77f9030f0f95..96a787543a8c 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -2058,76 +2058,6 @@ defmodule Explorer.ChainTest do end end - describe "list_top_addresses/0" do - test "without addresses with balance > 0" do - insert(:address, fetched_coin_balance: 0) - assert [] = Chain.list_top_addresses() - end - - test "with top addresses in order" do - address_hashes = - 4..1 - |> Enum.map(&insert(:address, fetched_coin_balance: &1)) - |> Enum.map(& &1.hash) - - assert address_hashes == - Chain.list_top_addresses() - |> Enum.map(fn {address, _transaction_count} -> address end) - |> Enum.map(& &1.hash) - end - - # flaky test - # test "with top addresses in order with matching value" do - # test_hashes = - # 4..0 - # |> Enum.map(&Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, &1)) - # |> Enum.map(&elem(&1, 1)) - - # tail = - # 4..1 - # |> Enum.map(&insert(:address, fetched_coin_balance: &1, hash: Enum.fetch!(test_hashes, &1 - 1))) - # |> Enum.map(& &1.hash) - - # first_result_hash = - # :address - # |> insert(fetched_coin_balance: 4, hash: Enum.fetch!(test_hashes, 4)) - # |> Map.fetch!(:hash) - - # assert [first_result_hash | tail] == - # Chain.list_top_addresses() - # |> Enum.map(fn {address, _transaction_count} -> address end) - # |> Enum.map(& &1.hash) - # end - - # flaky test - # test "paginates addresses" do - # test_hashes = - # 4..0 - # |> Enum.map(&Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, &1)) - # |> Enum.map(&elem(&1, 1)) - - # result = - # 4..1 - # |> Enum.map(&insert(:address, fetched_coin_balance: &1, hash: Enum.fetch!(test_hashes, &1 - 1))) - # |> Enum.map(& &1.hash) - - # options = [paging_options: %PagingOptions{page_size: 1}] - - # [{top_address, _}] = Chain.list_top_addresses(options) - # assert top_address.hash == List.first(result) - - # tail_options = [ - # paging_options: %PagingOptions{key: {top_address.fetched_coin_balance.value, top_address.hash}, page_size: 3} - # ] - - # tail_result = tail_options |> Chain.list_top_addresses() |> Enum.map(fn {address, _} -> address.hash end) - - # [_ | expected_tail] = result - - # assert tail_result == expected_tail - # end - end - describe "stream_blocks_without_rewards/2" do test "includes consensus blocks" do %Block{hash: consensus_hash} = insert(:block, consensus: true) From ef9aa010f27053831f1ab688a16c13a98a20f521 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 18:07:31 +0000 Subject: [PATCH 058/607] Bump ecto_sql from 3.10.2 to 3.11.0 Bumps [ecto_sql](https://github.com/elixir-ecto/ecto_sql) from 3.10.2 to 3.11.0. - [Changelog](https://github.com/elixir-ecto/ecto_sql/blob/master/CHANGELOG.md) - [Commits](https://github.com/elixir-ecto/ecto_sql/compare/v3.10.2...v3.11.0) --- updated-dependencies: - dependency-name: ecto_sql dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index b44a533985d3..aae1da0f115e 100644 --- a/mix.lock +++ b/mix.lock @@ -30,15 +30,15 @@ "credo": {:hex, :credo, "1.7.1", "6e26bbcc9e22eefbff7e43188e69924e78818e2fe6282487d0703652bc20fd62", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e9871c6095a4c0381c89b6aa98bc6260a8ba6addccf7f6a53da8849c748a58a2"}, "csv": {:hex, :csv, "2.5.0", "c47b5a5221bf2e56d6e8eb79e77884046d7fd516280dc7d9b674251e0ae46246", [:mix], [{:parallel_stream, "~> 1.0.4 or ~> 1.1.0", [hex: :parallel_stream, repo: "hexpm", optional: false]}], "hexpm", "e821f541487045c7591a1963eeb42afff0dfa99bdcdbeb3410795a2f59c77d34"}, "dataloader": {:hex, :dataloader, "1.0.11", "49bbfc7dd8a1990423c51000b869b1fecaab9e3ccd6b29eab51616ae8ad0a2f5", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba0b0ec532ec68e9d033d03553561d693129bd7cbd5c649dc7903f07ffba08fe"}, - "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"}, + "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "decorator": {:hex, :decorator, "1.4.0", "a57ac32c823ea7e4e67f5af56412d12b33274661bb7640ec7fc882f8d23ac419", [:mix], [], "hexpm", "0a07cedd9083da875c7418dea95b78361197cf2bf3211d743f6f7ce39656597f"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, "digital_token": {:hex, :digital_token, "0.6.0", "13e6de581f0b1f6c686f7c7d12ab11a84a7b22fa79adeb4b50eec1a2d278d258", [:mix], [{:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "2455d626e7c61a128b02a4a8caddb092548c3eb613ac6f6a85e4cbb6caddc4d1"}, "earmark_parser": {:hex, :earmark_parser, "1.4.37", "2ad73550e27c8946648b06905a57e4d454e4d7229c2dafa72a0348c99d8be5f7", [:mix], [], "hexpm", "6b19783f2802f039806f375610faa22da130b8edc21209d0bff47918bb48360e"}, - "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, - "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"}, + "ecto": {:hex, :ecto, "3.11.0", "ff8614b4e70a774f9d39af809c426def80852048440e8785d93a6e91f48fec00", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7769dad267ef967310d6e988e92d772659b11b09a0c015f101ce0fff81ce1f81"}, + "ecto_sql": {:hex, :ecto_sql, "3.11.0", "c787b24b224942b69c9ff7ab9107f258ecdc68326be04815c6cce2941b6fad1c", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "77aa3677169f55c2714dda7352d563002d180eb33c0dc29cd36d39c0a1a971f5"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_abi": {:hex, :ex_abi, "0.6.4", "f722a38298f176dab511cf94627b2815282669255bc2eb834674f23ca71f5cfb", [:mix], [{:ex_keccak, "~> 0.7.3", [hex: :ex_keccak, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "07eaf39b70dd3beac1286c10368d27091a9a64844830eb26a38f1c8d8b19dfbb"}, From 213590215bebbcfa48dee145bcb584d99dc90e1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 18:49:14 +0000 Subject: [PATCH 059/607] Bump core-js from 3.33.2 to 3.33.3 in /apps/block_scout_web/assets Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.33.2 to 3.33.3. - [Release notes](https://github.com/zloirock/core-js/releases) - [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/zloirock/core-js/commits/v3.33.3/packages/core-js) --- updated-dependencies: - dependency-name: core-js dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 0fc330c2a4a0..f30ccead9baf 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -17,7 +17,7 @@ "chart.js": "^4.4.0", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", - "core-js": "^3.33.2", + "core-js": "^3.33.3", "crypto-browserify": "^3.12.0", "dropzone": "^5.9.3", "eth-net-props": "^1.0.41", @@ -5937,9 +5937,9 @@ } }, "node_modules/core-js": { - "version": "3.33.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.2.tgz", - "integrity": "sha512-XeBzWI6QL3nJQiHmdzbAOiMYqjrb7hwU7A39Qhvd/POSa/t9E1AeZyEZx3fNvp/vtM8zXwhoL0FsiS0hD0pruQ==", + "version": "3.33.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz", + "integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -22219,9 +22219,9 @@ } }, "core-js": { - "version": "3.33.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.2.tgz", - "integrity": "sha512-XeBzWI6QL3nJQiHmdzbAOiMYqjrb7hwU7A39Qhvd/POSa/t9E1AeZyEZx3fNvp/vtM8zXwhoL0FsiS0hD0pruQ==" + "version": "3.33.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz", + "integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==" }, "core-js-compat": { "version": "3.33.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index b6d4b193cf2c..fc6db5e6c5f7 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -29,7 +29,7 @@ "chart.js": "^4.4.0", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", - "core-js": "^3.33.2", + "core-js": "^3.33.3", "crypto-browserify": "^3.12.0", "dropzone": "^5.9.3", "eth-net-props": "^1.0.41", From d84ee9d747d834efff4faddd5025016a01d11336 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 18:49:46 +0000 Subject: [PATCH 060/607] Bump @amplitude/analytics-browser in /apps/block_scout_web/assets Bumps [@amplitude/analytics-browser](https://github.com/amplitude/Amplitude-TypeScript) from 2.3.3 to 2.3.5. - [Release notes](https://github.com/amplitude/Amplitude-TypeScript/releases) - [Commits](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@2.3.3...@amplitude/analytics-browser@2.3.5) --- updated-dependencies: - dependency-name: "@amplitude/analytics-browser" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 130 +++++++++--------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 66 insertions(+), 66 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 0fc330c2a4a0..3b0167df6826 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -7,7 +7,7 @@ "name": "blockscout", "license": "GPL-3.0", "dependencies": { - "@amplitude/analytics-browser": "^2.3.3", + "@amplitude/analytics-browser": "^2.3.5", "@fortawesome/fontawesome-free": "^6.4.2", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", @@ -116,15 +116,15 @@ } }, "node_modules/@amplitude/analytics-browser": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.3.tgz", - "integrity": "sha512-we1tw7fn+yyf2waAyw/XqBOTEwIEY19qXJ5B9d1Xc1SKNxb6HzU5IpD3zbTKwZLlCGKbkVY6wI3JxuuoYIAI2w==", - "dependencies": { - "@amplitude/analytics-client-common": "^2.0.7", - "@amplitude/analytics-core": "^2.1.0", - "@amplitude/analytics-types": "^2.3.0", - "@amplitude/plugin-page-view-tracking-browser": "^2.0.13", - "@amplitude/plugin-web-attribution-browser": "^2.0.13", + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.5.tgz", + "integrity": "sha512-KL9Yv0lXvCsWrCwwWAMB0kzTswBlTLxxyOAS//z0378ckQvszLYvQqje3K5t0AlXrG728cLvKcBFveZ/UgUWfg==", + "dependencies": { + "@amplitude/analytics-client-common": "^2.0.8", + "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-types": "^2.3.1", + "@amplitude/plugin-page-view-tracking-browser": "^2.0.15", + "@amplitude/plugin-web-attribution-browser": "^2.0.15", "tslib": "^2.4.1" } }, @@ -134,13 +134,13 @@ "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" }, "node_modules/@amplitude/analytics-client-common": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.7.tgz", - "integrity": "sha512-2LqPMsY6ksS1OTda0EFPLUDFmIVTwU0bDN17+uY9WvpWTEWCAL616X2oNCQ6FTB9zWfCTr8X2I5jSS0y4rzPkw==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.8.tgz", + "integrity": "sha512-zKD/txmMFPfSVtT2gdZw+Tf07pZQEcPcB6X39+a+Wh8PjIIADYIeq6zL/2pn/9uwMkVz66sbKABKbq69XxPfCA==", "dependencies": { "@amplitude/analytics-connector": "^1.4.8", - "@amplitude/analytics-core": "^2.1.0", - "@amplitude/analytics-types": "^2.3.0", + "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" } }, @@ -155,11 +155,11 @@ "integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g==" }, "node_modules/@amplitude/analytics-core": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.0.tgz", - "integrity": "sha512-a7/WUacF+R6J8NTYf93gaVd3OjOyF0db2K9Y6+uSZp/xIbsGyR/47WN1R3CYPm4IC9Y9/ukBAHd/p4FsNJbsNg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.1.tgz", + "integrity": "sha512-2dHHiOnK7J/0Uk3gqu70JEvCKSgNBAXIUvw6u7bEHCQHBBW3ulpsVRSQomBeruyBBLKjgarwgawGs3yJrjIDkA==", "dependencies": { - "@amplitude/analytics-types": "^2.3.0", + "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" } }, @@ -169,17 +169,17 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@amplitude/analytics-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-2.3.0.tgz", - "integrity": "sha512-/sMCgimMzDjGZDh5ekHUrwKVi4IJc8/AFUXIxEuJn4wd9QVmThWNeKfszA36z6Ue+UOHhEUEZwruXo3PwXWz/g==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-2.3.1.tgz", + "integrity": "sha512-yojBG20qvph0rpCJKb4i/FJa+otqLINEwv//hfzvjnCOcPPyS0YscI8oiRBM0rG7kZIDgaL9a6jPwkqK4ACmcw==" }, "node_modules/@amplitude/plugin-page-view-tracking-browser": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.13.tgz", - "integrity": "sha512-23X7tEublqLx6cs7o7KXyQ9UdfX0lSVNHDgurhuGdAV8yEeTdg9bRvVHGZETZtnbQj31avD7M2U31nXXaopX3A==", + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.15.tgz", + "integrity": "sha512-iVviovZWROodoNs984dAslm3vCkMsl6bhIq5K0Tabt4ffi4ygIqlhdV8vj4Grr8u6mGtjgEzFchCkxdzb9TU1A==", "dependencies": { - "@amplitude/analytics-client-common": "^2.0.7", - "@amplitude/analytics-types": "^2.3.0", + "@amplitude/analytics-client-common": "^2.0.8", + "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" } }, @@ -189,13 +189,13 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@amplitude/plugin-web-attribution-browser": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.13.tgz", - "integrity": "sha512-oVsVxNk/twdcE5r5v+ALX3C443Q1NQGTaKNvbxyO/k3DiEGmKpgC8hSj9S5O7ZdrII63cS+u0IHevRXgmEnplg==", + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.15.tgz", + "integrity": "sha512-HSS6j2a40iSIKwug7ICzezXl6ag+cj7YU7cFbYiSF+cmNIVg4jPYgVCbTqq+IivOw+VW07pr0o+jz0ArL/Lyiw==", "dependencies": { - "@amplitude/analytics-client-common": "^2.0.7", - "@amplitude/analytics-core": "^2.1.0", - "@amplitude/analytics-types": "^2.3.0", + "@amplitude/analytics-client-common": "^2.0.8", + "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" } }, @@ -17854,15 +17854,15 @@ "dev": true }, "@amplitude/analytics-browser": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.3.tgz", - "integrity": "sha512-we1tw7fn+yyf2waAyw/XqBOTEwIEY19qXJ5B9d1Xc1SKNxb6HzU5IpD3zbTKwZLlCGKbkVY6wI3JxuuoYIAI2w==", - "requires": { - "@amplitude/analytics-client-common": "^2.0.7", - "@amplitude/analytics-core": "^2.1.0", - "@amplitude/analytics-types": "^2.3.0", - "@amplitude/plugin-page-view-tracking-browser": "^2.0.13", - "@amplitude/plugin-web-attribution-browser": "^2.0.13", + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.5.tgz", + "integrity": "sha512-KL9Yv0lXvCsWrCwwWAMB0kzTswBlTLxxyOAS//z0378ckQvszLYvQqje3K5t0AlXrG728cLvKcBFveZ/UgUWfg==", + "requires": { + "@amplitude/analytics-client-common": "^2.0.8", + "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-types": "^2.3.1", + "@amplitude/plugin-page-view-tracking-browser": "^2.0.15", + "@amplitude/plugin-web-attribution-browser": "^2.0.15", "tslib": "^2.4.1" }, "dependencies": { @@ -17874,13 +17874,13 @@ } }, "@amplitude/analytics-client-common": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.7.tgz", - "integrity": "sha512-2LqPMsY6ksS1OTda0EFPLUDFmIVTwU0bDN17+uY9WvpWTEWCAL616X2oNCQ6FTB9zWfCTr8X2I5jSS0y4rzPkw==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.8.tgz", + "integrity": "sha512-zKD/txmMFPfSVtT2gdZw+Tf07pZQEcPcB6X39+a+Wh8PjIIADYIeq6zL/2pn/9uwMkVz66sbKABKbq69XxPfCA==", "requires": { "@amplitude/analytics-connector": "^1.4.8", - "@amplitude/analytics-core": "^2.1.0", - "@amplitude/analytics-types": "^2.3.0", + "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" }, "dependencies": { @@ -17897,11 +17897,11 @@ "integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g==" }, "@amplitude/analytics-core": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.0.tgz", - "integrity": "sha512-a7/WUacF+R6J8NTYf93gaVd3OjOyF0db2K9Y6+uSZp/xIbsGyR/47WN1R3CYPm4IC9Y9/ukBAHd/p4FsNJbsNg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.1.tgz", + "integrity": "sha512-2dHHiOnK7J/0Uk3gqu70JEvCKSgNBAXIUvw6u7bEHCQHBBW3ulpsVRSQomBeruyBBLKjgarwgawGs3yJrjIDkA==", "requires": { - "@amplitude/analytics-types": "^2.3.0", + "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" }, "dependencies": { @@ -17913,17 +17913,17 @@ } }, "@amplitude/analytics-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-2.3.0.tgz", - "integrity": "sha512-/sMCgimMzDjGZDh5ekHUrwKVi4IJc8/AFUXIxEuJn4wd9QVmThWNeKfszA36z6Ue+UOHhEUEZwruXo3PwXWz/g==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-2.3.1.tgz", + "integrity": "sha512-yojBG20qvph0rpCJKb4i/FJa+otqLINEwv//hfzvjnCOcPPyS0YscI8oiRBM0rG7kZIDgaL9a6jPwkqK4ACmcw==" }, "@amplitude/plugin-page-view-tracking-browser": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.13.tgz", - "integrity": "sha512-23X7tEublqLx6cs7o7KXyQ9UdfX0lSVNHDgurhuGdAV8yEeTdg9bRvVHGZETZtnbQj31avD7M2U31nXXaopX3A==", + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.15.tgz", + "integrity": "sha512-iVviovZWROodoNs984dAslm3vCkMsl6bhIq5K0Tabt4ffi4ygIqlhdV8vj4Grr8u6mGtjgEzFchCkxdzb9TU1A==", "requires": { - "@amplitude/analytics-client-common": "^2.0.7", - "@amplitude/analytics-types": "^2.3.0", + "@amplitude/analytics-client-common": "^2.0.8", + "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" }, "dependencies": { @@ -17935,13 +17935,13 @@ } }, "@amplitude/plugin-web-attribution-browser": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.13.tgz", - "integrity": "sha512-oVsVxNk/twdcE5r5v+ALX3C443Q1NQGTaKNvbxyO/k3DiEGmKpgC8hSj9S5O7ZdrII63cS+u0IHevRXgmEnplg==", + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.15.tgz", + "integrity": "sha512-HSS6j2a40iSIKwug7ICzezXl6ag+cj7YU7cFbYiSF+cmNIVg4jPYgVCbTqq+IivOw+VW07pr0o+jz0ArL/Lyiw==", "requires": { - "@amplitude/analytics-client-common": "^2.0.7", - "@amplitude/analytics-core": "^2.1.0", - "@amplitude/analytics-types": "^2.3.0", + "@amplitude/analytics-client-common": "^2.0.8", + "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" }, "dependencies": { diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index b6d4b193cf2c..0ba88716bfc1 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^6.4.2", - "@amplitude/analytics-browser": "^2.3.3", + "@amplitude/analytics-browser": "^2.3.5", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", "assert": "^2.1.0", From 7f5d6dd209e7552d9211fdf9193dd601d2c29b08 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 18:50:18 +0000 Subject: [PATCH 061/607] Bump eslint from 8.53.0 to 8.54.0 in /apps/block_scout_web/assets Bumps [eslint](https://github.com/eslint/eslint) from 8.53.0 to 8.54.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v8.53.0...v8.54.0) --- updated-dependencies: - dependency-name: eslint dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 30 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 0fc330c2a4a0..75bd49a4bfcc 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -78,7 +78,7 @@ "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.8.1", "css-minimizer-webpack-plugin": "^5.0.1", - "eslint": "^8.53.0", + "eslint": "^8.54.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.0", "eslint-plugin-node": "^11.1.0", @@ -2122,9 +2122,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", - "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -7306,15 +7306,15 @@ } }, "node_modules/eslint": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", - "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.53.0", + "@eslint/js": "8.54.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -19294,9 +19294,9 @@ } }, "@eslint/js": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", - "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", "dev": true }, "@ethereumjs/common": { @@ -23254,15 +23254,15 @@ } }, "eslint": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", - "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.53.0", + "@eslint/js": "8.54.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index b6d4b193cf2c..f249705bde14 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -90,7 +90,7 @@ "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.8.1", "css-minimizer-webpack-plugin": "^5.0.1", - "eslint": "^8.53.0", + "eslint": "^8.54.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.0", "eslint-plugin-node": "^11.1.0", From 98ded8ed2760a3b6f53824062fdac0142d128042 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 18:50:40 +0000 Subject: [PATCH 062/607] Bump sweetalert2 from 11.9.0 to 11.10.1 in /apps/block_scout_web/assets Bumps [sweetalert2](https://github.com/sweetalert2/sweetalert2) from 11.9.0 to 11.10.1. - [Release notes](https://github.com/sweetalert2/sweetalert2/releases) - [Changelog](https://github.com/sweetalert2/sweetalert2/blob/main/CHANGELOG.md) - [Commits](https://github.com/sweetalert2/sweetalert2/compare/v11.9.0...v11.10.1) --- updated-dependencies: - dependency-name: sweetalert2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 0fc330c2a4a0..d00970545f78 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -61,7 +61,7 @@ "redux": "^4.2.1", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", - "sweetalert2": "^11.9.0", + "sweetalert2": "^11.10.1", "urijs": "^1.19.11", "url": "^0.11.3", "util": "^0.12.5", @@ -16086,9 +16086,9 @@ } }, "node_modules/sweetalert2": { - "version": "11.9.0", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.9.0.tgz", - "integrity": "sha512-PA3qinKZMNGAhA+AUu2wU7yQOpeZCgOaYWRcg26f4cZN6f7M9iPBuobsxOhR9EHs7ihUIxT6vhAMiB4kcmk1SA==", + "version": "11.10.1", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.1.tgz", + "integrity": "sha512-qu145oBuFfjYr5yZW9OSdG6YmRxDf8CnkgT/sXMfrXGe+asFy2imC2vlaLQ/L/naZ/JZna1MPAY56G4qYM0VUQ==", "funding": { "type": "individual", "url": "https://github.com/sponsors/limonte" @@ -29891,9 +29891,9 @@ } }, "sweetalert2": { - "version": "11.9.0", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.9.0.tgz", - "integrity": "sha512-PA3qinKZMNGAhA+AUu2wU7yQOpeZCgOaYWRcg26f4cZN6f7M9iPBuobsxOhR9EHs7ihUIxT6vhAMiB4kcmk1SA==" + "version": "11.10.1", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.1.tgz", + "integrity": "sha512-qu145oBuFfjYr5yZW9OSdG6YmRxDf8CnkgT/sXMfrXGe+asFy2imC2vlaLQ/L/naZ/JZna1MPAY56G4qYM0VUQ==" }, "symbol-tree": { "version": "3.2.4", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index b6d4b193cf2c..ecb236f04216 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -73,7 +73,7 @@ "redux": "^4.2.1", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", - "sweetalert2": "^11.9.0", + "sweetalert2": "^11.10.1", "urijs": "^1.19.11", "url": "^0.11.3", "util": "^0.12.5", From c1fdee0b5e5f89200e7c21bf74c898c505623953 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 18:50:59 +0000 Subject: [PATCH 063/607] Bump mixpanel-browser in /apps/block_scout_web/assets Bumps [mixpanel-browser](https://github.com/mixpanel/mixpanel-js) from 2.47.0 to 2.48.1. - [Release notes](https://github.com/mixpanel/mixpanel-js/releases) - [Changelog](https://github.com/mixpanel/mixpanel-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/mixpanel/mixpanel-js/compare/v2.47.0...v2.48.1) --- updated-dependencies: - dependency-name: mixpanel-browser dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 0fc330c2a4a0..e76404be8dc1 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -46,7 +46,7 @@ "lodash.reduce": "^4.6.0", "luxon": "^3.4.4", "malihu-custom-scrollbar-plugin": "3.1.5", - "mixpanel-browser": "^2.47.0", + "mixpanel-browser": "^2.48.1", "moment": "^2.29.4", "nanomorph": "^5.4.0", "numeral": "^2.0.6", @@ -12885,9 +12885,9 @@ } }, "node_modules/mixpanel-browser": { - "version": "2.47.0", - "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.47.0.tgz", - "integrity": "sha512-Ldrva0fRBEIFWmEibBQO1PulfpJVF3pf28Guk09lDirDaSQqqU/xs9zQLwN2rL5VwVtsP1aD3JaCgaa98EjojQ==" + "version": "2.48.1", + "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.48.1.tgz", + "integrity": "sha512-vXTuUzZMg+ht7sRqyjtc3dUDy/81Z/H6FLFgFkUZJqKFaAqcx1JSXmOdY/2kmsxCkUdy5JN5zW9m9TMCk+rxGQ==" }, "node_modules/mkdirp": { "version": "3.0.1", @@ -27592,9 +27592,9 @@ } }, "mixpanel-browser": { - "version": "2.47.0", - "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.47.0.tgz", - "integrity": "sha512-Ldrva0fRBEIFWmEibBQO1PulfpJVF3pf28Guk09lDirDaSQqqU/xs9zQLwN2rL5VwVtsP1aD3JaCgaa98EjojQ==" + "version": "2.48.1", + "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.48.1.tgz", + "integrity": "sha512-vXTuUzZMg+ht7sRqyjtc3dUDy/81Z/H6FLFgFkUZJqKFaAqcx1JSXmOdY/2kmsxCkUdy5JN5zW9m9TMCk+rxGQ==" }, "mkdirp": { "version": "3.0.1", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index b6d4b193cf2c..adbec7d51aa4 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -58,7 +58,7 @@ "lodash.reduce": "^4.6.0", "luxon": "^3.4.4", "malihu-custom-scrollbar-plugin": "3.1.5", - "mixpanel-browser": "^2.47.0", + "mixpanel-browser": "^2.48.1", "moment": "^2.29.4", "nanomorph": "^5.4.0", "numeral": "^2.0.6", From 32a8e811813ac079257775371a1550b64314bdfb Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 21 Nov 2023 00:23:23 +0400 Subject: [PATCH 064/607] v5.3.2 --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .../publish-docker-image-every-push.yml | 2 +- .../publish-docker-image-for-core.yml | 2 +- .../publish-docker-image-for-eth-goerli.yml | 2 +- .../publish-docker-image-for-eth-sepolia.yml | 2 +- .../publish-docker-image-for-eth.yml | 2 +- .../publish-docker-image-for-fuse.yml | 2 +- .../publish-docker-image-for-immutable.yml | 2 +- .../publish-docker-image-for-l2-staging.yml | 2 +- .../publish-docker-image-for-lukso.yml | 2 +- .../publish-docker-image-for-optimism.yml | 2 +- .../publish-docker-image-for-polygon-edge.yml | 2 +- .../publish-docker-image-for-rsk.yml | 2 +- .../publish-docker-image-for-stability.yml | 2 +- .../publish-docker-image-for-suave.yml | 2 +- .../publish-docker-image-for-xdai.yml | 2 +- .../publish-docker-image-for-zkevm.yml | 2 +- .../publish-docker-image-for-zksync.yml | 2 +- ...ublish-docker-image-release-additional.yml | 2 +- .../publish-docker-image-release.yml | 4 +-- CHANGELOG.md | 28 +++++++++++++++++++ apps/block_scout_web/mix.exs | 2 +- apps/ethereum_jsonrpc/mix.exs | 2 +- apps/explorer/mix.exs | 2 +- apps/indexer/mix.exs | 2 +- docker-compose/docker-compose.yml | 2 +- docker/Makefile | 2 +- mix.exs | 2 +- rel/config.exs | 2 +- 29 files changed, 57 insertions(+), 29 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index d8441328e0cc..1888fe929049 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -65,7 +65,7 @@ body: attributes: label: Backend version description: The release version of the backend or branch/commit. - placeholder: v5.3.1 + placeholder: v5.3.2 validations: required: true diff --git a/.github/workflows/publish-docker-image-every-push.yml b/.github/workflows/publish-docker-image-every-push.yml index 25d5b90e1470..98fc8a4de88b 100644 --- a/.github/workflows/publish-docker-image-every-push.yml +++ b/.github/workflows/publish-docker-image-every-push.yml @@ -7,7 +7,7 @@ on: env: OTP_VERSION: '25.2.1' ELIXIR_VERSION: '1.14.5' - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 jobs: push_to_registry: diff --git a/.github/workflows/publish-docker-image-for-core.yml b/.github/workflows/publish-docker-image-for-core.yml index 25bb973ee358..591a50a53f03 100644 --- a/.github/workflows/publish-docker-image-for-core.yml +++ b/.github/workflows/publish-docker-image-for-core.yml @@ -14,7 +14,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 DOCKER_CHAIN_NAME: poa steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-eth-goerli.yml b/.github/workflows/publish-docker-image-for-eth-goerli.yml index f871e4fa0b3b..c4028b0cbbb0 100644 --- a/.github/workflows/publish-docker-image-for-eth-goerli.yml +++ b/.github/workflows/publish-docker-image-for-eth-goerli.yml @@ -14,7 +14,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 DOCKER_CHAIN_NAME: eth-goerli steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-eth-sepolia.yml b/.github/workflows/publish-docker-image-for-eth-sepolia.yml index d53b987b0a1c..da11bfdfacbb 100644 --- a/.github/workflows/publish-docker-image-for-eth-sepolia.yml +++ b/.github/workflows/publish-docker-image-for-eth-sepolia.yml @@ -14,7 +14,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 DOCKER_CHAIN_NAME: eth-sepolia steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-eth.yml b/.github/workflows/publish-docker-image-for-eth.yml index 5ea173e78ae7..a7286696a5e5 100644 --- a/.github/workflows/publish-docker-image-for-eth.yml +++ b/.github/workflows/publish-docker-image-for-eth.yml @@ -14,7 +14,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 DOCKER_CHAIN_NAME: mainnet steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-fuse.yml b/.github/workflows/publish-docker-image-for-fuse.yml index 1f7501eab860..7aa400a5a715 100644 --- a/.github/workflows/publish-docker-image-for-fuse.yml +++ b/.github/workflows/publish-docker-image-for-fuse.yml @@ -14,7 +14,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 DOCKER_CHAIN_NAME: fuse steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-immutable.yml b/.github/workflows/publish-docker-image-for-immutable.yml index 67b0a7b70974..3423d5161547 100644 --- a/.github/workflows/publish-docker-image-for-immutable.yml +++ b/.github/workflows/publish-docker-image-for-immutable.yml @@ -14,7 +14,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 DOCKER_CHAIN_NAME: immutable steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-l2-staging.yml b/.github/workflows/publish-docker-image-for-l2-staging.yml index 21d2641d1d00..6fc944c70037 100644 --- a/.github/workflows/publish-docker-image-for-l2-staging.yml +++ b/.github/workflows/publish-docker-image-for-l2-staging.yml @@ -14,7 +14,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 DOCKER_CHAIN_NAME: optimism-l2-advanced steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-lukso.yml b/.github/workflows/publish-docker-image-for-lukso.yml index bc601f4bd280..9b5f6eaf7a3d 100644 --- a/.github/workflows/publish-docker-image-for-lukso.yml +++ b/.github/workflows/publish-docker-image-for-lukso.yml @@ -14,7 +14,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 DOCKER_CHAIN_NAME: lukso steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-optimism.yml b/.github/workflows/publish-docker-image-for-optimism.yml index e352d4c7353e..3a1f168fa02f 100644 --- a/.github/workflows/publish-docker-image-for-optimism.yml +++ b/.github/workflows/publish-docker-image-for-optimism.yml @@ -14,7 +14,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 DOCKER_CHAIN_NAME: optimism steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-polygon-edge.yml b/.github/workflows/publish-docker-image-for-polygon-edge.yml index a4de66bcfcd1..6087012e4c25 100644 --- a/.github/workflows/publish-docker-image-for-polygon-edge.yml +++ b/.github/workflows/publish-docker-image-for-polygon-edge.yml @@ -14,7 +14,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 DOCKER_CHAIN_NAME: polygon-edge steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-rsk.yml b/.github/workflows/publish-docker-image-for-rsk.yml index 424aece44e8b..5cf04ac303ba 100644 --- a/.github/workflows/publish-docker-image-for-rsk.yml +++ b/.github/workflows/publish-docker-image-for-rsk.yml @@ -14,7 +14,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 DOCKER_CHAIN_NAME: rsk steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-stability.yml b/.github/workflows/publish-docker-image-for-stability.yml index 57fd1e0fcb7c..0c04104be197 100644 --- a/.github/workflows/publish-docker-image-for-stability.yml +++ b/.github/workflows/publish-docker-image-for-stability.yml @@ -17,7 +17,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 DOCKER_CHAIN_NAME: stability steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-suave.yml b/.github/workflows/publish-docker-image-for-suave.yml index 3e66ed92dc2b..e84a0115f13c 100644 --- a/.github/workflows/publish-docker-image-for-suave.yml +++ b/.github/workflows/publish-docker-image-for-suave.yml @@ -17,7 +17,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 DOCKER_CHAIN_NAME: suave steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-xdai.yml b/.github/workflows/publish-docker-image-for-xdai.yml index 6706ec927eb3..fca17ff2e083 100644 --- a/.github/workflows/publish-docker-image-for-xdai.yml +++ b/.github/workflows/publish-docker-image-for-xdai.yml @@ -14,7 +14,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 DOCKER_CHAIN_NAME: xdai steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-zkevm.yml b/.github/workflows/publish-docker-image-for-zkevm.yml index 374cae48a909..a47940869663 100644 --- a/.github/workflows/publish-docker-image-for-zkevm.yml +++ b/.github/workflows/publish-docker-image-for-zkevm.yml @@ -14,7 +14,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 DOCKER_CHAIN_NAME: zkevm steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-zksync.yml b/.github/workflows/publish-docker-image-for-zksync.yml index aee2f7f70a90..057112de2093 100644 --- a/.github/workflows/publish-docker-image-for-zksync.yml +++ b/.github/workflows/publish-docker-image-for-zksync.yml @@ -14,7 +14,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 DOCKER_CHAIN_NAME: zksync steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-release-additional.yml b/.github/workflows/publish-docker-image-release-additional.yml index 9877068bff23..9e05afff6f4b 100644 --- a/.github/workflows/publish-docker-image-release-additional.yml +++ b/.github/workflows/publish-docker-image-release-additional.yml @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 steps: - name: Check out the repo uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-release.yml b/.github/workflows/publish-docker-image-release.yml index df9aa7d44a9f..fe999a37f76b 100644 --- a/.github/workflows/publish-docker-image-release.yml +++ b/.github/workflows/publish-docker-image-release.yml @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 steps: - name: Check out the repo uses: actions/checkout@v4 @@ -46,7 +46,7 @@ jobs: push: true cache-from: type=registry,ref=blockscout/blockscout:buildcache cache-to: type=registry,ref=blockscout/blockscout:buildcache,mode=max - tags: blockscout/blockscout:latest, blockscout/blockscout:${{ env.RELEASE_VERSION }} + tags: blockscout/blockscout:latest, blockscout/blockscout:${{ env.RELEASE_VERSION }}-alpha platforms: | linux/amd64 linux/arm64/v8 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a91a5afd53f..631a44486690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ ### Features +### Fixes + +### Chore + +## 5.3.2-beta + +### Features + - [#8848](https://github.com/blockscout/blockscout/pull/8848) - Add MainPageRealtimeEventHandler - [#8821](https://github.com/blockscout/blockscout/pull/8821) - Add new events to addresses channel: `eth_bytecode_db_lookup_started` and `smart_contract_was_not_verified` - [#8795](https://github.com/blockscout/blockscout/pull/8795) - Disable catchup indexer by env @@ -37,6 +45,26 @@
Dependencies version bumps + +- [#8727](https://github.com/blockscout/blockscout/pull/8727) - Bump browserify-sign from 4.2.1 to 4.2.2 in /apps/block_scout_web/assets +- [#8748](https://github.com/blockscout/blockscout/pull/8748) - Bump sweetalert2 from 11.7.32 to 11.9.0 in /apps/block_scout_web/assets +- [#8747](https://github.com/blockscout/blockscout/pull/8747) - Bump core-js from 3.33.1 to 3.33.2 in /apps/block_scout_web/assets +- [#8743](https://github.com/blockscout/blockscout/pull/8743) - Bump solc from 0.8.21 to 0.8.22 in /apps/explorer +- [#8745](https://github.com/blockscout/blockscout/pull/8745) - Bump tesla from 1.7.0 to 1.8.0 +- [#8749](https://github.com/blockscout/blockscout/pull/8749) - Bump sass from 1.69.4 to 1.69.5 in /apps/block_scout_web/assets +- [#8744](https://github.com/blockscout/blockscout/pull/8744) - Bump phoenix_ecto from 4.4.2 to 4.4.3 +- [#8746](https://github.com/blockscout/blockscout/pull/8746) - Bump floki from 0.35.1 to 0.35.2 +- [#8793](https://github.com/blockscout/blockscout/pull/8793) - Bump eslint from 8.52.0 to 8.53.0 in /apps/block_scout_web/assets +- [#8792](https://github.com/blockscout/blockscout/pull/8792) - Bump cldr_utils from 2.24.1 to 2.24.2 +- [#8787](https://github.com/blockscout/blockscout/pull/8787) - Bump ex_cldr_numbers from 2.32.2 to 2.32.3 +- [#8790](https://github.com/blockscout/blockscout/pull/8790) - Bump ex_abi from 0.6.3 to 0.6.4 +- [#8788](https://github.com/blockscout/blockscout/pull/8788) - Bump ex_cldr_units from 3.16.3 to 3.16.4 +- [#8827](https://github.com/blockscout/blockscout/pull/8827) - Bump @babel/core from 7.23.2 to 7.23.3 in /apps/block_scout_web/assets +- [#8823](https://github.com/blockscout/blockscout/pull/8823) - Bump benchee from 1.1.0 to 1.2.0 +- [#8826](https://github.com/blockscout/blockscout/pull/8826) - Bump luxon from 3.4.3 to 3.4.4 in /apps/block_scout_web/assets +- [#8824](https://github.com/blockscout/blockscout/pull/8824) - Bump httpoison from 2.1.0 to 2.2.0 +- [#8828](https://github.com/blockscout/blockscout/pull/8828) - Bump @babel/preset-env from 7.23.2 to 7.23.3 in /apps/block_scout_web/assets +- [#8825](https://github.com/blockscout/blockscout/pull/8825) - Bump solc from 0.8.22 to 0.8.23 in /apps/explorer
## 5.3.1-beta diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index 17b4a79de407..f134b2b3373f 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -23,7 +23,7 @@ defmodule BlockScoutWeb.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "5.3.1", + version: "5.3.2", xref: [exclude: [Explorer.Chain.Zkevm.Reader]] ] end diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index d4496a620da3..608b2bb28b38 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -23,7 +23,7 @@ defmodule EthereumJsonrpc.MixProject do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "5.3.1" + version: "5.3.2" ] end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 6d903ff853b6..fd91cf515e59 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -24,7 +24,7 @@ defmodule Explorer.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "5.3.1", + version: "5.3.2", xref: [exclude: [BlockScoutWeb.WebRouter.Helpers]] ] end diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs index 6444e6596182..e3454f3318eb 100644 --- a/apps/indexer/mix.exs +++ b/apps/indexer/mix.exs @@ -14,7 +14,7 @@ defmodule Indexer.MixProject do elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", start_permanent: Mix.env() == :prod, - version: "5.3.1" + version: "5.3.2" ] end diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index c5f140a31263..0ce792f80ded 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -34,7 +34,7 @@ services: CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED: "" CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL: "" ADMIN_PANEL_ENABLED: "" - RELEASE_VERSION: 5.3.1 + RELEASE_VERSION: 5.3.2 links: - db:database environment: diff --git a/docker/Makefile b/docker/Makefile index c6e874b4682c..e320e4c98356 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -10,7 +10,7 @@ STATS_CONTAINER_NAME := stats STATS_DB_CONTAINER_NAME := stats-postgres PROXY_CONTAINER_NAME := proxy PG_CONTAINER_NAME := postgres -RELEASE_VERSION ?= '5.3.1' +RELEASE_VERSION ?= '5.3.2' TAG := $(RELEASE_VERSION)-commit-$(shell git log -1 --pretty=format:"%h") STABLE_TAG := $(RELEASE_VERSION) diff --git a/mix.exs b/mix.exs index 12ddf19b66ee..f3a4838a8b3d 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule BlockScout.Mixfile do [ # app: :block_scout, # aliases: aliases(config_env()), - version: "5.3.1", + version: "5.3.2", apps_path: "apps", deps: deps(), dialyzer: dialyzer(), diff --git a/rel/config.exs b/rel/config.exs index a0584a8168b3..86844487f4aa 100644 --- a/rel/config.exs +++ b/rel/config.exs @@ -71,7 +71,7 @@ end # will be used by default release :blockscout do - set version: "5.3.1-beta" + set version: "5.3.2-beta" set applications: [ :runtime_tools, block_scout_web: :permanent, From 40a599134c971de5367e855d863cb0bbdbfb8734 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 21 Nov 2023 00:46:15 +0400 Subject: [PATCH 065/607] Configure prerelease workflows --- .../publish-docker-image-for-core.yml | 3 +- .../publish-docker-image-for-eth-goerli.yml | 3 +- .../publish-docker-image-for-eth-sepolia.yml | 3 +- .../publish-docker-image-for-eth.yml | 3 +- .../publish-docker-image-for-fuse.yml | 3 +- .../publish-docker-image-for-immutable.yml | 3 +- .../publish-docker-image-for-l2-staging.yml | 3 +- .../publish-docker-image-for-lukso.yml | 3 +- .../publish-docker-image-for-optimism.yml | 3 +- .../publish-docker-image-for-polygon-edge.yml | 3 +- .../publish-docker-image-for-rsk.yml | 3 +- .../publish-docker-image-for-stability.yml | 3 +- .../publish-docker-image-for-suave.yml | 3 +- .../publish-docker-image-for-xdai.yml | 3 +- .../publish-docker-image-for-zkevm.yml | 3 +- .../publish-docker-image-for-zksync.yml | 3 +- ...ublish-docker-image-release-additional.yml | 105 ----------- .../publish-docker-image-release.yml | 165 ------------------ 18 files changed, 32 insertions(+), 286 deletions(-) delete mode 100644 .github/workflows/publish-docker-image-release-additional.yml delete mode 100644 .github/workflows/publish-docker-image-release.yml diff --git a/.github/workflows/publish-docker-image-for-core.yml b/.github/workflows/publish-docker-image-for-core.yml index 591a50a53f03..b90942317baf 100644 --- a/.github/workflows/publish-docker-image-for-core.yml +++ b/.github/workflows/publish-docker-image-for-core.yml @@ -3,9 +3,10 @@ # separate terms of service, privacy policy, and support # documentation. -name: Publish Docker image for specific chain branches +name: POA Core Publish Docker image on: + workflow_dispatch: push: branches: - production-core-stg diff --git a/.github/workflows/publish-docker-image-for-eth-goerli.yml b/.github/workflows/publish-docker-image-for-eth-goerli.yml index c4028b0cbbb0..28b51f91c9ed 100644 --- a/.github/workflows/publish-docker-image-for-eth-goerli.yml +++ b/.github/workflows/publish-docker-image-for-eth-goerli.yml @@ -3,9 +3,10 @@ # separate terms of service, privacy policy, and support # documentation. -name: Publish Docker image for specific chain branches +name: ETH Goerli Publish Docker image on: + workflow_dispatch: push: branches: - production-eth-goerli-stg diff --git a/.github/workflows/publish-docker-image-for-eth-sepolia.yml b/.github/workflows/publish-docker-image-for-eth-sepolia.yml index da11bfdfacbb..28bedb51ee75 100644 --- a/.github/workflows/publish-docker-image-for-eth-sepolia.yml +++ b/.github/workflows/publish-docker-image-for-eth-sepolia.yml @@ -3,9 +3,10 @@ # separate terms of service, privacy policy, and support # documentation. -name: Publish Docker image for specific chain branches +name: ETH Sepolia Publish Docker image on: + workflow_dispatch: push: branches: - production-eth-sepolia-stg diff --git a/.github/workflows/publish-docker-image-for-eth.yml b/.github/workflows/publish-docker-image-for-eth.yml index a7286696a5e5..bfd8ae15198d 100644 --- a/.github/workflows/publish-docker-image-for-eth.yml +++ b/.github/workflows/publish-docker-image-for-eth.yml @@ -3,9 +3,10 @@ # separate terms of service, privacy policy, and support # documentation. -name: Publish Docker image for specific chain branches +name: ETH Publish Docker image on: + workflow_dispatch: push: branches: - production-eth-stg-experimental diff --git a/.github/workflows/publish-docker-image-for-fuse.yml b/.github/workflows/publish-docker-image-for-fuse.yml index 7aa400a5a715..b0d1eb68ff1e 100644 --- a/.github/workflows/publish-docker-image-for-fuse.yml +++ b/.github/workflows/publish-docker-image-for-fuse.yml @@ -3,9 +3,10 @@ # separate terms of service, privacy policy, and support # documentation. -name: Publish Docker image for specific chain branches +name: Fuse Publish Docker image on: + workflow_dispatch: push: branches: - production-fuse-stg diff --git a/.github/workflows/publish-docker-image-for-immutable.yml b/.github/workflows/publish-docker-image-for-immutable.yml index 3423d5161547..d86f7025cbc8 100644 --- a/.github/workflows/publish-docker-image-for-immutable.yml +++ b/.github/workflows/publish-docker-image-for-immutable.yml @@ -3,9 +3,10 @@ # separate terms of service, privacy policy, and support # documentation. -name: Publish Docker image for specific chain branches +name: Immutable Publish Docker image on: + workflow_dispatch: push: branches: - production-immutable-stg diff --git a/.github/workflows/publish-docker-image-for-l2-staging.yml b/.github/workflows/publish-docker-image-for-l2-staging.yml index 6fc944c70037..a6227eaee50a 100644 --- a/.github/workflows/publish-docker-image-for-l2-staging.yml +++ b/.github/workflows/publish-docker-image-for-l2-staging.yml @@ -3,9 +3,10 @@ # separate terms of service, privacy policy, and support # documentation. -name: Publish Docker image for specific chain branches +name: L2 staging Publish Docker image on: + workflow_dispatch: push: branches: - staging-l2 diff --git a/.github/workflows/publish-docker-image-for-lukso.yml b/.github/workflows/publish-docker-image-for-lukso.yml index 9b5f6eaf7a3d..f294165468c7 100644 --- a/.github/workflows/publish-docker-image-for-lukso.yml +++ b/.github/workflows/publish-docker-image-for-lukso.yml @@ -3,9 +3,10 @@ # separate terms of service, privacy policy, and support # documentation. -name: Publish Docker image for specific chain branches +name: LUKSO Publish Docker image on: + workflow_dispatch: push: branches: - production-lukso-stg diff --git a/.github/workflows/publish-docker-image-for-optimism.yml b/.github/workflows/publish-docker-image-for-optimism.yml index 3a1f168fa02f..c164e1b7d8eb 100644 --- a/.github/workflows/publish-docker-image-for-optimism.yml +++ b/.github/workflows/publish-docker-image-for-optimism.yml @@ -3,9 +3,10 @@ # separate terms of service, privacy policy, and support # documentation. -name: Publish Docker image for specific chain branches +name: Optimism Publish Docker image on: + workflow_dispatch: push: branches: - production-optimism-stg diff --git a/.github/workflows/publish-docker-image-for-polygon-edge.yml b/.github/workflows/publish-docker-image-for-polygon-edge.yml index 6087012e4c25..3b3f7604ca6e 100644 --- a/.github/workflows/publish-docker-image-for-polygon-edge.yml +++ b/.github/workflows/publish-docker-image-for-polygon-edge.yml @@ -3,9 +3,10 @@ # separate terms of service, privacy policy, and support # documentation. -name: Publish Docker image for specific chain branches +name: Polygon Edge Publish Docker image on: + workflow_dispatch: push: branches: - production-polygon-edge-stg diff --git a/.github/workflows/publish-docker-image-for-rsk.yml b/.github/workflows/publish-docker-image-for-rsk.yml index 5cf04ac303ba..d47134f2ae40 100644 --- a/.github/workflows/publish-docker-image-for-rsk.yml +++ b/.github/workflows/publish-docker-image-for-rsk.yml @@ -3,9 +3,10 @@ # separate terms of service, privacy policy, and support # documentation. -name: Publish Docker image for specific chain branches +name: Rootstock Publish Docker image on: + workflow_dispatch: push: branches: - production-rsk-stg diff --git a/.github/workflows/publish-docker-image-for-stability.yml b/.github/workflows/publish-docker-image-for-stability.yml index 0c04104be197..089e5924e3d4 100644 --- a/.github/workflows/publish-docker-image-for-stability.yml +++ b/.github/workflows/publish-docker-image-for-stability.yml @@ -3,9 +3,10 @@ # separate terms of service, privacy policy, and support # documentation. -name: Publish Docker image for specific chain branches +name: Stability Publish Docker image on: + workflow_dispatch: push: branches: - production-stability-stg diff --git a/.github/workflows/publish-docker-image-for-suave.yml b/.github/workflows/publish-docker-image-for-suave.yml index e84a0115f13c..b91bb91e546f 100644 --- a/.github/workflows/publish-docker-image-for-suave.yml +++ b/.github/workflows/publish-docker-image-for-suave.yml @@ -3,9 +3,10 @@ # separate terms of service, privacy policy, and support # documentation. -name: Publish Docker image for specific chain branches +name: SUAVE Publish Docker image on: + workflow_dispatch: push: branches: - production-suave-stg diff --git a/.github/workflows/publish-docker-image-for-xdai.yml b/.github/workflows/publish-docker-image-for-xdai.yml index fca17ff2e083..947a675c57fe 100644 --- a/.github/workflows/publish-docker-image-for-xdai.yml +++ b/.github/workflows/publish-docker-image-for-xdai.yml @@ -3,9 +3,10 @@ # separate terms of service, privacy policy, and support # documentation. -name: Publish Docker image for specific chain branches +name: Gnosis chain Publish Docker image on: + workflow_dispatch: push: branches: - production-xdai-stg diff --git a/.github/workflows/publish-docker-image-for-zkevm.yml b/.github/workflows/publish-docker-image-for-zkevm.yml index a47940869663..32a17480e8e5 100644 --- a/.github/workflows/publish-docker-image-for-zkevm.yml +++ b/.github/workflows/publish-docker-image-for-zkevm.yml @@ -3,9 +3,10 @@ # separate terms of service, privacy policy, and support # documentation. -name: Publish Docker image for specific chain branches +name: Zkevm publish Docker image on: + workflow_dispatch: push: branches: - production-zkevm-stg diff --git a/.github/workflows/publish-docker-image-for-zksync.yml b/.github/workflows/publish-docker-image-for-zksync.yml index 057112de2093..8d02b444c17a 100644 --- a/.github/workflows/publish-docker-image-for-zksync.yml +++ b/.github/workflows/publish-docker-image-for-zksync.yml @@ -3,9 +3,10 @@ # separate terms of service, privacy policy, and support # documentation. -name: Publish Docker image for specific chain branches +name: Zksync publish Docker image on: + workflow_dispatch: push: branches: - production-zksync-stg diff --git a/.github/workflows/publish-docker-image-release-additional.yml b/.github/workflows/publish-docker-image-release-additional.yml deleted file mode 100644 index 9e05afff6f4b..000000000000 --- a/.github/workflows/publish-docker-image-release-additional.yml +++ /dev/null @@ -1,105 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Publish Docker image for some custom chains - -on: - release: - types: [published] - -env: - OTP_VERSION: '25.2.1' - ELIXIR_VERSION: '1.14.5' - -jobs: - push_to_registry: - name: Push Docker image to Docker Hub - runs-on: ubuntu-latest - env: - RELEASE_VERSION: 5.3.2 - steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout - - - name: Build and push Docker image for Rootstock - uses: docker/build-push-action@v5 - with: - context: . - file: ./docker/Dockerfile - push: true - tags: blockscout/blockscout-rsk:latest, blockscout/blockscout-rsk:${{ env.RELEASE_VERSION }} - platforms: | - linux/amd64 - linux/arm64/v8 - build-args: | - CACHE_EXCHANGE_RATES_PERIOD= - API_V1_READ_METHODS_DISABLED=false - DISABLE_WEBAPP=false - API_V1_WRITE_METHODS_DISABLED=false - CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= - ADMIN_PANEL_ENABLED=false - CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= - BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta - RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=rsk - - - name: Build and push Docker image for Polygon Edge - uses: docker/build-push-action@v5 - with: - context: . - file: ./docker/Dockerfile - push: true - tags: blockscout/blockscout-polygon-edge:latest, blockscout/blockscout-polygon-edge:${{ env.RELEASE_VERSION }} - platforms: | - linux/amd64 - linux/arm64/v8 - build-args: | - CACHE_EXCHANGE_RATES_PERIOD= - API_V1_READ_METHODS_DISABLED=false - DISABLE_WEBAPP=false - API_V1_WRITE_METHODS_DISABLED=false - CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= - ADMIN_PANEL_ENABLED=false - CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= - BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta - RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=polygon_edge - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: . - file: ./docker/Dockerfile - push: true - tags: blockscout/blockscout-stability:latest, blockscout/blockscout-stability:${{ env.RELEASE_VERSION }} - platforms: | - linux/amd64 - linux/arm64/v8 - build-args: | - CACHE_EXCHANGE_RATES_PERIOD= - API_V1_READ_METHODS_DISABLED=false - DISABLE_WEBAPP=false - API_V1_WRITE_METHODS_DISABLED=false - CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= - ADMIN_PANEL_ENABLED=false - CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= - BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta - RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=stability \ No newline at end of file diff --git a/.github/workflows/publish-docker-image-release.yml b/.github/workflows/publish-docker-image-release.yml deleted file mode 100644 index fe999a37f76b..000000000000 --- a/.github/workflows/publish-docker-image-release.yml +++ /dev/null @@ -1,165 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Publish Docker image - -on: - release: - types: [published] - -env: - OTP_VERSION: '25.2.1' - ELIXIR_VERSION: '1.14.5' - -jobs: - push_to_registry: - name: Push Docker image to Docker Hub - runs-on: ubuntu-latest - env: - RELEASE_VERSION: 5.3.2 - steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout - - - name: Build & Push Docker image - uses: docker/build-push-action@v5 - with: - context: . - file: ./docker/Dockerfile - push: true - cache-from: type=registry,ref=blockscout/blockscout:buildcache - cache-to: type=registry,ref=blockscout/blockscout:buildcache,mode=max - tags: blockscout/blockscout:latest, blockscout/blockscout:${{ env.RELEASE_VERSION }}-alpha - platforms: | - linux/amd64 - linux/arm64/v8 - build-args: | - CACHE_EXCHANGE_RATES_PERIOD= - API_V1_READ_METHODS_DISABLED=false - DISABLE_WEBAPP=false - API_V1_WRITE_METHODS_DISABLED=false - CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= - ADMIN_PANEL_ENABLED=false - DECODE_NOT_A_CONTRACT_CALLS=false - MIXPANEL_URL= - MIXPANEL_TOKEN= - AMPLITUDE_URL= - AMPLITUDE_API_KEY= - CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= - BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta - RELEASE_VERSION=${{ env.RELEASE_VERSION }} - - - name: Build and push Docker image for zkEVM - uses: docker/build-push-action@v5 - with: - context: . - file: ./docker/Dockerfile - push: true - tags: blockscout/blockscout-zkevm:latest, blockscout/blockscout-zkevm:${{ env.RELEASE_VERSION }} - platforms: | - linux/amd64 - linux/arm64/v8 - build-args: | - CACHE_EXCHANGE_RATES_PERIOD= - API_V1_READ_METHODS_DISABLED=false - DISABLE_WEBAPP=false - API_V1_WRITE_METHODS_DISABLED=false - CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= - ADMIN_PANEL_ENABLED=false - CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= - BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta - RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=polygon_zkevm - - - name: Build and push Docker image for SUAVE - uses: docker/build-push-action@v5 - with: - context: . - file: ./docker/Dockerfile - push: true - tags: blockscout/blockscout-suave:latest, blockscout/blockscout-suave:${{ env.RELEASE_VERSION }} - platforms: | - linux/amd64 - linux/arm64/v8 - build-args: | - CACHE_EXCHANGE_RATES_PERIOD= - API_V1_READ_METHODS_DISABLED=false - DISABLE_WEBAPP=false - API_V1_WRITE_METHODS_DISABLED=false - CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= - ADMIN_PANEL_ENABLED=false - CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= - BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta - RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=suave - - - name: Send release announcement to Slack workflow - id: slack - uses: slackapi/slack-github-action@v1.24.0 - with: - payload: | - { - "release-version": "${{ env.RELEASE_VERSION }}", - "release-link": "https://github.com/blockscout/blockscout/releases/tag/v${{ env.RELEASE_VERSION }}-beta" - } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - - # merge-master-after-release: - # name: Merge 'master' to specific branch after release - # runs-on: ubuntu-latest - # env: - # BRANCHES: | - # production-core-stg - # production-sokol-stg - # production-eth-stg-experimental - # production-eth-goerli-stg - # production-lukso-stg - # production-xdai-stg - # production-polygon-supernets-stg - # production-rsk-stg - # production-immutable-stg - # steps: - # - uses: actions/checkout@v4 - # - name: Set Git config - # run: | - # git config --local user.email "actions@github.com" - # git config --local user.name "Github Actions" - # - name: Merge master back after release - # run: | - # git fetch --unshallow - # touch errors.txt - # for branch in $BRANCHES; - # do - # git reset --merge - # git checkout master - # git fetch origin - # echo $branch - # git ls-remote --exit-code --heads origin $branch || { echo $branch >> errors.txt; continue; } - # echo "Merge 'master' to $branch" - # git checkout $branch - # git pull || { echo $branch >> errors.txt; continue; } - # git merge --no-ff master -m "Auto-merge master back to $branch" || { echo $branch >> errors.txt; continue; } - # git push || { echo $branch >> errors.txt; continue; } - # git checkout master; - # done - # [ -s errors.txt ] && echo "There are problems with merging 'master' to branches:" || echo "Errors file is empty" - # cat errors.txt - # [ ! -s errors.txt ] From a864ad3c08d005bec1b25be02c0cfd3017824e88 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 21 Nov 2023 00:48:18 +0400 Subject: [PATCH 066/607] Prerelease workflows --- .github/workflows/prerelease-main.yml | 67 +++++++++ .github/workflows/release-additional.yml | 106 +++++++++++++++ .github/workflows/release-main.yml | 166 +++++++++++++++++++++++ 3 files changed, 339 insertions(+) create mode 100644 .github/workflows/prerelease-main.yml create mode 100644 .github/workflows/release-additional.yml create mode 100644 .github/workflows/release-main.yml diff --git a/.github/workflows/prerelease-main.yml b/.github/workflows/prerelease-main.yml new file mode 100644 index 000000000000..69cd93909ae3 --- /dev/null +++ b/.github/workflows/prerelease-main.yml @@ -0,0 +1,67 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Pre-release main + +on: + release: + types: [published] + +env: + OTP_VERSION: '25.2.1' + ELIXIR_VERSION: '1.14.5' + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 5.3.2 + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: blockscout/blockscout + + - name: Build & Push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + cache-from: type=registry,ref=blockscout/blockscout:buildcache + cache-to: type=registry,ref=blockscout/blockscout:buildcache,mode=max + tags: blockscout/blockscout:latest, blockscout/blockscout:${{ env.RELEASE_VERSION }}-alpha + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} \ No newline at end of file diff --git a/.github/workflows/release-additional.yml b/.github/workflows/release-additional.yml new file mode 100644 index 000000000000..06617873ad2d --- /dev/null +++ b/.github/workflows/release-additional.yml @@ -0,0 +1,106 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Release additional + +on: + workflow_dispatch: + release: + types: [published] + +env: + OTP_VERSION: '25.2.1' + ELIXIR_VERSION: '1.14.5' + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 5.3.2 + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: blockscout/blockscout + + - name: Build and push Docker image for Rootstock + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-rsk:latest, blockscout/blockscout-rsk:${{ env.RELEASE_VERSION }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=rsk + + - name: Build and push Docker image for Polygon Edge + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-polygon-edge:latest, blockscout/blockscout-polygon-edge:${{ env.RELEASE_VERSION }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=polygon_edge + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-stability:latest, blockscout/blockscout-stability:${{ env.RELEASE_VERSION }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=stability \ No newline at end of file diff --git a/.github/workflows/release-main.yml b/.github/workflows/release-main.yml new file mode 100644 index 000000000000..2cb018aef6c3 --- /dev/null +++ b/.github/workflows/release-main.yml @@ -0,0 +1,166 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Release main + +on: + workflow_dispatch: + release: + types: [published] + +env: + OTP_VERSION: '25.2.1' + ELIXIR_VERSION: '1.14.5' + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 5.3.2 + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: blockscout/blockscout + + - name: Build & Push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + cache-from: type=registry,ref=blockscout/blockscout:buildcache + cache-to: type=registry,ref=blockscout/blockscout:buildcache,mode=max + tags: blockscout/blockscout:latest, blockscout/blockscout:${{ env.RELEASE_VERSION }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + + - name: Build and push Docker image for zkEVM + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zkevm:latest, blockscout/blockscout-zkevm:${{ env.RELEASE_VERSION }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=polygon_zkevm + + - name: Build and push Docker image for SUAVE + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-suave:latest, blockscout/blockscout-suave:${{ env.RELEASE_VERSION }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=suave + + - name: Send release announcement to Slack workflow + id: slack + uses: slackapi/slack-github-action@v1.24.0 + with: + payload: | + { + "release-version": "${{ env.RELEASE_VERSION }}", + "release-link": "https://github.com/blockscout/blockscout/releases/tag/v${{ env.RELEASE_VERSION }}-beta" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + # merge-master-after-release: + # name: Merge 'master' to specific branch after release + # runs-on: ubuntu-latest + # env: + # BRANCHES: | + # production-core-stg + # production-sokol-stg + # production-eth-stg-experimental + # production-eth-goerli-stg + # production-lukso-stg + # production-xdai-stg + # production-polygon-supernets-stg + # production-rsk-stg + # production-immutable-stg + # steps: + # - uses: actions/checkout@v4 + # - name: Set Git config + # run: | + # git config --local user.email "actions@github.com" + # git config --local user.name "Github Actions" + # - name: Merge master back after release + # run: | + # git fetch --unshallow + # touch errors.txt + # for branch in $BRANCHES; + # do + # git reset --merge + # git checkout master + # git fetch origin + # echo $branch + # git ls-remote --exit-code --heads origin $branch || { echo $branch >> errors.txt; continue; } + # echo "Merge 'master' to $branch" + # git checkout $branch + # git pull || { echo $branch >> errors.txt; continue; } + # git merge --no-ff master -m "Auto-merge master back to $branch" || { echo $branch >> errors.txt; continue; } + # git push || { echo $branch >> errors.txt; continue; } + # git checkout master; + # done + # [ -s errors.txt ] && echo "There are problems with merging 'master' to branches:" || echo "Errors file is empty" + # cat errors.txt + # [ ! -s errors.txt ] From f1ee56b2e0508eda1890cd45dbf00b8826dcd724 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 21 Nov 2023 00:57:09 +0400 Subject: [PATCH 067/607] Allow setting number of pre-release --- .github/workflows/prerelease-main.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/prerelease-main.yml b/.github/workflows/prerelease-main.yml index 69cd93909ae3..369b1b1c2b73 100644 --- a/.github/workflows/prerelease-main.yml +++ b/.github/workflows/prerelease-main.yml @@ -1,11 +1,11 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: Pre-release main on: + workflow_dispatch: + inputs: + number: + type: number + required: true release: types: [published] @@ -46,7 +46,7 @@ jobs: push: true cache-from: type=registry,ref=blockscout/blockscout:buildcache cache-to: type=registry,ref=blockscout/blockscout:buildcache,mode=max - tags: blockscout/blockscout:latest, blockscout/blockscout:${{ env.RELEASE_VERSION }}-alpha + tags: blockscout/blockscout:latest, blockscout/blockscout:${{ env.RELEASE_VERSION }}-alpha${{ inputs.number }} platforms: | linux/amd64 linux/arm64/v8 From 0dfda3a9c7e0bdc35dfeb4d29964e9ad591b2594 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 21 Nov 2023 01:01:46 +0400 Subject: [PATCH 068/607] Enhance prerelease, release workflows --- .github/workflows/prerelease-main.yml | 4 +--- .github/workflows/release-additional.yml | 1 - .github/workflows/release-main.yml | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/prerelease-main.yml b/.github/workflows/prerelease-main.yml index 369b1b1c2b73..57890b3ffb9b 100644 --- a/.github/workflows/prerelease-main.yml +++ b/.github/workflows/prerelease-main.yml @@ -6,8 +6,6 @@ on: number: type: number required: true - release: - types: [published] env: OTP_VERSION: '25.2.1' @@ -46,7 +44,7 @@ jobs: push: true cache-from: type=registry,ref=blockscout/blockscout:buildcache cache-to: type=registry,ref=blockscout/blockscout:buildcache,mode=max - tags: blockscout/blockscout:latest, blockscout/blockscout:${{ env.RELEASE_VERSION }}-alpha${{ inputs.number }} + tags: blockscout/blockscout:latest, blockscout/blockscout:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} platforms: | linux/amd64 linux/arm64/v8 diff --git a/.github/workflows/release-additional.yml b/.github/workflows/release-additional.yml index 06617873ad2d..72c287e93730 100644 --- a/.github/workflows/release-additional.yml +++ b/.github/workflows/release-additional.yml @@ -6,7 +6,6 @@ name: Release additional on: - workflow_dispatch: release: types: [published] diff --git a/.github/workflows/release-main.yml b/.github/workflows/release-main.yml index 2cb018aef6c3..60c840a7f70b 100644 --- a/.github/workflows/release-main.yml +++ b/.github/workflows/release-main.yml @@ -6,7 +6,6 @@ name: Release main on: - workflow_dispatch: release: types: [published] From 795b17032705f960070af2881d9dca50f60944bc Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Tue, 21 Nov 2023 12:33:03 +0400 Subject: [PATCH 069/607] Limit TokenBalance fetcher timeout --- CHANGELOG.md | 1 + apps/indexer/lib/indexer/fetcher/token_balance.ex | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a91a5afd53f..54968cb8e23f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ### Fixes +- [#8869](https://github.com/blockscout/blockscout/pull/8869) - Limit TokenBalance fetcher timeout - [#8855](https://github.com/blockscout/blockscout/pull/8855) - All transactions count at top addresses page - [#8836](https://github.com/blockscout/blockscout/pull/8836) - Safe token update - [#8814](https://github.com/blockscout/blockscout/pull/8814) - Improve performance for EOA addresses in `/api/v2/addresses/{address_hash}` diff --git a/apps/indexer/lib/indexer/fetcher/token_balance.ex b/apps/indexer/lib/indexer/fetcher/token_balance.ex index c9507315bc16..29b3bad04312 100644 --- a/apps/indexer/lib/indexer/fetcher/token_balance.ex +++ b/apps/indexer/lib/indexer/fetcher/token_balance.ex @@ -28,6 +28,8 @@ defmodule Indexer.Fetcher.TokenBalance do @default_max_batch_size 100 @default_max_concurrency 10 + @timeout :timer.minutes(10) + @max_retries 3 @spec async_fetch([ @@ -156,7 +158,7 @@ defmodule Indexer.Fetcher.TokenBalance do address_current_token_balances: %{ params: TokenBalances.to_address_current_token_balances(formatted_token_balances_params) }, - timeout: :infinity + timeout: @timeout } case Chain.import(import_params) do From 35f1be02e7595e4ae21b31c2372d339af44aef7e Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Fri, 24 Nov 2023 12:09:12 +0300 Subject: [PATCH 070/607] Revert "Remove /api/account/v1 path" --- CHANGELOG.md | 1 - .../lib/block_scout_web/api_router.ex | 67 +++++- .../api/{v2 => v1}/authenticate_controller.ex | 2 +- .../api/{v2 => v1}/email_controller.ex | 2 +- .../api/{v2 => v1}/fallback_controller.ex | 4 +- .../account/api/{v2 => v1}/tags_controller.ex | 2 +- .../account/api/{v2 => v1}/user_controller.ex | 4 +- .../templates/layout/app.html.eex | 2 +- .../account/api/{v2 => v1}/account_view.ex | 2 +- .../views/account/api/{v2 => v1}/tags_view.ex | 2 +- .../views/account/api/{v2 => v1}/user_view.ex | 4 +- .../api/{v2 => v1}/user_controller_test.exs | 211 +++++++++--------- .../api/v2/smart_contract_controller_test.exs | 6 +- 13 files changed, 184 insertions(+), 125 deletions(-) rename apps/block_scout_web/lib/block_scout_web/controllers/account/api/{v2 => v1}/authenticate_controller.ex (93%) rename apps/block_scout_web/lib/block_scout_web/controllers/account/api/{v2 => v1}/email_controller.ex (97%) rename apps/block_scout_web/lib/block_scout_web/controllers/account/api/{v2 => v1}/fallback_controller.ex (96%) rename apps/block_scout_web/lib/block_scout_web/controllers/account/api/{v2 => v1}/tags_controller.ex (98%) rename apps/block_scout_web/lib/block_scout_web/controllers/account/api/{v2 => v1}/user_controller.ex (99%) rename apps/block_scout_web/lib/block_scout_web/views/account/api/{v2 => v1}/account_view.ex (65%) rename apps/block_scout_web/lib/block_scout_web/views/account/api/{v2 => v1}/tags_view.ex (92%) rename apps/block_scout_web/lib/block_scout_web/views/account/api/{v2 => v1}/user_view.ex (98%) rename apps/block_scout_web/test/block_scout_web/controllers/account/api/{v2 => v1}/user_controller_test.exs (86%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39783a027e66..7bf4c4c7eeb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,6 @@ ### Chore -- [#8843](https://github.com/blockscout/blockscout/pull/8843) - Remove /api/account/v1 path - [#8832](https://github.com/blockscout/blockscout/pull/8832) - Log more details in regards 413 error - [#8807](https://github.com/blockscout/blockscout/pull/8807) - Smart-contract proxy detection refactoring - [#8802](https://github.com/blockscout/blockscout/pull/8802) - Enable API v2 by default diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index a133568f62e5..dee973786cfe 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -46,9 +46,74 @@ defmodule BlockScoutWeb.ApiRouter do plug(RateLimit) end - alias BlockScoutWeb.Account.Api.V2.{AuthenticateController, EmailController, TagsController, UserController} + alias BlockScoutWeb.Account.Api.V1.{AuthenticateController, EmailController, TagsController, UserController} alias BlockScoutWeb.API.V2 + # TODO: Remove /account/v1 paths + scope "/account/v1", as: :account_v1 do + pipe_through(:api) + pipe_through(:account_api) + + get("/authenticate", AuthenticateController, :authenticate_get) + post("/authenticate", AuthenticateController, :authenticate_post) + + get("/get_csrf", UserController, :get_csrf) + + scope "/email" do + get("/resend", EmailController, :resend_email) + end + + scope "/user" do + get("/info", UserController, :info) + + get("/watchlist", UserController, :watchlist_old) + delete("/watchlist/:id", UserController, :delete_watchlist) + post("/watchlist", UserController, :create_watchlist) + put("/watchlist/:id", UserController, :update_watchlist) + + get("/api_keys", UserController, :api_keys) + delete("/api_keys/:api_key", UserController, :delete_api_key) + post("/api_keys", UserController, :create_api_key) + put("/api_keys/:api_key", UserController, :update_api_key) + + get("/custom_abis", UserController, :custom_abis) + delete("/custom_abis/:id", UserController, :delete_custom_abi) + post("/custom_abis", UserController, :create_custom_abi) + put("/custom_abis/:id", UserController, :update_custom_abi) + + get("/public_tags", UserController, :public_tags_requests) + delete("/public_tags/:id", UserController, :delete_public_tags_request) + post("/public_tags", UserController, :create_public_tags_request) + put("/public_tags/:id", UserController, :update_public_tags_request) + + scope "/tags" do + get("/address/", UserController, :tags_address_old) + get("/address/:id", UserController, :tags_address) + delete("/address/:id", UserController, :delete_tag_address) + post("/address/", UserController, :create_tag_address) + put("/address/:id", UserController, :update_tag_address) + + get("/transaction/", UserController, :tags_transaction_old) + get("/transaction/:id", UserController, :tags_transaction) + delete("/transaction/:id", UserController, :delete_tag_transaction) + post("/transaction/", UserController, :create_tag_transaction) + put("/transaction/:id", UserController, :update_tag_transaction) + end + end + end + + # TODO: Remove /account/v1 paths + scope "/account/v1" do + pipe_through(:api) + pipe_through(:account_api) + + scope "/tags" do + get("/address/:address_hash", TagsController, :tags_address) + + get("/transaction/:transaction_hash", TagsController, :tags_transaction) + end + end + scope "/account/v2", as: :account_v2 do pipe_through(:api) pipe_through(:account_api) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/authenticate_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/authenticate_controller.ex similarity index 93% rename from apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/authenticate_controller.ex rename to apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/authenticate_controller.ex index 2358fdc2cef6..0c16034d0217 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/authenticate_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/authenticate_controller.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.Account.Api.V2.AuthenticateController do +defmodule BlockScoutWeb.Account.Api.V1.AuthenticateController do use BlockScoutWeb, :controller import BlockScoutWeb.Account.AuthController, only: [current_user: 1] diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/email_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/email_controller.ex similarity index 97% rename from apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/email_controller.ex rename to apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/email_controller.ex index f93315779bb6..9e79c2d33645 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/email_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/email_controller.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.Account.Api.V2.EmailController do +defmodule BlockScoutWeb.Account.Api.V1.EmailController do use BlockScoutWeb, :controller alias BlockScoutWeb.Models.UserFromAuth diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/fallback_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/fallback_controller.ex similarity index 96% rename from apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/fallback_controller.ex rename to apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/fallback_controller.ex index a4821b23e41f..14d44fffb3c0 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/fallback_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/fallback_controller.ex @@ -1,7 +1,7 @@ -defmodule BlockScoutWeb.Account.Api.V2.FallbackController do +defmodule BlockScoutWeb.Account.Api.V1.FallbackController do use Phoenix.Controller - alias BlockScoutWeb.Account.Api.V2.UserView + alias BlockScoutWeb.Account.Api.V1.UserView alias Ecto.Changeset def call(conn, {:identity, _}) do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/tags_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/tags_controller.ex similarity index 98% rename from apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/tags_controller.ex rename to apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/tags_controller.ex index 18765be70c45..3549024f905c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/tags_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/tags_controller.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.Account.Api.V2.TagsController do +defmodule BlockScoutWeb.Account.Api.V1.TagsController do use BlockScoutWeb, :controller import BlockScoutWeb.Account.AuthController, only: [current_user: 1] diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/user_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex similarity index 99% rename from apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/user_controller.ex rename to apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex index 12e9e0ab3056..724378cc69d6 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/user_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.Account.Api.V2.UserController do +defmodule BlockScoutWeb.Account.Api.V1.UserController do use BlockScoutWeb, :controller import BlockScoutWeb.Account.AuthController, only: [current_user: 1] @@ -21,7 +21,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do alias Explorer.{Chain, Market, PagingOptions, Repo} alias Plug.CSRFProtection - action_fallback(BlockScoutWeb.Account.Api.V2.FallbackController) + action_fallback(BlockScoutWeb.Account.Api.V1.FallbackController) @ok_message "OK" @token_balances_amount 150 diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex index 2481992ea493..d07b77ca9e14 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex @@ -94,7 +94,7 @@ <% session = Explorer.Account.enabled?() && Plug.Conn.get_session(@conn, :current_user) %> <%= render BlockScoutWeb.LayoutView, "_topnav.html", current_user: session, conn: @conn %> <%= if session && !session[:email_verified] do %> - + <% else %> <% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/account_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/account_view.ex similarity index 65% rename from apps/block_scout_web/lib/block_scout_web/views/account/api/v2/account_view.ex rename to apps/block_scout_web/lib/block_scout_web/views/account/api/v1/account_view.ex index 632b3109501f..0e3a65e61653 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/account_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/account_view.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.Account.Api.V2.AccountView do +defmodule BlockScoutWeb.Account.Api.V1.AccountView do def render("message.json", %{message: message}) do %{ "message" => message diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/tags_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/tags_view.ex similarity index 92% rename from apps/block_scout_web/lib/block_scout_web/views/account/api/v2/tags_view.ex rename to apps/block_scout_web/lib/block_scout_web/views/account/api/v1/tags_view.ex index 80670000d2e6..d97e35f9145b 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/tags_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/tags_view.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.Account.Api.V2.TagsView do +defmodule BlockScoutWeb.Account.Api.V1.TagsView do def render("address_tags.json", %{tags_map: tags_map}) do tags_map end diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/user_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex similarity index 98% rename from apps/block_scout_web/lib/block_scout_web/views/account/api/v2/user_view.ex rename to apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex index 96974a909218..26c8d7c8295b 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/user_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex @@ -1,5 +1,5 @@ -defmodule BlockScoutWeb.Account.Api.V2.UserView do - alias BlockScoutWeb.Account.Api.V2.AccountView +defmodule BlockScoutWeb.Account.Api.V1.UserView do + alias BlockScoutWeb.Account.Api.V1.AccountView alias BlockScoutWeb.API.V2.Helper alias Ecto.Changeset alias Explorer.Chain diff --git a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v2/user_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs similarity index 86% rename from apps/block_scout_web/test/block_scout_web/controllers/account/api/v2/user_controller_test.exs rename to apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs index a4bc4dffe4a9..f32b5727e727 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v2/user_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do +defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do use BlockScoutWeb.ConnCase alias Explorer.Account.{ @@ -19,11 +19,11 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do {:ok, user: user, conn: Plug.Test.init_test_session(conn, current_user: user)} end - describe "Test account/api/v2/user" do + describe "Test account/api/v1/user" do test "get user info", %{conn: conn, user: user} do result_conn = conn - |> get("/api/account/v2/user/info") + |> get("/api/account/v1/user/info") |> doc(description: "Get info about user") assert json_response(result_conn, 200) == %{ @@ -37,7 +37,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do test "post private address tag", %{conn: conn} do tag_address_response = conn - |> post("/api/account/v2/user/tags/address", %{ + |> post("/api/account/v1/user/tags/address", %{ "address_hash" => "0x3e9ac8f16c92bc4f093357933b5befbf1e16987b", "name" => "MyName" }) @@ -45,7 +45,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do |> json_response(200) conn - |> get("/api/account/v2/tags/address/0x3e9ac8f16c92bc4f093357933b5befbf1e16987b") + |> get("/api/account/v1/tags/address/0x3e9ac8f16c92bc4f093357933b5befbf1e16987b") |> doc(description: "Get tags for address") |> json_response(200) @@ -69,11 +69,11 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do end assert conn - |> post("/api/account/v2/user/tags/address", build(:tag_address)) + |> post("/api/account/v1/user/tags/address", build(:tag_address)) |> json_response(200) assert conn - |> post("/api/account/v2/user/tags/address", build(:tag_address)) + |> post("/api/account/v1/user/tags/address", build(:tag_address)) |> json_response(422) Application.put_env(:explorer, Explorer.Account, old_env) @@ -103,12 +103,12 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do tag_address_response = conn - |> post("/api/account/v2/user/tags/address", address_tag) + |> post("/api/account/v1/user/tags/address", address_tag) |> json_response(200) _response = conn - |> get("/api/account/v2/user/tags/address") + |> get("/api/account/v1/user/tags/address") |> json_response(200) == [tag_address_response] assert tag_address_response["address_hash"] == address_tag["address_hash"] @@ -119,7 +119,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do new_tag_address_response = conn - |> put("/api/account/v2/user/tags/address/#{tag_address_response["id"]}", new_address_tag) + |> put("/api/account/v1/user/tags/address/#{tag_address_response["id"]}", new_address_tag) |> doc(description: "Edit private address tag") |> json_response(200) @@ -137,7 +137,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do Enum.map(zipped, fn {addr, name} -> id = (conn - |> post("/api/account/v2/user/tags/address", %{ + |> post("/api/account/v1/user/tags/address", %{ "address_hash" => addr, "name" => name }) @@ -164,7 +164,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert Enum.all?(created, fn {addr, map_tag, _} -> response = conn - |> get("/api/account/v2/tags/address/#{addr}") + |> get("/api/account/v1/tags/address/#{addr}") |> json_response(200) response["personal_tags"] == [map_tag] @@ -172,10 +172,9 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do response = conn - |> get("/api/account/v2/user/tags/address") + |> get("/api/account/v1/user/tags/address") |> doc(description: "Get private addresses tags") |> json_response(200) - |> Map.get("items") assert Enum.all?(created, fn {_, _, map} -> map in response end) end @@ -189,7 +188,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do Enum.map(zipped, fn {addr, name} -> id = (conn - |> post("/api/account/v2/user/tags/address", %{ + |> post("/api/account/v1/user/tags/address", %{ "address_hash" => addr, "name" => name }) @@ -216,7 +215,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert Enum.all?(created, fn {addr, map_tag, _} -> response = conn - |> get("/api/account/v2/tags/address/#{addr}") + |> get("/api/account/v1/tags/address/#{addr}") |> json_response(200) response["personal_tags"] == [map_tag] @@ -224,31 +223,32 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do response = conn - |> get("/api/account/v2/user/tags/address") + |> get("/api/account/v1/user/tags/address") |> json_response(200) - |> Map.get("items") assert Enum.all?(created, fn {_, _, map} -> map in response end) {_, _, %{"id" => id}} = Enum.at(created, 0) assert conn - |> delete("/api/account/v2/user/tags/address/#{id}") + |> delete("/api/account/v1/user/tags/address/#{id}") |> doc("Delete private address tag") |> json_response(200) == %{"message" => "OK"} assert Enum.all?(Enum.drop(created, 1), fn {_, _, %{"id" => id}} -> conn - |> delete("/api/account/v2/user/tags/address/#{id}") + |> delete("/api/account/v1/user/tags/address/#{id}") |> json_response(200) == %{"message" => "OK"} end) - assert conn |> get("/api/account/v2/user/tags/address") |> json_response(200) |> Map.get("items") == [] + assert conn + |> get("/api/account/v1/user/tags/address") + |> json_response(200) == [] assert Enum.all?(created, fn {addr, _, _} -> response = conn - |> get("/api/account/v2/tags/address/#{addr}") + |> get("/api/account/v1/tags/address/#{addr}") |> json_response(200) response["personal_tags"] == [] @@ -260,7 +260,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do tx_hash = to_string(insert(:transaction).hash) assert conn - |> post("/api/account/v2/user/tags/transaction", %{ + |> post("/api/account/v1/user/tags/transaction", %{ "transaction_hash" => tx_hash_non_existing, "name" => "MyName" }) @@ -269,7 +269,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do tag_transaction_response = conn - |> post("/api/account/v2/user/tags/transaction", %{ + |> post("/api/account/v1/user/tags/transaction", %{ "transaction_hash" => tx_hash, "name" => "MyName" }) @@ -277,7 +277,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do |> json_response(200) conn - |> get("/api/account/v2/tags/transaction/#{tx_hash}") + |> get("/api/account/v1/tags/transaction/#{tx_hash}") |> doc(description: "Get tags for transaction") |> json_response(200) @@ -301,11 +301,11 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do end assert conn - |> post("/api/account/v2/user/tags/transaction", build(:tag_transaction)) + |> post("/api/account/v1/user/tags/transaction", build(:tag_transaction)) |> json_response(200) assert conn - |> post("/api/account/v2/user/tags/transaction", build(:tag_transaction)) + |> post("/api/account/v1/user/tags/transaction", build(:tag_transaction)) |> json_response(422) Application.put_env(:explorer, Explorer.Account, old_env) @@ -335,12 +335,12 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do tag_response = conn - |> post("/api/account/v2/user/tags/transaction", tx_tag) + |> post("/api/account/v1/user/tags/transaction", tx_tag) |> json_response(200) _response = conn - |> get("/api/account/v2/user/tags/transaction") + |> get("/api/account/v1/user/tags/transaction") |> json_response(200) == [tag_response] assert tag_response["address_hash"] == tx_tag["address_hash"] @@ -351,7 +351,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do new_tag_response = conn - |> put("/api/account/v2/user/tags/transaction/#{tag_response["id"]}", new_tx_tag) + |> put("/api/account/v1/user/tags/transaction/#{tag_response["id"]}", new_tx_tag) |> doc(description: "Edit private transaction tag") |> json_response(200) @@ -369,7 +369,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do Enum.map(zipped, fn {tx_hash, name} -> id = (conn - |> post("/api/account/v2/user/tags/transaction", %{ + |> post("/api/account/v1/user/tags/transaction", %{ "transaction_hash" => tx_hash, "name" => name }) @@ -381,7 +381,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert Enum.all?(created, fn {tx_hash, map_tag, _} -> response = conn - |> get("/api/account/v2/tags/transaction/#{tx_hash}") + |> get("/api/account/v1/tags/transaction/#{tx_hash}") |> json_response(200) response["personal_tx_tag"] == map_tag @@ -389,10 +389,9 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do response = conn - |> get("/api/account/v2/user/tags/transaction") + |> get("/api/account/v1/user/tags/transaction") |> doc(description: "Get private transactions tags") |> json_response(200) - |> Map.get("items") assert Enum.all?(created, fn {_, _, map} -> map in response end) end @@ -406,7 +405,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do Enum.map(zipped, fn {tx_hash, name} -> id = (conn - |> post("/api/account/v2/user/tags/transaction", %{ + |> post("/api/account/v1/user/tags/transaction", %{ "transaction_hash" => tx_hash, "name" => name }) @@ -418,15 +417,15 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert Enum.all?(created, fn {tx_hash, map_tag, _} -> response = conn - |> get("/api/account/v2/tags/transaction/#{tx_hash}") + |> get("/api/account/v1/tags/transaction/#{tx_hash}") |> json_response(200) response["personal_tx_tag"] == map_tag end) - %{"items" => response, "next_page_params" => nil} = + response = conn - |> get("/api/account/v2/user/tags/transaction") + |> get("/api/account/v1/user/tags/transaction") |> json_response(200) assert Enum.all?(created, fn {_, _, map} -> map in response end) @@ -434,24 +433,24 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do {_, _, %{"id" => id}} = Enum.at(created, 0) assert conn - |> delete("/api/account/v2/user/tags/transaction/#{id}") + |> delete("/api/account/v1/user/tags/transaction/#{id}") |> doc("Delete private transaction tag") |> json_response(200) == %{"message" => "OK"} assert Enum.all?(Enum.drop(created, 1), fn {_, _, %{"id" => id}} -> conn - |> delete("/api/account/v2/user/tags/transaction/#{id}") + |> delete("/api/account/v1/user/tags/transaction/#{id}") |> json_response(200) == %{"message" => "OK"} end) assert conn - |> get("/api/account/v2/user/tags/transaction") - |> json_response(200) == %{"items" => [], "next_page_params" => nil} + |> get("/api/account/v1/user/tags/transaction") + |> json_response(200) == [] assert Enum.all?(created, fn {addr, _, _} -> response = conn - |> get("/api/account/v2/tags/transaction/#{addr}") + |> get("/api/account/v1/tags/transaction/#{addr}") |> json_response(200) response["personal_tx_tag"] == nil @@ -464,7 +463,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do post_watchlist_address_response = conn |> post( - "/api/account/v2/user/watchlist", + "/api/account/v1/user/watchlist", watchlist_address_map ) |> doc(description: "Add address to watch list") @@ -475,8 +474,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert post_watchlist_address_response["notification_methods"] == watchlist_address_map["notification_methods"] assert post_watchlist_address_response["address_hash"] == watchlist_address_map["address_hash"] - get_watchlist_address_response = - conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") |> Enum.at(0) + get_watchlist_address_response = conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(0) assert get_watchlist_address_response["notification_settings"] == watchlist_address_map["notification_settings"] assert get_watchlist_address_response["name"] == watchlist_address_map["name"] @@ -489,21 +487,20 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do post_watchlist_address_response_1 = conn |> post( - "/api/account/v2/user/watchlist", + "/api/account/v1/user/watchlist", watchlist_address_map_1 ) |> json_response(200) get_watchlist_address_response_1_0 = conn - |> get("/api/account/v2/user/watchlist") + |> get("/api/account/v1/user/watchlist") |> doc(description: "Get addresses from watchlists") |> json_response(200) - |> Map.get("items") |> Enum.at(1) get_watchlist_address_response_1_1 = - conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") |> Enum.at(0) + conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(0) assert get_watchlist_address_response_1_0 == get_watchlist_address_response @@ -534,11 +531,11 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do end assert conn - |> post("/api/account/v2/user/watchlist", build(:watchlist_address)) + |> post("/api/account/v1/user/watchlist", build(:watchlist_address)) |> json_response(200) assert conn - |> post("/api/account/v2/user/watchlist", build(:watchlist_address)) + |> post("/api/account/v1/user/watchlist", build(:watchlist_address)) |> json_response(422) Application.put_env(:explorer, Explorer.Account, old_env) @@ -569,7 +566,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do post_watchlist_address_response = conn |> post( - "/api/account/v2/user/watchlist", + "/api/account/v1/user/watchlist", watchlist_address_map ) |> json_response(200) @@ -579,8 +576,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert post_watchlist_address_response["notification_methods"] == watchlist_address_map["notification_methods"] assert post_watchlist_address_response["address_hash"] == watchlist_address_map["address_hash"] - get_watchlist_address_response = - conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") |> Enum.at(0) + get_watchlist_address_response = conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(0) assert get_watchlist_address_response["notification_settings"] == watchlist_address_map["notification_settings"] assert get_watchlist_address_response["name"] == watchlist_address_map["name"] @@ -593,16 +589,16 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do post_watchlist_address_response_1 = conn |> post( - "/api/account/v2/user/watchlist", + "/api/account/v1/user/watchlist", watchlist_address_map_1 ) |> json_response(200) get_watchlist_address_response_1_0 = - conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") |> Enum.at(1) + conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(1) get_watchlist_address_response_1_1 = - conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") |> Enum.at(0) + conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(0) assert get_watchlist_address_response_1_0 == get_watchlist_address_response @@ -618,15 +614,15 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert get_watchlist_address_response_1_1["id"] == post_watchlist_address_response_1["id"] assert conn - |> delete("/api/account/v2/user/watchlist/#{get_watchlist_address_response_1_1["id"]}") + |> delete("/api/account/v1/user/watchlist/#{get_watchlist_address_response_1_1["id"]}") |> doc(description: "Delete address from watchlist by id") |> json_response(200) == %{"message" => "OK"} assert conn - |> delete("/api/account/v2/user/watchlist/#{get_watchlist_address_response_1_0["id"]}") + |> delete("/api/account/v1/user/watchlist/#{get_watchlist_address_response_1_0["id"]}") |> json_response(200) == %{"message" => "OK"} - assert conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") == [] + assert conn |> get("/api/account/v1/user/watchlist") |> json_response(200) == [] end test "put watchlist address", %{conn: conn} do @@ -635,7 +631,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do post_watchlist_address_response = conn |> post( - "/api/account/v2/user/watchlist", + "/api/account/v1/user/watchlist", watchlist_address_map ) |> json_response(200) @@ -645,8 +641,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert post_watchlist_address_response["notification_methods"] == watchlist_address_map["notification_methods"] assert post_watchlist_address_response["address_hash"] == watchlist_address_map["address_hash"] - get_watchlist_address_response = - conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") |> Enum.at(0) + get_watchlist_address_response = conn |> get("/api/account/v1/user/watchlist") |> json_response(200) |> Enum.at(0) assert get_watchlist_address_response["notification_settings"] == watchlist_address_map["notification_settings"] assert get_watchlist_address_response["name"] == watchlist_address_map["name"] @@ -659,7 +654,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do put_watchlist_address_response = conn |> put( - "/api/account/v2/user/watchlist/#{post_watchlist_address_response["id"]}", + "/api/account/v1/user/watchlist/#{post_watchlist_address_response["id"]}", new_watchlist_address_map ) |> doc(description: "Edit watchlist address") @@ -680,7 +675,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do post_watchlist_address_response = conn |> post( - "/api/account/v2/user/watchlist", + "/api/account/v1/user/watchlist", watchlist_address_map ) |> json_response(200) @@ -692,7 +687,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert conn |> post( - "/api/account/v2/user/watchlist", + "/api/account/v1/user/watchlist", watchlist_address_map ) |> doc(description: "Example of error on creating watchlist address") @@ -703,14 +698,14 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do post_watchlist_address_response_1 = conn |> post( - "/api/account/v2/user/watchlist", + "/api/account/v1/user/watchlist", new_watchlist_address_map ) |> json_response(200) assert conn |> put( - "/api/account/v2/user/watchlist/#{post_watchlist_address_response_1["id"]}", + "/api/account/v1/user/watchlist/#{post_watchlist_address_response_1["id"]}", watchlist_address_map ) |> doc(description: "Example of error on editing watchlist address") @@ -722,7 +717,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do conn |> post( - "/api/account/v2/user/watchlist", + "/api/account/v1/user/watchlist", watchlist_address_map ) |> json_response(200) @@ -731,7 +726,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do conn |> post( - "/api/account/v2/user/watchlist", + "/api/account/v1/user/watchlist", watchlist_address_map_1 ) |> json_response(200) @@ -766,7 +761,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do |> Enum.sort(fn x1, x2 -> Decimal.compare(x1, x2) in [:gt, :eq] end) |> Enum.take(150) - [wa2, wa1] = conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") + [wa2, wa1] = conn |> get("/api/account/v1/user/watchlist") |> json_response(200) assert wa1["tokens_fiat_value"] |> Decimal.new() |> Decimal.round(13) == values |> Enum.reduce(Decimal.new(0), fn x, acc -> Decimal.add(x, acc) end) |> Decimal.round(13) @@ -786,7 +781,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do conn |> post( - "/api/account/v2/user/watchlist", + "/api/account/v1/user/watchlist", watchlist_address_map ) |> json_response(200) @@ -813,7 +808,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do token_contract_address_hash: token.contract_address_hash ) - [wa1] = conn |> get("/api/account/v2/user/watchlist") |> json_response(200) |> Map.get("items") + [wa1] = conn |> get("/api/account/v1/user/watchlist") |> json_response(200) assert wa1["tokens_fiat_value"] |> Decimal.new() |> Decimal.round(13) == values |> Enum.reduce(Decimal.new(0), fn x, acc -> Decimal.add(x, acc) end) |> Decimal.round(13) @@ -826,7 +821,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do post_api_key_response = conn |> post( - "/api/account/v2/user/api_keys", + "/api/account/v1/user/api_keys", %{"name" => "test"} ) |> doc(description: "Add api key") @@ -840,7 +835,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do Enum.each(0..2, fn _x -> conn |> post( - "/api/account/v2/user/api_keys", + "/api/account/v1/user/api_keys", %{"name" => "test"} ) |> json_response(200) @@ -848,14 +843,14 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert conn |> post( - "/api/account/v2/user/api_keys", + "/api/account/v1/user/api_keys", %{"name" => "test"} ) |> doc(description: "Example of error on creating api key") |> json_response(422) == %{"errors" => %{"name" => ["Max 3 keys per account"]}} assert conn - |> get("/api/account/v2/user/api_keys") + |> get("/api/account/v1/user/api_keys") |> doc(description: "Get api keys list") |> json_response(200) |> Enum.count() == 3 @@ -865,7 +860,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do post_api_key_response = conn |> post( - "/api/account/v2/user/api_keys", + "/api/account/v1/user/api_keys", %{"name" => "test"} ) |> json_response(200) @@ -876,7 +871,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do put_api_key_response = conn |> put( - "/api/account/v2/user/api_keys/#{post_api_key_response["api_key"]}", + "/api/account/v1/user/api_keys/#{post_api_key_response["api_key"]}", %{"name" => "test_1"} ) |> doc(description: "Edit api key") @@ -886,7 +881,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert put_api_key_response["name"] == "test_1" assert conn - |> get("/api/account/v2/user/api_keys") + |> get("/api/account/v1/user/api_keys") |> json_response(200) == [put_api_key_response] end @@ -894,7 +889,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do post_api_key_response = conn |> post( - "/api/account/v2/user/api_keys", + "/api/account/v1/user/api_keys", %{"name" => "test"} ) |> json_response(200) @@ -903,17 +898,17 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert post_api_key_response["api_key"] assert conn - |> get("/api/account/v2/user/api_keys") + |> get("/api/account/v1/user/api_keys") |> json_response(200) |> Enum.count() == 1 assert conn - |> delete("/api/account/v2/user/api_keys/#{post_api_key_response["api_key"]}") + |> delete("/api/account/v1/user/api_keys/#{post_api_key_response["api_key"]}") |> doc(description: "Delete api key") |> json_response(200) == %{"message" => "OK"} assert conn - |> get("/api/account/v2/user/api_keys") + |> get("/api/account/v1/user/api_keys") |> json_response(200) == [] end @@ -923,7 +918,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do post_custom_abi_response = conn |> post( - "/api/account/v2/user/custom_abis", + "/api/account/v1/user/custom_abis", custom_abi ) |> doc(description: "Add custom abi") @@ -939,7 +934,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do Enum.each(0..14, fn _x -> conn |> post( - "/api/account/v2/user/custom_abis", + "/api/account/v1/user/custom_abis", build(:custom_abi) ) |> json_response(200) @@ -947,14 +942,14 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert conn |> post( - "/api/account/v2/user/custom_abis", + "/api/account/v1/user/custom_abis", build(:custom_abi) ) |> doc(description: "Example of error on creating custom abi") |> json_response(422) == %{"errors" => %{"name" => ["Max 15 ABIs per account"]}} assert conn - |> get("/api/account/v2/user/custom_abis") + |> get("/api/account/v1/user/custom_abis") |> doc(description: "Get custom abis list") |> json_response(200) |> Enum.count() == 15 @@ -966,7 +961,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do post_custom_abi_response = conn |> post( - "/api/account/v2/user/custom_abis", + "/api/account/v1/user/custom_abis", custom_abi ) |> json_response(200) @@ -981,7 +976,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do put_custom_abi_response = conn |> put( - "/api/account/v2/user/custom_abis/#{post_custom_abi_response["id"]}", + "/api/account/v1/user/custom_abis/#{post_custom_abi_response["id"]}", custom_abi_1 ) |> doc(description: "Edit custom abi") @@ -993,7 +988,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert put_custom_abi_response["abi"] == custom_abi_1["abi"] assert conn - |> get("/api/account/v2/user/custom_abis") + |> get("/api/account/v1/user/custom_abis") |> json_response(200) == [put_custom_abi_response] end @@ -1003,7 +998,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do post_custom_abi_response = conn |> post( - "/api/account/v2/user/custom_abis", + "/api/account/v1/user/custom_abis", custom_abi ) |> json_response(200) @@ -1012,17 +1007,17 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert post_custom_abi_response["id"] assert conn - |> get("/api/account/v2/user/custom_abis") + |> get("/api/account/v1/user/custom_abis") |> json_response(200) |> Enum.count() == 1 assert conn - |> delete("/api/account/v2/user/custom_abis/#{post_custom_abi_response["id"]}") + |> delete("/api/account/v1/user/custom_abis/#{post_custom_abi_response["id"]}") |> doc(description: "Delete custom abi") |> json_response(200) == %{"message" => "OK"} assert conn - |> get("/api/account/v2/user/custom_abis") + |> get("/api/account/v1/user/custom_abis") |> json_response(200) == [] end end @@ -1034,7 +1029,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do post_public_tags_request_response = conn |> post( - "/api/account/v2/user/public_tags", + "/api/account/v1/user/public_tags", public_tags_request ) |> doc(description: "Submit request to add a public tag") @@ -1057,7 +1052,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do post_public_tags_request_response = conn |> post( - "/api/account/v2/user/public_tags", + "/api/account/v1/user/public_tags", public_tags_request ) |> json_response(200) @@ -1073,7 +1068,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert post_public_tags_request_response["id"] assert conn - |> get("/api/account/v2/user/public_tags") + |> get("/api/account/v1/user/public_tags") |> json_response(200) |> Enum.map(&convert_date/1) == [post_public_tags_request_response] @@ -1089,7 +1084,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do response = conn |> post( - "/api/account/v2/user/public_tags", + "/api/account/v1/user/public_tags", request ) |> json_response(200) @@ -1109,7 +1104,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do |> Enum.reverse() assert conn - |> get("/api/account/v2/user/public_tags") + |> get("/api/account/v1/user/public_tags") |> doc(description: "Get list of requests to add a public tag") |> json_response(200) |> Enum.map(&convert_date/1) == final_list @@ -1117,18 +1112,18 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do %{"id" => id} = Enum.at(final_list, 0) assert conn - |> delete("/api/account/v2/user/public_tags/#{id}", %{"remove_reason" => "reason"}) + |> delete("/api/account/v1/user/public_tags/#{id}", %{"remove_reason" => "reason"}) |> doc(description: "Delete public tags request") |> json_response(200) == %{"message" => "OK"} Enum.each(Enum.drop(final_list, 1), fn request -> assert conn - |> delete("/api/account/v2/user/public_tags/#{request["id"]}", %{"remove_reason" => "reason"}) + |> delete("/api/account/v1/user/public_tags/#{request["id"]}", %{"remove_reason" => "reason"}) |> json_response(200) == %{"message" => "OK"} end) assert conn - |> get("/api/account/v2/user/public_tags") + |> get("/api/account/v1/user/public_tags") |> json_response(200) == [] end @@ -1138,7 +1133,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do post_public_tags_request_response = conn |> post( - "/api/account/v2/user/public_tags", + "/api/account/v1/user/public_tags", public_tags_request ) |> json_response(200) @@ -1154,7 +1149,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert post_public_tags_request_response["id"] assert conn - |> get("/api/account/v2/user/public_tags") + |> get("/api/account/v1/user/public_tags") |> json_response(200) |> Enum.map(&convert_date/1) == [post_public_tags_request_response] @@ -1165,7 +1160,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do put_public_tags_request_response = conn |> put( - "/api/account/v2/user/public_tags/#{post_public_tags_request_response["id"]}", + "/api/account/v1/user/public_tags/#{post_public_tags_request_response["id"]}", new_public_tags_request ) |> doc(description: "Edit request to add a public tag") @@ -1182,7 +1177,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert put_public_tags_request_response["id"] == post_public_tags_request_response["id"] assert conn - |> get("/api/account/v2/user/public_tags") + |> get("/api/account/v1/user/public_tags") |> json_response(200) |> Enum.map(&convert_date/1) == [put_public_tags_request_response] diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index 64b620d34a69..ad6eabf1f113 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -1815,7 +1815,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do conn |> post( - "/api/account/v2/user/custom_abis", + "/api/account/v1/user/custom_abis", custom_abi ) @@ -1867,7 +1867,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do conn |> post( - "/api/account/v2/user/custom_abis", + "/api/account/v1/user/custom_abis", custom_abi ) @@ -1934,7 +1934,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do conn |> post( - "/api/account/v2/user/custom_abis", + "/api/account/v1/user/custom_abis", custom_abi ) From dfb076e5bfa04ede84099008fe75c06a72ba5445 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 27 Nov 2023 12:58:25 +0300 Subject: [PATCH 071/607] Update Readme in docker-compose --- .github/workflows/config.yml | 3 +++ docker-compose/README.md | 2 ++ 2 files changed, 5 insertions(+) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 744afbbd3a58..6329e65a2869 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -20,6 +20,9 @@ on: - production-zkevm-stg - production-zksync-stg - staging-l2 + paths-ignore: + - 'CHANGELOG.md' + - '**/README.md' pull_request: branches: - master diff --git a/docker-compose/README.md b/docker-compose/README.md index ded1ce9ad0b4..c212e1cde760 100644 --- a/docker-compose/README.md +++ b/docker-compose/README.md @@ -37,6 +37,8 @@ and 4 containers for microservices (written in Rust): The repo contains built-in configs for different JSON RPC clients without need to build the image. +**Note**: in all below examples, you can use `docker compose` instead of `docker-compose`, if compose v2 plugin is installed in Docker. + - Erigon: `docker-compose -f docker-compose-no-build-erigon.yml up -d` - Geth (suitable for Reth as well): `docker-compose -f docker-compose-no-build-geth.yml up -d` - Geth Clique: `docker-compose -f docker-compose-no-build-geth-clique-consensus.yml up -d` From 349916f7625b663a2a364932f79243c60de435db Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 27 Nov 2023 13:07:07 +0300 Subject: [PATCH 072/607] Update paths-ignore for CI --- .github/workflows/config.yml | 2 ++ .github/workflows/publish-docker-image-every-push.yml | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 6329e65a2869..31e89c223fa4 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -23,6 +23,8 @@ on: paths-ignore: - 'CHANGELOG.md' - '**/README.md' + - 'docker/*' + - 'docker-compose/*' pull_request: branches: - master diff --git a/.github/workflows/publish-docker-image-every-push.yml b/.github/workflows/publish-docker-image-every-push.yml index 98fc8a4de88b..a5ebce6f57f6 100644 --- a/.github/workflows/publish-docker-image-every-push.yml +++ b/.github/workflows/publish-docker-image-every-push.yml @@ -4,6 +4,10 @@ on: push: branches: - master + paths-ignore: + - 'CHANGELOG.md' + - '**/README.md' + - 'docker-compose/*' env: OTP_VERSION: '25.2.1' ELIXIR_VERSION: '1.14.5' From 9383b144a9a426f632ea8eceea51e7d0775de690 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 18:30:41 +0000 Subject: [PATCH 073/607] Bump httpoison from 2.2.0 to 2.2.1 Bumps [httpoison](https://github.com/edgurgel/httpoison) from 2.2.0 to 2.2.1. - [Release notes](https://github.com/edgurgel/httpoison/releases) - [Commits](https://github.com/edgurgel/httpoison/compare/v2.2.0...v2.2.1) --- updated-dependencies: - dependency-name: httpoison dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index b44a533985d3..cff3ceedc4a0 100644 --- a/mix.lock +++ b/mix.lock @@ -68,7 +68,7 @@ "hammer": {:hex, :hammer, "6.1.0", "f263e3c3e9946bd410ea0336b2abe0cb6260af4afb3a221e1027540706e76c55", [:make, :mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b47e415a562a6d072392deabcd58090d8a41182cf9044cdd6b0d0faaaf68ba57"}, "hammer_backend_redis": {:hex, :hammer_backend_redis, "6.1.2", "eb296bb4924928e24135308b2afc189201fd09411c870c6bbadea444a49b2f2c", [:mix], [{:hammer, "~> 6.0", [hex: :hammer, repo: "hexpm", optional: false]}, {:redix, "~> 1.1", [hex: :redix, repo: "hexpm", optional: false]}], "hexpm", "217ea066278910543a5e9b577d5bf2425419446b94fe76bdd9f255f39feec9fa"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, - "httpoison": {:hex, :httpoison, "2.2.0", "839298929243b872b3f53c3693fa369ac3dbe03102cefd0a126194738bf4bb0e", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a49a9337c2b671464948a00cff6a882d271c1c8e3d25a6ca14d0532cbd23f65a"}, + "httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, From 9c3e2cf852d658c281100238fc12afe6e13312d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 18:31:01 +0000 Subject: [PATCH 074/607] Bump prometheus from 4.10.0 to 4.11.0 Bumps [prometheus](https://github.com/deadtrickster/prometheus.erl) from 4.10.0 to 4.11.0. - [Release notes](https://github.com/deadtrickster/prometheus.erl/releases) - [Commits](https://github.com/deadtrickster/prometheus.erl/compare/v4.10.0...v4.11.0) --- updated-dependencies: - dependency-name: prometheus dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index b44a533985d3..576a01f63da1 100644 --- a/mix.lock +++ b/mix.lock @@ -110,7 +110,7 @@ "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, "postgrex": {:hex, :postgrex, "0.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"}, - "prometheus": {:hex, :prometheus, "4.10.0", "792adbf0130ff61b5fa8826f013772af24b6e57b984445c8d602c8a0355704a1", [:mix, :rebar3], [{:quantile_estimator, "~> 0.2.1", [hex: :quantile_estimator, repo: "hexpm", optional: false]}], "hexpm", "2a99bb6dce85e238c7236fde6b0064f9834dc420ddbd962aac4ea2a3c3d59384"}, + "prometheus": {:hex, :prometheus, "4.11.0", "b95f8de8530f541bd95951e18e355a840003672e5eda4788c5fa6183406ba29a", [:mix, :rebar3], [{:quantile_estimator, "~> 0.2.1", [hex: :quantile_estimator, repo: "hexpm", optional: false]}], "hexpm", "719862351aabf4df7079b05dc085d2bbcbe3ac0ac3009e956671b1d5ab88247d"}, "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"}, "prometheus_ex": {:git, "https://github.com/lanodan/prometheus.ex", "31f7fbe4b71b79ba27efc2a5085746c4011ceb8f", [branch: "fix/elixir-1.14"]}, "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "c4d1404ac4e9d3d963da601db2a7d8ea31194f0017057fabf0cfb9bf5a6c8c75"}, From 14015de9b0fcb1e8617b510ead729d6e76221c78 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 28 Nov 2023 11:01:21 +0300 Subject: [PATCH 075/607] Staging build workflow --- ...publish-docker-image-staging-on-demand.yml | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 .github/workflows/publish-docker-image-staging-on-demand.yml diff --git a/.github/workflows/publish-docker-image-staging-on-demand.yml b/.github/workflows/publish-docker-image-staging-on-demand.yml new file mode 100644 index 000000000000..ad6b96a3240b --- /dev/null +++ b/.github/workflows/publish-docker-image-staging-on-demand.yml @@ -0,0 +1,75 @@ +name: Publish Docker image to staging on demand + +on: + workflow_dispatch: + push: + branches: + - staging + paths-ignore: + - 'CHANGELOG.md' + - '**/README.md' + - 'docker-compose/*' +env: + OTP_VERSION: '25.2.1' + ELIXIR_VERSION: '1.14.5' + RELEASE_VERSION: 5.3.2 + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + outputs: + release-version: ${{ steps.output-step.outputs.release-version }} + short-sha: ${{ steps.output-step.outputs.short-sha }} + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: blockscout/blockscout-staging + + - name: Add SHORT_SHA env property with commit short sha + run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + + - name: Add outputs + run: | + echo "::set-output name=release-version::${{ env.NEXT_RELEASE_VERSION }}" + echo "::set-output name=short-sha::${{ env.SHORT_SHA }}" + id: output-step + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + cache-from: type=registry,ref=blockscout/blockscout:buildcache + cache-to: type=registry,ref=blockscout/blockscout:buildcache,mode=max + tags: blockscout/blockscout-staging:latest, blockscout/blockscout-staging:${{ env.RELEASE_VERSION }}.commit.${{ env.SHORT_SHA }} + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} From bd2d21b2cb01be5560762188a4bc9ac37d89d6e2 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Mon, 27 Nov 2023 18:19:28 +0600 Subject: [PATCH 076/607] Fix average block time --- CHANGELOG.md | 2 + .../explorer/counters/average_block_time.ex | 4 +- .../counters/average_block_time_test.exs | 50 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bf4c4c7eeb4..1e92e99ffb05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Fixes +- [#8891](https://github.com/blockscout/blockscout/pull/8891) - Fix average block time + ### Chore ## 5.3.2-beta diff --git a/apps/explorer/lib/explorer/counters/average_block_time.ex b/apps/explorer/lib/explorer/counters/average_block_time.ex index 9075415854a0..9c7338dc9e97 100644 --- a/apps/explorer/lib/explorer/counters/average_block_time.ex +++ b/apps/explorer/lib/explorer/counters/average_block_time.ex @@ -84,7 +84,7 @@ defmodule Explorer.Counters.AverageBlockTime do timestamps = timestamps_row - |> Enum.sort_by(fn {_, timestamp} -> timestamp end, &>=/2) + |> Enum.sort_by(fn {_, timestamp} -> timestamp end, &Timex.after?/2) |> Enum.map(fn {number, timestamp} -> {number, DateTime.to_unix(timestamp, :millisecond)} end) @@ -125,7 +125,7 @@ defmodule Explorer.Counters.AverageBlockTime do defp compose_durations(durations, block_number, last_block_number, last_timestamp, timestamp) do block_numbers_range = last_block_number - block_number - if block_numbers_range == 0 do + if block_numbers_range <= 0 do {durations, block_number, timestamp} else duration = (last_timestamp - timestamp) / block_numbers_range diff --git a/apps/explorer/test/explorer/counters/average_block_time_test.exs b/apps/explorer/test/explorer/counters/average_block_time_test.exs index d9e0cfb35cd4..3472276d1959 100644 --- a/apps/explorer/test/explorer/counters/average_block_time_test.exs +++ b/apps/explorer/test/explorer/counters/average_block_time_test.exs @@ -129,5 +129,55 @@ defmodule Explorer.Counters.AverageBlockTimeTest do assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT3S") end + + test "timestamps are compared correctly" do + block_number = 99_999_999 + + first_timestamp = ~U[2023-08-23 19:04:59.000000Z] + pseudo_after_timestamp = ~U[2022-08-23 19:05:59.000000Z] + + insert(:block, number: block_number, consensus: true, timestamp: pseudo_after_timestamp) + insert(:block, number: block_number + 1, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 3)) + insert(:block, number: block_number + 2, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 6)) + + Enum.each(1..100, fn i -> + insert(:block, + number: block_number + i + 2, + consensus: true, + timestamp: Timex.shift(first_timestamp, seconds: -(101 - i) - 9) + ) + end) + + AverageBlockTime.refresh() + + %{timestamps: timestamps} = :sys.get_state(AverageBlockTime) + + assert Enum.sort_by(timestamps, fn {_bn, ts} -> ts end, &>=/2) == timestamps + end + + test "average time are calculated correctly for blocks that are not in chronological order" do + block_number = 99_999_999 + + first_timestamp = Timex.now() + + insert(:block, number: block_number, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 3)) + insert(:block, number: block_number + 1, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 6)) + insert(:block, number: block_number + 2, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 9)) + insert(:block, number: block_number + 3, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: -69)) + insert(:block, number: block_number + 4, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: -66)) + insert(:block, number: block_number + 5, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: -63)) + + Enum.each(1..100, fn i -> + insert(:block, + number: block_number + i + 5, + consensus: true, + timestamp: Timex.shift(first_timestamp, seconds: -(101 - i) - 9) + ) + end) + + AverageBlockTime.refresh() + + assert Timex.Duration.to_milliseconds(AverageBlockTime.average_block_time()) == 3000 + end end end From d26e6ef4d0c9469f6918ca7999c5438afd5ce74b Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 28 Nov 2023 18:59:24 +0300 Subject: [PATCH 077/607] Update CHANGELOG --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e92e99ffb05..efac2523b600 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,6 @@ ### Fixes -- [#8891](https://github.com/blockscout/blockscout/pull/8891) - Fix average block time - ### Chore ## 5.3.2-beta @@ -25,6 +23,7 @@ ### Fixes +- [#8891](https://github.com/blockscout/blockscout/pull/8891) - Fix average block time - [#8869](https://github.com/blockscout/blockscout/pull/8869) - Limit TokenBalance fetcher timeout - [#8855](https://github.com/blockscout/blockscout/pull/8855) - All transactions count at top addresses page - [#8836](https://github.com/blockscout/blockscout/pull/8836) - Safe token update @@ -67,6 +66,7 @@ - [#8824](https://github.com/blockscout/blockscout/pull/8824) - Bump httpoison from 2.1.0 to 2.2.0 - [#8828](https://github.com/blockscout/blockscout/pull/8828) - Bump @babel/preset-env from 7.23.2 to 7.23.3 in /apps/block_scout_web/assets - [#8825](https://github.com/blockscout/blockscout/pull/8825) - Bump solc from 0.8.22 to 0.8.23 in /apps/explorer +
## 5.3.1-beta From 6b705e2d045b7ca2bffdd41affe216b214770966 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Nov 2023 08:39:39 +0000 Subject: [PATCH 078/607] Bump absinthe from 1.7.5 to 1.7.6 Bumps [absinthe](https://github.com/absinthe-graphql/absinthe) from 1.7.5 to 1.7.6. - [Release notes](https://github.com/absinthe-graphql/absinthe/releases) - [Changelog](https://github.com/absinthe-graphql/absinthe/blob/main/CHANGELOG.md) - [Commits](https://github.com/absinthe-graphql/absinthe/compare/v1.7.5...v1.7.6) --- updated-dependencies: - dependency-name: absinthe dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index aae1da0f115e..87e788363b13 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "absinthe": {:hex, :absinthe, "1.7.5", "a15054f05738e766f7cc7fd352887dfd5e61cec371fb4741cca37c3359ff74ac", [:mix], [{:dataloader, "~> 1.0.0 or ~> 2.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.2.2 or ~> 1.3.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2.1", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "22a9a38adca26294ad0ee91226168f5d215b401efd770b8a1b8fd9c9b21ec316"}, + "absinthe": {:hex, :absinthe, "1.7.6", "0b897365f98d068cfcb4533c0200a8e58825a4aeeae6ec33633ebed6de11773b", [:mix], [{:dataloader, "~> 1.0.0 or ~> 2.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2.1", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7626951ca5eec627da960615b51009f3a774765406ff02722b1d818f17e5778"}, "absinthe_phoenix": {:hex, :absinthe_phoenix, "2.0.2", "e607b438db900049b9b3760f8ecd0591017a46122fffed7057bf6989020992b5", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:absinthe_plug, "~> 1.5", [hex: :absinthe_plug, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "d36918925c380dc7d2ed7d039c9a3b4182ec36723f7417a68745ade5aab22f8d"}, "absinthe_plug": {:git, "https://github.com/blockscout/absinthe_plug.git", "c435d43f316769e1beee1dbe500b623124c96785", [tag: "1.5.3"]}, "absinthe_relay": {:hex, :absinthe_relay, "1.5.2", "cfb8aed70f4e4c7718d3f1c212332d2ea728f17c7fc0f68f1e461f0f5f0c4b9a", [:mix], [{:absinthe, "~> 1.5.0 or ~> 1.6.0 or ~> 1.7.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "0587ee913afa31512e1457a5064ee88427f8fe7bcfbeeecd41c71d9cff0b62b6"}, @@ -92,7 +92,7 @@ "msgpax": {:hex, :msgpax, "2.4.0", "4647575c87cb0c43b93266438242c21f71f196cafa268f45f91498541148c15d", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "ca933891b0e7075701a17507c61642bf6e0407bb244040d5d0a58597a06369d2"}, "nimble_csv": {:hex, :nimble_csv, "1.2.0", "4e26385d260c61eba9d4412c71cea34421f296d5353f914afe3f2e71cce97722", [:mix], [], "hexpm", "d0628117fcc2148178b034044c55359b26966c6eaa8e2ce15777be3bbc91b12a"}, "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "number": {:hex, :number, "1.0.4", "3e6e6032a3c1d4c3760e77a42c580a57a15545dd993af380809da30fe51a032c", [:mix], [{:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "16f7516584ef2be812af4f33f2eaf3f9b9f6ed8892f45853eb93113f83721e42"}, "numbers": {:hex, :numbers, "5.2.4", "f123d5bb7f6acc366f8f445e10a32bd403c8469bdbce8ce049e1f0972b607080", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "eeccf5c61d5f4922198395bf87a465b6f980b8b862dd22d28198c5e6fab38582"}, "oauth2": {:hex, :oauth2, "2.0.1", "70729503e05378697b958919bb2d65b002ba6b28c8112328063648a9348aaa3f", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "c64e20d4d105bcdbcbe03170fb530d0eddc3a3e6b135a87528a22c8aecf74c52"}, From 52b58c9ea0ea174bdd977731c8a8b336b70f4d86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Nov 2023 09:53:18 +0000 Subject: [PATCH 079/607] Bump ex_json_schema from 0.10.1 to 0.10.2 Bumps [ex_json_schema](https://github.com/jonasschmidt/ex_json_schema) from 0.10.1 to 0.10.2. - [Commits](https://github.com/jonasschmidt/ex_json_schema/compare/v0.10.1...v0.10.2) --- updated-dependencies: - dependency-name: ex_json_schema dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 0772598d44f1..343093379b85 100644 --- a/mix.lock +++ b/mix.lock @@ -48,7 +48,7 @@ "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.32.3", "b631ff94c982ec518e46bf4736000a30a33d6b58facc085d5f240305f512ad4a", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7b626ff1e59a0ec9c3c5db5ce9ca91a6995e2ab56426b71f3cbf67181ea225f5"}, "ex_cldr_units": {:hex, :ex_cldr_units, "3.16.4", "fee054e9ebed40ef05cbb405cb0c7e7c9fda201f8f03ec0d1e54e879af413246", [:mix], [{:cldr_utils, "~> 2.24", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.31", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7c15c6357dd555a5bc6c72fdeb243e4706a04065753dbd2f40150f062ca996c7"}, "ex_doc": {:hex, :ex_doc, "0.30.9", "d691453495c47434c0f2052b08dd91cc32bc4e1a218f86884563448ee2502dd2", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "d7aaaf21e95dc5cddabf89063327e96867d00013963eadf2c6ad135506a8bc10"}, - "ex_json_schema": {:hex, :ex_json_schema, "0.10.1", "e03b746b6675a750c0bb1a5cc919f61353f7ab8450977e11ceede20e6180c560", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "66a64e60dadad89914d92f89c7e7906c57de75a8b79ac2480d0d53e1b8096fb0"}, + "ex_json_schema": {:hex, :ex_json_schema, "0.10.2", "7c4b8c1481fdeb1741e2ce66223976edfb9bccebc8014f6aec35d4efe964fb71", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "37f43be60f8407659d4d0155a7e45e7f406dab1f827051d3d35858a709baf6a6"}, "ex_keccak": {:hex, :ex_keccak, "0.7.3", "33298f97159f6b0acd28f6e96ce5ea975a0f4a19f85fe615b4f4579b88b24d06", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6.1", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "4c5e6d9d5f77b64ab48769a0166a9814180d40ced68ed74ce60a5174ab55b3fc"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, "ex_rlp": {:hex, :ex_rlp, "0.6.0", "985391d2356a7cb8712a4a9a2deb93f19f2fbca0323f5c1203fcaf64d077e31e", [:mix], [], "hexpm", "7135db93b861d9e76821039b60b00a6a22d2c4e751bf8c444bffe7a042f1abaf"}, From ee68029d0e3b9c8bdf5bc7755663277e3b9fb134 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 30 Nov 2023 16:26:45 +0300 Subject: [PATCH 080/607] Set client_connection_check_interval for main Postgres DB in docker-compose setup --- CHANGELOG.md | 2 ++ docker-compose/services/docker-compose-db.yml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efac2523b600..d5b21eaad9bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Chore +- [#8911](https://github.com/blockscout/blockscout/pull/8911) - Set client_connection_check_interval for main Postgres DB in docker-compose setup + ## 5.3.2-beta ### Features diff --git a/docker-compose/services/docker-compose-db.yml b/docker-compose/services/docker-compose-db.yml index 131d90bb931c..b7d036e966b2 100644 --- a/docker-compose/services/docker-compose-db.yml +++ b/docker-compose/services/docker-compose-db.yml @@ -19,7 +19,7 @@ services: user: 2000:2000 restart: always container_name: 'db' - command: postgres -c 'max_connections=200' + command: postgres -c 'max_connections=200' -c 'client_connection_check_interval=60000' environment: POSTGRES_DB: 'blockscout' POSTGRES_USER: 'blockscout' From 8130ac550d8a14c0bf8cedde979cdc10f0b76bb3 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 30 Nov 2023 18:36:35 +0300 Subject: [PATCH 081/607] smart-contract: delete embeds_many relation on replace --- CHANGELOG.md | 2 ++ apps/explorer/lib/explorer/chain/smart_contract.ex | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efac2523b600..50ef1d5579e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Fixes +- [#8915](https://github.com/blockscout/blockscout/pull/8915) - smart-contract: delete embeds_many relation on replace + ### Chore ## 5.3.2-beta diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index edf9dda5441e..9e9e198212c2 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -278,7 +278,7 @@ defmodule Explorer.Chain.SmartContract do field(:constructor_arguments, :string) field(:evm_version, :string) field(:optimization_runs, :integer) - embeds_many(:external_libraries, ExternalLibrary) + embeds_many(:external_libraries, ExternalLibrary, on_replace: :delete) field(:abi, {:array, :map}) field(:verified_via_sourcify, :boolean) field(:partially_verified, :boolean) From f4f449df57ddbda1df3969d7a1392de239719bc1 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 1 Dec 2023 15:33:26 +0300 Subject: [PATCH 082/607] Update bug_report.yml --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1888fe929049..677dd9604aa1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -24,8 +24,8 @@ body: description: How the application has been deployed. options: - Docker-compose + - Helm charts (k8s) - Manual from the source code - - Helm charts - Docker validations: required: true From c0ca7081d8cf10e0630822eebaf1f8dde6e58411 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Fri, 1 Dec 2023 22:14:08 +0600 Subject: [PATCH 083/607] Delete invalid current token balances in OnDemand fetcher --- CHANGELOG.md | 2 ++ .../lib/indexer/fetcher/token_balance_on_demand.ex | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efac2523b600..f19588dcffd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current +- [#8924](https://github.com/blockscout/blockscout/pull/8924) - Delete invalid current token balances in OnDemand fetcher + ### Features ### Fixes diff --git a/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex b/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex index d1dfcf6d3364..cd090f19fe36 100644 --- a/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex +++ b/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex @@ -6,7 +6,7 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do use Indexer.Fetcher - alias Explorer.Chain + alias Explorer.{Chain, Repo} alias Explorer.Chain.Address.CurrentTokenBalance alias Explorer.Chain.Cache.BlockNumber alias Explorer.Chain.Events.Publisher @@ -52,6 +52,7 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do stale_current_token_balances = address_hash |> Chain.fetch_last_token_balances_include_unfetched() + |> delete_invalid_balances() |> Enum.filter(fn current_token_balance -> current_token_balance.block_number < stale_balance_window end) if Enum.count(stale_current_token_balances) > 0 do @@ -63,6 +64,12 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do :ok end + defp delete_invalid_balances(current_token_balances) do + {invalid_balances, valid_balances} = Enum.split_with(current_token_balances, &is_nil(&1.token_type)) + Enum.each(invalid_balances, &Repo.delete/1) + valid_balances + end + defp fetch_and_update(block_number, address_hash, stale_current_token_balances) do %{erc_1155: erc_1155_ctbs, other: other_ctbs, tokens: tokens} = stale_current_token_balances From afac872dccba13cffed2ec410c74c321d27c026b Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 30 Nov 2023 23:12:47 +0300 Subject: [PATCH 084/607] Proxy detection hotfix in API v2 --- CHANGELOG.md | 1 + .../block_scout_web/views/api/v2/address_view.ex | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50ef1d5579e8..f8a58ba5f22f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Fixes +- [#8917](https://github.com/blockscout/blockscout/pull/8917) - Proxy detection hotfix in API v2 - [#8915](https://github.com/blockscout/blockscout/pull/8915) - smart-contract: delete embeds_many relation on replace ### Chore diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex index fec26f5d2325..bbfe0c077062 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex @@ -80,11 +80,20 @@ defmodule BlockScoutWeb.API.V2.AddressView do def prepare_address(address, conn \\ nil) do base_info = Helper.address_with_info(conn, address, address.hash, true) - is_proxy = AddressView.smart_contract_is_proxy?(address, @api_true) + + {:ok, address_with_smart_contract} = + Chain.hash_to_address( + address.hash, + [necessity_by_association: %{:smart_contract => :optional}], + false + ) + + is_proxy = AddressView.smart_contract_is_proxy?(address_with_smart_contract, @api_true) {implementation_address, implementation_name} = with true <- is_proxy, - {address, name} <- SmartContract.get_implementation_address_hash(address.smart_contract, @api_true), + {address, name} <- + SmartContract.get_implementation_address_hash(address_with_smart_contract.smart_contract, @api_true), false <- is_nil(address), {:ok, address_hash} <- Chain.string_to_address_hash(address), checksummed_address <- Address.checksum(address_hash) do @@ -118,7 +127,8 @@ defmodule BlockScoutWeb.API.V2.AddressView do "has_methods_read" => AddressView.smart_contract_with_read_only_functions?(address), "has_methods_write" => AddressView.smart_contract_with_write_functions?(address), "has_methods_read_proxy" => is_proxy, - "has_methods_write_proxy" => AddressView.smart_contract_with_write_functions?(address) && is_proxy, + "has_methods_write_proxy" => + AddressView.smart_contract_with_write_functions?(address_with_smart_contract) && is_proxy, "has_decompiled_code" => AddressView.has_decompiled_code?(address), "has_validated_blocks" => Counters.check_if_validated_blocks_at_address(address.hash, @api_true), "has_logs" => Counters.check_if_logs_at_address(address.hash, @api_true), From b1fc22f43c067d8dd810f3cee408e4d340da0485 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 4 Dec 2023 12:16:36 +0300 Subject: [PATCH 085/607] Regression test added --- .../views/api/v2/address_view_test.exs | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 apps/block_scout_web/test/block_scout_web/views/api/v2/address_view_test.exs diff --git a/apps/block_scout_web/test/block_scout_web/views/api/v2/address_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/api/v2/address_view_test.exs new file mode 100644 index 000000000000..7177316bc125 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/api/v2/address_view_test.exs @@ -0,0 +1,76 @@ +defmodule BlockScoutWeb.API.V2.AddressViewTest do + use BlockScoutWeb.ConnCase, async: true + + import Mox + + alias BlockScoutWeb.API.V2.AddressView + alias Explorer.Repo + + test "for a proxy contract has_methods_read_proxy is true" do + implementation_address = insert(:contract_address) + proxy_address = insert(:contract_address) |> Repo.preload([:token]) + + _proxy_smart_contract = + insert(:smart_contract, + address_hash: proxy_address.hash, + contract_code_md5: "123", + implementation_address_hash: implementation_address.hash + ) + + get_eip1967_implementation_zero_addresses() + + assert AddressView.prepare_address(proxy_address)["has_methods_read_proxy"] == true + end + + def get_eip1967_implementation_zero_addresses do + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + end +end From e8b0c463f415d3ecc3af3f1171595f3b890fa7c5 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 23 Nov 2023 19:17:43 +0400 Subject: [PATCH 086/607] Change order of proxy contracts patterns detection: existing popular EIPs to the top of the list --- CHANGELOG.md | 1 + .../api/v2/smart_contract_controller_test.exs | 93 ++------ .../smart_contract_controller_test.exs | 69 ++++++ .../explorer/chain/smart_contract/proxy.ex | 69 +++++- .../chain/smart_contract/proxy/eip_1967.ex | 16 +- .../chain/smart_contract/proxy_test.exs | 117 ++++++++- .../explorer/chain/smart_contract_test.exs | 224 ++++++------------ 7 files changed, 350 insertions(+), 239 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c9a1fbdad34..6daf40ffd839 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - [#8917](https://github.com/blockscout/blockscout/pull/8917) - Proxy detection hotfix in API v2 - [#8915](https://github.com/blockscout/blockscout/pull/8915) - smart-contract: delete embeds_many relation on replace +- [#8882](https://github.com/blockscout/blockscout/pull/8882) - Change order of proxy contracts patterns detection: existing popular EIPs to the top of the list ### Chore diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index ad6eabf1f113..d429160cdab9 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -2026,18 +2026,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do target_contract = insert(:smart_contract, abi: abi) - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} - end) + mock_logic_storage_pointer_request(target_contract.address_hash) expect( EthereumJSONRPC.Mox, @@ -2136,18 +2125,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do target_contract = insert(:smart_contract, abi: abi) - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} - end) + mock_logic_storage_pointer_request(target_contract.address_hash) expect( EthereumJSONRPC.Mox, @@ -2221,18 +2199,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do target_contract = insert(:smart_contract, abi: abi) - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} - end) + mock_logic_storage_pointer_request(target_contract.address_hash) expect( EthereumJSONRPC.Mox, @@ -2290,18 +2257,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do target_contract = insert(:smart_contract, abi: abi) - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} - end) + mock_logic_storage_pointer_request(target_contract.address_hash) expect( EthereumJSONRPC.Mox, @@ -2358,18 +2314,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do target_contract = insert(:smart_contract, abi: abi) - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} - end) + mock_logic_storage_pointer_request(target_contract.address_hash) expect( EthereumJSONRPC.Mox, @@ -2450,18 +2395,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do target_contract = insert(:smart_contract, abi: abi) - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} - end) + mock_logic_storage_pointer_request(target_contract.address_hash) contract = insert(:smart_contract) @@ -2579,4 +2513,19 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "solidity" end end + + defp mock_logic_storage_pointer_request(address_hash) do + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x000000000000000000000000#{address_hash |> to_string() |> String.replace("0x", "")}"} + end) + end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs index aec5f0d1d327..8d2889876964 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs @@ -86,6 +86,8 @@ defmodule BlockScoutWeb.SmartContractControllerTest do contract_code_md5: "123" ) + get_eip1967_implementation_zero_addresses() + path = smart_contract_path(BlockScoutWeb.Endpoint, :index, hash: token_contract_address.hash, @@ -304,4 +306,71 @@ defmodule BlockScoutWeb.SmartContractControllerTest do {:ok, "0x000000000000000000000000cebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"} end) end + + defp mock_empty_logic_storage_pointer_request do + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + end + + defp mock_empty_beacon_storage_pointer_request(mox) do + expect(mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + end + + defp mock_empty_eip_1822_storage_pointer_request(mox) do + expect(mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + end + + defp mock_empty_oz_storage_pointer_request(mox) do + expect(mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + end + + def get_eip1967_implementation_zero_addresses do + mock_empty_logic_storage_pointer_request() + |> mock_empty_beacon_storage_pointer_request() + |> mock_empty_oz_storage_pointer_request() + |> mock_empty_eip_1822_storage_pointer_request() + end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex index 7c086516059a..6bae3ae51f47 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -13,7 +13,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do string_to_address_hash: 1 ] - import Explorer.Chain.SmartContract, only: [is_burn_signature_or_nil: 1] + import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0, is_burn_signature_or_nil: 1] # supported signatures: # 5c60da1b = keccak256(implementation()) @@ -114,7 +114,8 @@ defmodule Explorer.Chain.SmartContract.Proxy do def gnosis_safe_contract?(abi) when is_nil(abi), do: false @doc """ - Checks if the input of the smart-contract follows master-copy proxy pattern + Checks if the input of the smart-contract follows master-copy (or Safe) proxy pattern before + fetching its implementation from 0x0 storage pointer """ @spec master_copy_pattern?(map()) :: any() def master_copy_pattern?(method) do @@ -155,6 +156,66 @@ defmodule Explorer.Chain.SmartContract.Proxy do end defp get_implementation_address_hash_string(proxy_address_hash, proxy_abi) do + get_implementation_address_hash_string_eip1967( + proxy_address_hash, + proxy_abi + ) + end + + @doc """ + Returns EIP-1967 implementation address or tries next proxy pattern + """ + @spec get_implementation_address_hash_string_eip1967(Hash.Address.t(), any()) :: String.t() | nil + def get_implementation_address_hash_string_eip1967(proxy_address_hash, proxy_abi) do + get_implementation_address_hash_string_by_module( + EIP1967, + :get_implementation_address_hash_string_eip1167, + [ + proxy_address_hash, + proxy_abi + ] + ) + end + + @doc """ + Returns EIP-1167 implementation address or tries next proxy pattern + """ + @spec get_implementation_address_hash_string_eip1167(Hash.Address.t(), any()) :: String.t() | nil + def get_implementation_address_hash_string_eip1167(proxy_address_hash, proxy_abi) do + get_implementation_address_hash_string_by_module( + EIP1167, + :get_implementation_address_hash_string_eip1822, + [ + proxy_address_hash, + proxy_abi + ] + ) + end + + @doc """ + Returns EIP-1822 implementation address or tries next proxy pattern + """ + @spec get_implementation_address_hash_string_eip1822(Hash.Address.t(), any()) :: String.t() | nil + def get_implementation_address_hash_string_eip1822(proxy_address_hash, proxy_abi) do + get_implementation_address_hash_string_by_module(EIP1822, [proxy_address_hash, proxy_abi]) + end + + defp get_implementation_address_hash_string_by_module( + module, + next_func \\ :fallback_proxy_detection, + [proxy_address_hash, _proxy_abi] = args + ) do + implementation_address_hash_string = module.get_implementation_address_hash_string(proxy_address_hash) + + if !is_nil(implementation_address_hash_string) && implementation_address_hash_string !== burn_address_hash_string() do + implementation_address_hash_string + else + apply(__MODULE__, next_func, args) + end + end + + @spec fallback_proxy_detection(Hash.Address.t(), any()) :: String.t() | nil + def fallback_proxy_detection(proxy_address_hash, proxy_abi) do implementation_method_abi = get_naive_implementation_abi(proxy_abi, "implementation") get_implementation_method_abi = get_naive_implementation_abi(proxy_abi, "getImplementation") @@ -177,9 +238,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do EIP930.get_implementation_address_hash_string(@get_address_signature, proxy_address_hash, proxy_abi) true -> - EIP1967.get_implementation_address_hash_string(proxy_address_hash) || - EIP1167.get_implementation_address_hash_string(proxy_address_hash) || - EIP1822.get_implementation_address_hash_string(proxy_address_hash) + nil end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex index aea3fa39be04..ca426ef59f22 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex @@ -27,18 +27,24 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do def get_implementation_address_hash_string(proxy_address_hash) do json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) - implementation_address_hash_string = + eip1967_implementation_address_hash_string = Proxy.get_implementation_from_storage( proxy_address_hash, @storage_slot_logic_contract_address, json_rpc_named_arguments ) || - fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) || + fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) + + implementation_address_hash_string = + if eip1967_implementation_address_hash_string do + eip1967_implementation_address_hash_string + else Proxy.get_implementation_from_storage( proxy_address_hash, @storage_slot_openzeppelin_contract_address, json_rpc_named_arguments ) + end Proxy.abi_decode_address_output(implementation_address_hash_string) end @@ -70,17 +76,17 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do nil {:ok, beacon_contract_address} -> - case beacon_contract_address + case @implementation_signature |> Proxy.abi_decode_address_output() |> Basic.get_implementation_address_hash_string( - @implementation_signature, + beacon_contract_address, implementation_method_abi ) do <> -> implementation_address _ -> - beacon_contract_address + nil end _ -> diff --git a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs index d636e8967cd9..410b9d498c65 100644 --- a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs @@ -129,8 +129,7 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do } ] - # EIP-1967 + EIP-1822 - defp request_zero_implementations do + defp request_EIP1967_zero_implementations do EthereumJSONRPC.Mox |> expect(:json_rpc, fn %{ id: 0, @@ -168,6 +167,11 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) + end + + # EIP-1967 + EIP-1822 + defp request_zero_implementations do + request_EIP1967_zero_implementations() |> expect(:json_rpc, fn %{ id: 0, method: "eth_getStorageAt", @@ -206,6 +210,8 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do smart_contract = insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") + request_zero_implementations() + assert Proxy.combine_proxy_implementation_abi(smart_contract) == @proxy_abi end @@ -226,6 +232,8 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do implementation_contract_address_hash_string = Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + request_zero_implementations() + expect( EthereumJSONRPC.Mox, :json_rpc, @@ -276,6 +284,8 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do smart_contract = insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") + request_zero_implementations() + assert Proxy.get_implementation_abi_from_proxy(smart_contract, []) == [] end @@ -296,6 +306,8 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do implementation_contract_address_hash_string = Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + request_zero_implementations() + expect( EthereumJSONRPC.Mox, :json_rpc, @@ -316,7 +328,7 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do assert implementation_abi == @implementation_abi end - test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern" do + test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern (logic contract)" do proxy_contract_address = insert(:contract_address) smart_contract = @@ -355,4 +367,103 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do assert implementation_abi == @implementation_abi end end + + @beacon_abi [ + %{ + "type" => "function", + "stateMutability" => "view", + "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}], + "name" => "implementation", + "inputs" => [] + } + ] + test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern (beacon contract)" do + proxy_contract_address = insert(:contract_address) + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") + + beacon_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: beacon_contract_address.hash, + abi: @beacon_abi, + contract_code_md5: "123" + ) + + beacon_contract_address_hash_string = Base.encode16(beacon_contract_address.hash.bytes, case: :lower) + + implementation_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: implementation_contract_address.hash, + abi: @implementation_abi, + contract_code_md5: "123" + ) + + implementation_contract_address_hash_string = + Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + + EthereumJSONRPC.Mox + |> expect( + :json_rpc, + fn %{ + id: _id, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end + ) + |> expect( + :json_rpc, + fn %{ + id: _id, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x000000000000000000000000" <> beacon_contract_address_hash_string} + end + ) + |> expect( + :json_rpc, + fn [ + %{ + id: _id, + method: "eth_call", + params: [ + %{data: "0x5c60da1b", to: "0x000000000000000000000000" <> beacon_contract_address_hash_string}, + "latest" + ] + } + ], + _options -> + { + :ok, + [ + %{ + id: _id, + jsonrpc: "2.0", + result: "0x000000000000000000000000" <> implementation_contract_address_hash_string + } + ] + } + end + ) + + implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, []) + verify!(EthereumJSONRPC.Mox) + + assert implementation_abi == @implementation_abi + end end diff --git a/apps/explorer/test/explorer/chain/smart_contract_test.exs b/apps/explorer/test/explorer/chain/smart_contract_test.exs index 631cffe3724d..030601f64634 100644 --- a/apps/explorer/test/explorer/chain/smart_contract_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract_test.exs @@ -75,30 +75,8 @@ defmodule Explorer.Chain.SmartContractTest do string_implementation_address_hash = to_string(implementation_smart_contract.address_hash) - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) + mock_empty_logic_storage_pointer_request() + |> mock_empty_beacon_storage_pointer_request() |> expect(:json_rpc, fn %{ id: 0, method: "eth_getStorageAt", @@ -296,82 +274,15 @@ defmodule Explorer.Chain.SmartContractTest do end def get_eip1967_implementation_zero_addresses do - EthereumJSONRPC.Mox - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) + mock_empty_logic_storage_pointer_request() + |> mock_empty_beacon_storage_pointer_request() + |> mock_empty_oz_storage_pointer_request() + |> mock_empty_eip_1822_storage_pointer_request() end def get_eip1967_implementation_non_zero_address do - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) + mock_empty_logic_storage_pointer_request() + |> mock_empty_beacon_storage_pointer_request() |> expect(:json_rpc, fn %{ id: 0, method: "eth_getStorageAt", @@ -400,42 +311,9 @@ defmodule Explorer.Chain.SmartContractTest do _options -> {:error, "error"} end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) + |> mock_empty_beacon_storage_pointer_request() + |> mock_empty_oz_storage_pointer_request() + |> mock_empty_eip_1822_storage_pointer_request() end def assert_empty_implementation(address_hash) do @@ -893,6 +771,23 @@ defmodule Explorer.Chain.SmartContractTest do end defp expect_address_in_response(string_implementation_address_hash) do + mock_empty_logic_storage_pointer_request() + |> mock_empty_beacon_storage_pointer_request() + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, string_implementation_address_hash} + end) + end + + defp mock_empty_logic_storage_pointer_request do expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ id: 0, method: "eth_getStorageAt", @@ -905,29 +800,50 @@ defmodule Explorer.Chain.SmartContractTest do _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", - "latest" - ] - }, - _options -> + end + + defp mock_empty_beacon_storage_pointer_request(mox) do + expect(mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", - "latest" - ] - }, - _options -> - {:ok, string_implementation_address_hash} + end + + defp mock_empty_eip_1822_storage_pointer_request(mox) do + expect(mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + end + + defp mock_empty_oz_storage_pointer_request(mox) do + expect(mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) end end From 62362b1f316baae076319a4872a098e1bd5969c3 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 27 Nov 2023 11:42:28 +0300 Subject: [PATCH 087/607] Fix order of proxy standards: 1167, 1967 --- .../explorer/chain/smart_contract/proxy.ex | 20 +++++++++---------- .../chain/smart_contract/proxy_test.exs | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex index 6bae3ae51f47..8912cca6a3de 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -156,20 +156,20 @@ defmodule Explorer.Chain.SmartContract.Proxy do end defp get_implementation_address_hash_string(proxy_address_hash, proxy_abi) do - get_implementation_address_hash_string_eip1967( + get_implementation_address_hash_string_eip1167( proxy_address_hash, proxy_abi ) end @doc """ - Returns EIP-1967 implementation address or tries next proxy pattern + Returns EIP-1167 implementation address or tries next proxy pattern """ - @spec get_implementation_address_hash_string_eip1967(Hash.Address.t(), any()) :: String.t() | nil - def get_implementation_address_hash_string_eip1967(proxy_address_hash, proxy_abi) do + @spec get_implementation_address_hash_string_eip1167(Hash.Address.t(), any()) :: String.t() | nil + def get_implementation_address_hash_string_eip1167(proxy_address_hash, proxy_abi) do get_implementation_address_hash_string_by_module( - EIP1967, - :get_implementation_address_hash_string_eip1167, + EIP1167, + :get_implementation_address_hash_string_eip1967, [ proxy_address_hash, proxy_abi @@ -178,12 +178,12 @@ defmodule Explorer.Chain.SmartContract.Proxy do end @doc """ - Returns EIP-1167 implementation address or tries next proxy pattern + Returns EIP-1967 implementation address or tries next proxy pattern """ - @spec get_implementation_address_hash_string_eip1167(Hash.Address.t(), any()) :: String.t() | nil - def get_implementation_address_hash_string_eip1167(proxy_address_hash, proxy_abi) do + @spec get_implementation_address_hash_string_eip1967(Hash.Address.t(), any()) :: String.t() | nil + def get_implementation_address_hash_string_eip1967(proxy_address_hash, proxy_abi) do get_implementation_address_hash_string_by_module( - EIP1167, + EIP1967, :get_implementation_address_hash_string_eip1822, [ proxy_address_hash, diff --git a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs index 410b9d498c65..c0f84d72da3f 100644 --- a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs @@ -439,10 +439,10 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do :json_rpc, fn [ %{ - id: _id, + id: id, method: "eth_call", params: [ - %{data: "0x5c60da1b", to: "0x000000000000000000000000" <> beacon_contract_address_hash_string}, + %{data: "0x5c60da1b", to: "0x000000000000000000000000" <> ^beacon_contract_address_hash_string}, "latest" ] } @@ -452,7 +452,7 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do :ok, [ %{ - id: _id, + id: id, jsonrpc: "2.0", result: "0x000000000000000000000000" <> implementation_contract_address_hash_string } From b79b2ab50f1845cd3f97bd6702e8433fd0ebbb9a Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 4 Dec 2023 14:50:22 +0300 Subject: [PATCH 088/607] Fix reviewer comments --- .../explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex index ca426ef59f22..5cc06c75ed9e 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex @@ -77,7 +77,6 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do {:ok, beacon_contract_address} -> case @implementation_signature - |> Proxy.abi_decode_address_output() |> Basic.get_implementation_address_hash_string( beacon_contract_address, implementation_method_abi From ef095cd148c114fbf90f36c0820e86e3d4d53b46 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 23 Nov 2023 19:17:43 +0400 Subject: [PATCH 089/607] Change order of proxy contracts patterns detection: existing popular EIPs to the top of the list --- .../test/explorer/chain/smart_contract/proxy_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs index c0f84d72da3f..f5fd83b562a2 100644 --- a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs @@ -439,7 +439,7 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do :json_rpc, fn [ %{ - id: id, + id: _id, method: "eth_call", params: [ %{data: "0x5c60da1b", to: "0x000000000000000000000000" <> ^beacon_contract_address_hash_string}, @@ -452,7 +452,7 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do :ok, [ %{ - id: id, + id: _id, jsonrpc: "2.0", result: "0x000000000000000000000000" <> implementation_contract_address_hash_string } From e84ffe7770b4bd769339317db31a75c60e0a76a8 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 28 Nov 2023 12:36:00 +0300 Subject: [PATCH 090/607] Add Compound proxy pattern --- CHANGELOG.md | 2 ++ .../block_scout_web/views/address_contract_view.ex | 10 +++++++++- apps/block_scout_web/priv/gettext/default.pot | 4 ++-- .../priv/gettext/en/LC_MESSAGES/default.po | 4 ++-- .../lib/explorer/chain/smart_contract/proxy.ex | 11 +++++++++++ 5 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6daf40ffd839..66fbf8355461 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Features +- [#8900](https://github.com/blockscout/blockscout/pull/8900) - Add Compound proxy contract pattern + ### Fixes - [#8917](https://github.com/blockscout/blockscout/pull/8917) - Proxy detection hotfix in API v2 diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex index 230f17c8ed60..01b13e6a71d3 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex @@ -1,6 +1,8 @@ defmodule BlockScoutWeb.AddressContractView do use BlockScoutWeb, :view + require Logger + import Explorer.Helper, only: [decode_data: 2] alias ABI.FunctionSelector @@ -132,6 +134,12 @@ defmodule BlockScoutWeb.AddressContractView do chain_id = Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:chain_id] repo_url = Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:repo_url] match = if partial_match, do: "/partial_match/", else: "/full_match/" - repo_url <> match <> chain_id <> "/" <> checksummed_hash <> "/" + + if chain_id do + repo_url <> match <> chain_id <> "/" <> checksummed_hash <> "/" + else + Logger.warning("chain_id is nil. Please set CHAIN_ID env variable.") + nil + end end end diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index d1902773008b..7ef46448585b 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -3581,7 +3581,7 @@ msgstr "" msgid "fallback" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:28 +#: lib/block_scout_web/views/address_contract_view.ex:30 #, elixir-autogen, elixir-format msgid "false" msgstr "" @@ -3637,7 +3637,7 @@ msgstr "" msgid "string" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:27 +#: lib/block_scout_web/views/address_contract_view.ex:29 #, elixir-autogen, elixir-format msgid "true" msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index c3c3d7628b7a..2f650d468712 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -3581,7 +3581,7 @@ msgstr "" msgid "fallback" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:28 +#: lib/block_scout_web/views/address_contract_view.ex:30 #, elixir-autogen, elixir-format msgid "false" msgstr "" @@ -3637,7 +3637,7 @@ msgstr "" msgid "string" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:27 +#: lib/block_scout_web/views/address_contract_view.ex:29 #, elixir-autogen, elixir-format msgid "true" msgstr "" diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex index 8912cca6a3de..a5c9ce5a4e09 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -20,6 +20,8 @@ defmodule Explorer.Chain.SmartContract.Proxy do @implementation_signature "5c60da1b" # aaf10f42 = keccak256(getImplementation()) @get_implementation_signature "aaf10f42" + # bb82aa5e = keccak256(comptrollerImplementation()) Compound protocol proxy pattern + @comptroller_implementation_signature "bb82aa5e" # aaf10f42 = keccak256(getAddress(bytes32)) @get_address_signature "21f8a721" @@ -220,6 +222,8 @@ defmodule Explorer.Chain.SmartContract.Proxy do get_implementation_method_abi = get_naive_implementation_abi(proxy_abi, "getImplementation") + comptroller_implementation_method_abi = get_naive_implementation_abi(proxy_abi, "comptrollerImplementation") + master_copy_method_abi = get_master_copy_pattern(proxy_abi) get_address_method_abi = get_naive_implementation_abi(proxy_abi, "getAddress") @@ -234,6 +238,13 @@ defmodule Explorer.Chain.SmartContract.Proxy do master_copy_method_abi -> MasterCopy.get_implementation_address_hash_string(proxy_address_hash) + comptroller_implementation_method_abi -> + Basic.get_implementation_address_hash_string( + @comptroller_implementation_signature, + proxy_address_hash, + proxy_abi + ) + get_address_method_abi -> EIP930.get_implementation_address_hash_string(@get_address_signature, proxy_address_hash, proxy_abi) From 8c98551c5c86565f2e7c5dbe85af65aa344b23af Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 28 Nov 2023 21:53:24 +0300 Subject: [PATCH 091/607] Fix abi encoded string argument --- CHANGELOG.md | 1 + .../lib/ethereum_jsonrpc/encoder.ex | 18 ++++++++++++------ .../test/ethereum_jsonrpc/encoder_test.exs | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6daf40ffd839..bdd63e945109 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - [#8917](https://github.com/blockscout/blockscout/pull/8917) - Proxy detection hotfix in API v2 - [#8915](https://github.com/blockscout/blockscout/pull/8915) - smart-contract: delete embeds_many relation on replace +- [#8906](https://github.com/blockscout/blockscout/pull/8906) - Fix abi encoded string argument - [#8882](https://github.com/blockscout/blockscout/pull/8882) - Change order of proxy contracts patterns detection: existing popular EIPs to the top of the list ### Chore diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex index 37573679aa51..4f2d83e49433 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex @@ -13,7 +13,7 @@ defmodule EthereumJSONRPC.Encoder do """ @spec encode_function_call(ABI.FunctionSelector.t(), [term()]) :: String.t() def encode_function_call(function_selector, args) when is_list(args) do - parsed_args = parse_args(args) + parsed_args = parse_args(args, function_selector.types) encoded_args = function_selector @@ -25,16 +25,22 @@ defmodule EthereumJSONRPC.Encoder do def encode_function_call(function_selector, args), do: encode_function_call(function_selector, [args]) - defp parse_args(args) when is_list(args) do + defp parse_args(args, types) when is_list(args) do args - |> Enum.map(&parse_args/1) + |> Enum.zip(types) + |> Enum.map(fn {arg, type} -> + parse_args(arg, type) + end) end - defp parse_args(<<"0x", hexadecimal_digits::binary>>), do: Base.decode16!(hexadecimal_digits, case: :mixed) + defp parse_args(<<"0x", hexadecimal_digits::binary>>, _type), do: Base.decode16!(hexadecimal_digits, case: :mixed) + + defp parse_args(<>, type) when type in [:string, "string"], + do: hexadecimal_digits |> Base.encode16() |> try_to_decode() - defp parse_args(<>), do: try_to_decode(hexadecimal_digits) + defp parse_args(<>, _type), do: try_to_decode(hexadecimal_digits) - defp parse_args(arg), do: arg + defp parse_args(arg, _type), do: arg defp try_to_decode(hexadecimal_digits) do case Base.decode16(hexadecimal_digits, case: :mixed) do diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs index 38f87e08af4c..d23af7e0de93 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs @@ -27,6 +27,22 @@ defmodule EthereumJSONRPC.EncoderTest do "0x9507d39a000000000000000000000000000000000000000000000000000000000000000a" end + test "generates the correct encoding with string argument" do + function_selector = %ABI.FunctionSelector{ + function: "isNewsletterCoverFullyClaimed", + input_names: ["newsletterId"], + inputs_indexed: nil, + return_names: [""], + returns: [:bool], + state_mutability: :view, + type: :function, + types: [:string] + } + + assert Encoder.encode_function_call(function_selector, ["6564f5623e2a9f0001cb7fee"]) == + "0xa07a712d000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000183635363466353632336532613966303030316362376665650000000000000000" + end + test "generates the correct encoding with addresses arguments" do function_selector = %ABI.FunctionSelector{ function: "tokens", From 65b1cef8a62fe7ede83fd6d7df5be8604e8a6e33 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 4 Dec 2023 17:54:39 +0300 Subject: [PATCH 092/607] Reviewer comment: parse_args swap clauses --- apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex index 4f2d83e49433..e667ce8eaa89 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex @@ -33,11 +33,11 @@ defmodule EthereumJSONRPC.Encoder do end) end - defp parse_args(<<"0x", hexadecimal_digits::binary>>, _type), do: Base.decode16!(hexadecimal_digits, case: :mixed) - defp parse_args(<>, type) when type in [:string, "string"], do: hexadecimal_digits |> Base.encode16() |> try_to_decode() + defp parse_args(<<"0x", hexadecimal_digits::binary>>, _type), do: Base.decode16!(hexadecimal_digits, case: :mixed) + defp parse_args(<>, _type), do: try_to_decode(hexadecimal_digits) defp parse_args(arg, _type), do: arg From 571a6cdc2c32f87c86f03fd95aebb9c1a29a2c8c Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 4 Dec 2023 19:24:56 +0300 Subject: [PATCH 093/607] Add test with 0x... input in string type argument --- .../test/ethereum_jsonrpc/encoder_test.exs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs index d23af7e0de93..7539b8e582ba 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs @@ -43,6 +43,22 @@ defmodule EthereumJSONRPC.EncoderTest do "0xa07a712d000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000183635363466353632336532613966303030316362376665650000000000000000" end + test "generates the correct encoding with string started with 0x" do + function_selector = %ABI.FunctionSelector{ + function: "isNewsletterCoverFullyClaimed", + input_names: ["newsletterId"], + inputs_indexed: nil, + return_names: [""], + returns: [:bool], + state_mutability: :view, + type: :function, + types: [:string] + } + + assert Encoder.encode_function_call(function_selector, ["0x123"]) == + "0xa07a712d000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000053078313233000000000000000000000000000000000000000000000000000000" + end + test "generates the correct encoding with addresses arguments" do function_selector = %ABI.FunctionSelector{ function: "tokens", From d275b2256d96acea66b76ba559e6e9593f13a970 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Mon, 7 Aug 2023 18:10:39 +0300 Subject: [PATCH 094/607] Add general sorting for any scheme --- CHANGELOG.md | 1 + .../lib/block_scout_web/chain.ex | 57 +- .../controllers/address_token_controller.ex | 6 +- .../address_transaction_controller.ex | 4 +- .../controllers/api/v2/address_controller.ex | 24 +- .../api/v2/smart_contract_controller.ex | 7 +- .../controllers/api/v2/token_controller.ex | 4 +- .../controllers/tokens/tokens_controller.ex | 4 +- .../verified_contracts_controller.ex | 3 +- .../lib/block_scout_web/paging_helper.ex | 32 +- .../templates/robots/sitemap.xml.eex | 2 +- .../lib/block_scout_web/views/robots_view.ex | 2 +- .../api/v2/address_controller_test.exs | 161 +++ .../api/v2/smart_contract_controller_test.exs | 101 ++ .../api/v2/token_controller_test.exs | 24 +- .../verified_contracts_controller_test.exs | 4 +- apps/explorer/lib/explorer/chain.ex | 460 ++------- .../lib/explorer/chain/block/reward.ex | 12 +- .../address_transaction_csv_exporter.ex | 6 +- .../explorer/chain/internal_transaction.ex | 32 - .../lib/explorer/chain/smart_contract.ex | 58 +- apps/explorer/lib/explorer/chain/token.ex | 207 +--- .../lib/explorer/chain/transaction.ex | 436 +++++++- apps/explorer/lib/explorer/sorting_helper.ex | 168 +++ .../explorer/chain/smart_contract_test.exs | 11 +- .../test/explorer/chain/transaction_test.exs | 953 +++++++++++++++++- apps/explorer/test/explorer/chain_test.exs | 534 ---------- cspell.json | 18 +- 28 files changed, 2148 insertions(+), 1183 deletions(-) create mode 100644 apps/explorer/lib/explorer/sorting_helper.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index c13b70f75e4c..a19a159835d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ - [#8768](https://github.com/blockscout/blockscout/pull/8768) - Add possibility to search tokens by address hash - [#8750](https://github.com/blockscout/blockscout/pull/8750) - Support new eth-bytecode-db request metadata fields - [#8634](https://github.com/blockscout/blockscout/pull/8634) - API v2: NFT for address +- [#8611](https://github.com/blockscout/blockscout/pull/8611) - Implement sorting of smart contracts, address transactions - [#8609](https://github.com/blockscout/blockscout/pull/8609) - Change logs format to JSON; Add endpoint url to the block_scout_web logging - [#8558](https://github.com/blockscout/blockscout/pull/8558) - Add CoinBalanceDailyUpdater diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index c7a317956057..8bb9d4d0eb2c 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -17,6 +17,7 @@ defmodule BlockScoutWeb.Chain do import Explorer.Helper, only: [parse_integer: 1] + alias Ecto.Association.NotLoaded alias Explorer.Account.{TagAddress, TagTransaction, WatchlistAddress} alias Explorer.Chain.Block.Reward @@ -140,6 +141,34 @@ defmodule BlockScoutWeb.Chain do end end + def paging_options(%{ + "fee" => fee_string, + "value" => value_string, + "block_number" => block_number_string, + "index" => index_string, + "inserted_at" => inserted_at_string, + "hash" => hash_string + }) do + with {:ok, hash} <- string_to_transaction_hash(hash_string), + {:ok, inserted_at, _} <- DateTime.from_iso8601(inserted_at_string) do + [ + paging_options: %{ + @default_paging_options + | key: %{ + fee: decimal_parse(fee_string), + value: decimal_parse(value_string), + block_number: parse_integer(block_number_string), + index: parse_integer(index_string), + inserted_at: inserted_at, + hash: hash + } + } + ] + else + _ -> [paging_options: @default_paging_options] + end + end + def paging_options(%{ "address_hash" => address_hash_string, "tx_hash" => tx_hash_string, @@ -352,8 +381,17 @@ defmodule BlockScoutWeb.Chain do end end - def paging_options(%{"smart_contract_id" => id}) do - [paging_options: %{@default_paging_options | key: {id}}] + def paging_options(%{"smart_contract_id" => id_str} = params) do + transactions_count = parse_integer(params["tx_count"]) + coin_balance = parse_integer(params["coin_balance"]) + id = parse_integer(id_str) + + [ + paging_options: %{ + @default_paging_options + | key: %{id: id, transactions_count: transactions_count, fetched_coin_balance: coin_balance} + } + ] end def paging_options(%{"items_count" => items_count_string, "state_changes" => _}) when is_binary(items_count_string) do @@ -553,10 +591,19 @@ defmodule BlockScoutWeb.Chain do %{"block_number" => block_number} end - defp paging_params(%SmartContract{} = smart_contract) do + defp paging_params(%SmartContract{address: %NotLoaded{}} = smart_contract) do %{"smart_contract_id" => smart_contract.id} end + defp paging_params(%SmartContract{} = smart_contract) do + %{ + "smart_contract_id" => smart_contract.id, + "tx_count" => smart_contract.address.transactions_count, + "coin_balance" => + smart_contract.address.fetched_coin_balance && Wei.to(smart_contract.address.fetched_coin_balance, :wei) + } + end + defp paging_params(%Withdrawal{index: index}) do %{"index" => index} end @@ -602,7 +649,9 @@ defmodule BlockScoutWeb.Chain do %{"id" => msg_id} end - @spec paging_params_with_fiat_value(CurrentTokenBalance.t()) :: %{binary() => any} + @spec paging_params_with_fiat_value(CurrentTokenBalance.t()) :: %{ + required(String.t()) => Decimal.t() | non_neg_integer() | nil + } def paging_params_with_fiat_value(%CurrentTokenBalance{id: id, value: value} = ctb) do %{"fiat_value" => ctb.fiat_value, "value" => value, "id" => id} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex index 8433e96be186..d130043d8700 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex @@ -1,7 +1,9 @@ defmodule BlockScoutWeb.AddressTokenController do use BlockScoutWeb, :controller - import BlockScoutWeb.Chain, only: [next_page_params: 4, paging_options: 1, split_list_by_page: 1] + import BlockScoutWeb.Chain, + only: [next_page_params: 4, paging_options: 1, split_list_by_page: 1, paging_params_with_fiat_value: 1] + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] @@ -22,7 +24,7 @@ defmodule BlockScoutWeb.AddressTokenController do {tokens, next_page} = split_list_by_page(token_balances_plus_one) next_page_path = - case next_page_params(next_page, tokens, params, &BlockScoutWeb.Chain.paging_params_with_fiat_value/1) do + case next_page_params(next_page, tokens, params, &paging_params_with_fiat_value/1) do nil -> nil diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex index 639e5cc39466..48ed703f1217 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -20,7 +20,7 @@ defmodule BlockScoutWeb.AddressTransactionController do AddressTransactionCsvExporter } - alias Explorer.Chain.Wei + alias Explorer.Chain.{Transaction, Wei} alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -53,7 +53,7 @@ defmodule BlockScoutWeb.AddressTransactionController do |> Keyword.merge(paging_options(params)) |> Keyword.merge(current_filter(params)) - results_plus_one = Chain.address_to_transactions_with_rewards(address_hash, options) + results_plus_one = Transaction.address_to_transactions_with_rewards(address_hash, options) {results, next_page} = split_list_by_page(results_plus_one) next_page_url = diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex index b3008a00f5ba..b6d875c7d91c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -8,11 +8,17 @@ defmodule BlockScoutWeb.API.V2.AddressController do token_transfers_next_page_params: 3, paging_options: 1, split_list_by_page: 1, - current_filter: 1 + current_filter: 1, + paging_params_with_fiat_value: 1 ] import BlockScoutWeb.PagingHelper, - only: [delete_parameters_from_next_page_params: 1, token_transfers_types_options: 1, nft_token_types_options: 1] + only: [ + delete_parameters_from_next_page_params: 1, + token_transfers_types_options: 1, + address_transactions_sorting: 1, + nft_token_types_options: 1 + ] alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.API.V2.{BlockView, TransactionView, WithdrawalView} @@ -20,6 +26,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do alias Explorer.Chain.Address alias Explorer.Chain.Address.Counters alias Explorer.Chain.Token.Instance + alias Explorer.Chain.Transaction alias Indexer.Fetcher.{CoinBalanceOnDemand, TokenBalanceOnDemand} @transaction_necessity_by_association [ @@ -120,11 +127,18 @@ defmodule BlockScoutWeb.API.V2.AddressController do @transaction_necessity_by_association |> Keyword.merge(paging_options(params)) |> Keyword.merge(current_filter(params)) + |> Keyword.merge(address_transactions_sorting(params)) - results_plus_one = Chain.address_to_transactions_without_rewards(address_hash, options, false) + results_plus_one = Transaction.address_to_transactions_without_rewards(address_hash, options, false) {transactions, next_page} = split_list_by_page(results_plus_one) - next_page_params = next_page |> next_page_params(transactions, delete_parameters_from_next_page_params(params)) + next_page_params = + next_page + |> next_page_params( + transactions, + delete_parameters_from_next_page_params(params), + &Transaction.address_transactions_next_page_params/1 + ) conn |> put_status(200) @@ -354,7 +368,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do |> next_page_params( tokens, delete_parameters_from_next_page_params(params), - &BlockScoutWeb.Chain.paging_params_with_fiat_value/1 + &paging_params_with_fiat_value/1 ) conn diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex index ab443c33571f..3fe7b47c26a9 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] import BlockScoutWeb.PagingHelper, - only: [current_filter: 1, delete_parameters_from_next_page_params: 1, search_query: 1] + only: [current_filter: 1, delete_parameters_from_next_page_params: 1, search_query: 1, smart_contracts_sorting: 1] import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] import Explorer.SmartContract.Solidity.Verifier, only: [parse_boolean: 1] @@ -192,13 +192,14 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do def smart_contracts_list(conn, params) do full_options = - [necessity_by_association: %{[address: :token] => :optional, [address: :names] => :optional}] + [necessity_by_association: %{[address: :token] => :optional, [address: :names] => :optional, address: :required}] |> Keyword.merge(paging_options(params)) |> Keyword.merge(current_filter(params)) |> Keyword.merge(search_query(params)) + |> Keyword.merge(smart_contracts_sorting(params)) |> Keyword.merge(@api_true) - smart_contracts_plus_one = Chain.verified_contracts(full_options) + smart_contracts_plus_one = SmartContract.verified_contracts(full_options) {smart_contracts, next_page} = split_list_by_page(smart_contracts_plus_one) next_page_params = diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex index 14852429ab61..e98f648cb984 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.API.V2.{AddressView, TransactionView} alias Explorer.{Chain, Repo} - alias Explorer.Chain.{Address, Token.Instance} + alias Explorer.Chain.{Address, Token, Token.Instance} alias Indexer.Fetcher.TokenTotalSupplyOnDemand import BlockScoutWeb.Chain, @@ -248,7 +248,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do |> Keyword.merge(tokens_sorting(params)) |> Keyword.merge(@api_true) - {tokens, next_page} = filter |> Chain.list_top_tokens(options) |> split_list_by_page() + {tokens, next_page} = filter |> Token.list_top(options) |> split_list_by_page() next_page_params = next_page |> next_page_params(tokens, delete_parameters_from_next_page_params(params)) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex index a0bae11f5ef8..17ee2823fcd5 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.TokensController do import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] alias BlockScoutWeb.{Controller, TokensView} - alias Explorer.Chain + alias Explorer.Chain.Token alias Phoenix.View def index(conn, %{"type" => "JSON"} = params) do @@ -18,7 +18,7 @@ defmodule BlockScoutWeb.TokensController do params |> paging_options() - tokens = Chain.list_top_tokens(filter, paging_params) + tokens = Token.list_top(filter, paging_params) {tokens_page, next_page} = split_list_by_page(tokens) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/verified_contracts_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/verified_contracts_controller.ex index 2059498cce2a..1eec03d72678 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/verified_contracts_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/verified_contracts_controller.ex @@ -8,6 +8,7 @@ defmodule BlockScoutWeb.VerifiedContractsController do alias BlockScoutWeb.{Controller, VerifiedContractsView} alias Explorer.Chain + alias Explorer.Chain.SmartContract alias Phoenix.View @necessity_by_association %{[address: :token] => :optional} @@ -19,7 +20,7 @@ defmodule BlockScoutWeb.VerifiedContractsController do |> Keyword.merge(current_filter(params)) |> Keyword.merge(search_query(params)) - verified_contracts_plus_one = Chain.verified_contracts(full_options) + verified_contracts_plus_one = SmartContract.verified_contracts(full_options) {verified_contracts, next_page} = split_list_by_page(verified_contracts_plus_one) items = diff --git a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex index c9a3472c850f..8c2d2b0cca73 100644 --- a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex @@ -3,7 +3,8 @@ defmodule BlockScoutWeb.PagingHelper do Helper for fetching filters and other url query parameters """ import Explorer.Chain, only: [string_to_transaction_hash: 1] - alias Explorer.PagingOptions + alias Explorer.Chain.Transaction + alias Explorer.{PagingOptions, SortingHelper} @page_size 50 @default_paging_options %PagingOptions{page_size: @page_size + 1} @@ -186,6 +187,7 @@ defmodule BlockScoutWeb.PagingHelper do def search_query(_), do: [] + @spec tokens_sorting(%{required(String.t()) => String.t()}) :: [{:sorting, SortingHelper.sorting_params()}] def tokens_sorting(%{"sort" => sort_field, "order" => order}) do [sorting: do_tokens_sorting(sort_field, order)] end @@ -199,4 +201,32 @@ defmodule BlockScoutWeb.PagingHelper do defp do_tokens_sorting("circulating_market_cap", "asc"), do: [asc_nulls_first: :circulating_market_cap] defp do_tokens_sorting("circulating_market_cap", "desc"), do: [desc_nulls_last: :circulating_market_cap] defp do_tokens_sorting(_, _), do: [] + + @spec smart_contracts_sorting(%{required(String.t()) => String.t()}) :: [{:sorting, SortingHelper.sorting_params()}] + def smart_contracts_sorting(%{"sort" => sort_field, "order" => order}) do + [sorting: do_smart_contracts_sorting(sort_field, order)] + end + + def smart_contracts_sorting(_), do: [] + + defp do_smart_contracts_sorting("balance", "asc"), do: [{:asc_nulls_first, :fetched_coin_balance, :address}] + defp do_smart_contracts_sorting("balance", "desc"), do: [{:desc_nulls_last, :fetched_coin_balance, :address}] + defp do_smart_contracts_sorting("txs_count", "asc"), do: [{:asc_nulls_first, :transactions_count, :address}] + defp do_smart_contracts_sorting("txs_count", "desc"), do: [{:desc_nulls_last, :transactions_count, :address}] + defp do_smart_contracts_sorting(_, _), do: [] + + @spec address_transactions_sorting(%{required(String.t()) => String.t()}) :: [ + {:sorting, SortingHelper.sorting_params()} + ] + def address_transactions_sorting(%{"sort" => sort_field, "order" => order}) do + [sorting: do_address_transaction_sorting(sort_field, order)] + end + + def address_transactions_sorting(_), do: [] + + defp do_address_transaction_sorting("value", "asc"), do: [asc: :value] + defp do_address_transaction_sorting("value", "desc"), do: [desc: :value] + defp do_address_transaction_sorting("fee", "asc"), do: [{:dynamic, :fee, :asc, Transaction.dynamic_fee()}] + defp do_address_transaction_sorting("fee", "desc"), do: [{:dynamic, :fee, :desc, Transaction.dynamic_fee()}] + defp do_address_transaction_sorting(_, _), do: [] end diff --git a/apps/block_scout_web/lib/block_scout_web/templates/robots/sitemap.xml.eex b/apps/block_scout_web/lib/block_scout_web/templates/robots/sitemap.xml.eex index 9f3cbc669802..55d8b1a6115a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/robots/sitemap.xml.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/robots/sitemap.xml.eex @@ -35,7 +35,7 @@ <% end %> - <% tokens = Chain.list_top_tokens(nil, params) %> + <% tokens = Token.list_top(nil, params) %> <%= for token <- tokens do %> <%= host %>/token/<%= to_string(token.contract_address_hash) %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/robots_view.ex b/apps/block_scout_web/lib/block_scout_web/views/robots_view.ex index 20e4cca0596f..9939ec3e684f 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/robots_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/robots_view.ex @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.RobotsView do alias BlockScoutWeb.APIDocsView alias Explorer.{Chain, PagingOptions} - alias Explorer.Chain.Address + alias Explorer.Chain.{Address, Token} @limit 200 defp limit, do: @limit diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index 967c9cee5f1a..13ee1e884f1a 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -16,6 +16,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do Token.Instance, TokenTransfer, Transaction, + Wei, Withdrawal } @@ -422,6 +423,166 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do check_paginated_response(response_2nd_page, response, txs_from ++ [Enum.at(txs_to, 0)]) end + + test "ignores wrong ordering params", %{conn: conn} do + address = insert(:address) + + txs = insert_list(51, :transaction, from_address: address) |> with_block() + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "foo", "order" => "bar"}) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/addresses/#{address.hash}/transactions", + %{"sort" => "foo", "order" => "bar"} |> Map.merge(response["next_page_params"]) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, txs) + end + + test "can order and paginate by fee ascending", %{conn: conn} do + address = insert(:address) + + txs_from = insert_list(25, :transaction, from_address: address) |> with_block() + txs_to = insert_list(26, :transaction, to_address: address) |> with_block() + + txs = + (txs_from ++ txs_to) + |> Enum.sort( + &(Decimal.compare(&1 |> Chain.fee(:wei) |> elem(1), &2 |> Chain.fee(:wei) |> elem(1)) in [:eq, :lt]) + ) + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "fee", "order" => "asc"}) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/addresses/#{address.hash}/transactions", + %{"sort" => "fee", "order" => "asc"} |> Map.merge(response["next_page_params"]) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + assert Enum.count(response["items"]) == 50 + assert response["next_page_params"] != nil + compare_item(Enum.at(txs, 0), Enum.at(response["items"], 0)) + compare_item(Enum.at(txs, 49), Enum.at(response["items"], 49)) + + assert Enum.count(response_2nd_page["items"]) == 1 + assert response_2nd_page["next_page_params"] == nil + compare_item(Enum.at(txs, 50), Enum.at(response_2nd_page["items"], 0)) + + check_paginated_response(response, response_2nd_page, txs |> Enum.reverse()) + end + + test "can order and paginate by fee descending", %{conn: conn} do + address = insert(:address) + + txs_from = insert_list(25, :transaction, from_address: address) |> with_block() + txs_to = insert_list(26, :transaction, to_address: address) |> with_block() + + txs = + (txs_from ++ txs_to) + |> Enum.sort( + &(Decimal.compare(&1 |> Chain.fee(:wei) |> elem(1), &2 |> Chain.fee(:wei) |> elem(1)) in [:eq, :gt]) + ) + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "fee", "order" => "desc"}) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/addresses/#{address.hash}/transactions", + %{"sort" => "fee", "order" => "desc"} |> Map.merge(response["next_page_params"]) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + assert Enum.count(response["items"]) == 50 + assert response["next_page_params"] != nil + compare_item(Enum.at(txs, 0), Enum.at(response["items"], 0)) + compare_item(Enum.at(txs, 49), Enum.at(response["items"], 49)) + + assert Enum.count(response_2nd_page["items"]) == 1 + assert response_2nd_page["next_page_params"] == nil + compare_item(Enum.at(txs, 50), Enum.at(response_2nd_page["items"], 0)) + + check_paginated_response(response, response_2nd_page, txs |> Enum.reverse()) + end + + test "can order and paginate by value ascending", %{conn: conn} do + address = insert(:address) + + txs_from = insert_list(25, :transaction, from_address: address) |> with_block() + txs_to = insert_list(26, :transaction, to_address: address) |> with_block() + + txs = + (txs_from ++ txs_to) + |> Enum.sort(&(Decimal.compare(Wei.to(&1.value, :wei), Wei.to(&2.value, :wei)) in [:eq, :lt])) + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "value", "order" => "asc"}) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/addresses/#{address.hash}/transactions", + %{"sort" => "value", "order" => "asc"} |> Map.merge(response["next_page_params"]) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + assert Enum.count(response["items"]) == 50 + assert response["next_page_params"] != nil + compare_item(Enum.at(txs, 0), Enum.at(response["items"], 0)) + compare_item(Enum.at(txs, 49), Enum.at(response["items"], 49)) + + assert Enum.count(response_2nd_page["items"]) == 1 + assert response_2nd_page["next_page_params"] == nil + compare_item(Enum.at(txs, 50), Enum.at(response_2nd_page["items"], 0)) + + check_paginated_response(response, response_2nd_page, txs |> Enum.reverse()) + end + + test "can order and paginate by value descending", %{conn: conn} do + address = insert(:address) + + txs_from = insert_list(25, :transaction, from_address: address) |> with_block() + txs_to = insert_list(26, :transaction, to_address: address) |> with_block() + + txs = + (txs_from ++ txs_to) + |> Enum.sort(&(Decimal.compare(Wei.to(&1.value, :wei), Wei.to(&2.value, :wei)) in [:eq, :gt])) + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "value", "order" => "desc"}) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/addresses/#{address.hash}/transactions", + %{"sort" => "value", "order" => "desc"} |> Map.merge(response["next_page_params"]) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + assert Enum.count(response["items"]) == 50 + assert response["next_page_params"] != nil + compare_item(Enum.at(txs, 0), Enum.at(response["items"], 0)) + compare_item(Enum.at(txs, 49), Enum.at(response["items"], 49)) + + assert Enum.count(response_2nd_page["items"]) == 1 + assert response_2nd_page["next_page_params"] == nil + compare_item(Enum.at(txs, 50), Enum.at(response_2nd_page["items"], 0)) + + check_paginated_response(response, response_2nd_page, txs |> Enum.reverse()) + end end describe "/addresses/{address_hash}/token-transfers" do diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index d429160cdab9..42477f8fa099 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -2447,6 +2447,107 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do check_paginated_response(response, response_2nd_page, smart_contracts) end + + test "ignores wrong ordering params", %{conn: conn} do + smart_contracts = + for _ <- 0..50 do + insert(:smart_contract) + end + + ordering_params = %{"sort" => "foo", "order" => "bar"} + + request = get(conn, "/api/v2/smart-contracts", ordering_params) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/smart-contracts", ordering_params |> Map.merge(response["next_page_params"])) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, smart_contracts) + end + + test "can order by balance ascending", %{conn: conn} do + smart_contracts = + for i <- 0..50 do + address = insert(:address, fetched_coin_balance: i) + insert(:smart_contract, address_hash: address.hash, address: address) + end + |> Enum.reverse() + + ordering_params = %{"sort" => "balance", "order" => "asc"} + + request = get(conn, "/api/v2/smart-contracts", ordering_params) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/smart-contracts", ordering_params |> Map.merge(response["next_page_params"])) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, smart_contracts) + end + + test "can order by balance descending", %{conn: conn} do + smart_contracts = + for i <- 0..50 do + address = insert(:address, fetched_coin_balance: i) + insert(:smart_contract, address_hash: address.hash, address: address) + end + + ordering_params = %{"sort" => "balance", "order" => "desc"} + + request = get(conn, "/api/v2/smart-contracts", ordering_params) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/smart-contracts", ordering_params |> Map.merge(response["next_page_params"])) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, smart_contracts) + end + + test "can order by transaction count ascending", %{conn: conn} do + smart_contracts = + for i <- 0..50 do + address = insert(:address, transactions_count: i) + insert(:smart_contract, address_hash: address.hash, address: address) + end + |> Enum.reverse() + + ordering_params = %{"sort" => "txs_count", "order" => "asc"} + + request = get(conn, "/api/v2/smart-contracts", ordering_params) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/smart-contracts", ordering_params |> Map.merge(response["next_page_params"])) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, smart_contracts) + end + + test "can order by transaction count descending", %{conn: conn} do + smart_contracts = + for i <- 0..50 do + address = insert(:address, transactions_count: i) + insert(:smart_contract, address_hash: address.hash, address: address) + end + + ordering_params = %{"sort" => "txs_count", "order" => "desc"} + + request = get(conn, "/api/v2/smart-contracts", ordering_params) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/smart-contracts", ordering_params |> Map.merge(response["next_page_params"])) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, smart_contracts) + end end describe "/smart-contracts/counters" do diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs index d3506dbe5191..aa1a313ecfda 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs @@ -593,6 +593,23 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do assert %{"items" => [], "next_page_params" => nil} = json_response(request, 200) end + test "ignores wrong ordering params", %{conn: conn} do + tokens = + for i <- 0..50 do + insert(:token, fiat_value: i) + end + + request = get(conn, "/api/v2/tokens", %{"sort" => "foo", "order" => "bar"}) + + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/tokens", %{"sort" => "foo", "order" => "bar"} |> Map.merge(response["next_page_params"])) + + assert response_2nd_page = json_response(request_2nd_page, 200) + check_paginated_response(response, response_2nd_page, tokens) + end + test "tokens are filtered by single type", %{conn: conn} do erc_20_tokens = for i <- 0..50 do @@ -609,14 +626,14 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do insert(:token, type: "ERC-1155") end - check_tokens_pagination(erc_20_tokens |> Enum.reverse(), conn, %{"type" => "ERC-20"}) + check_tokens_pagination(erc_20_tokens, conn, %{"type" => "ERC-20"}) check_tokens_pagination(erc_721_tokens |> Enum.reverse(), conn, %{"type" => "ERC-721"}) check_tokens_pagination(erc_1155_tokens |> Enum.reverse(), conn, %{"type" => "ERC-1155"}) end test "tokens are filtered by multiple type", %{conn: conn} do erc_20_tokens = - for i <- 0..25 do + for i <- 11..36 do insert(:token, fiat_value: i) end @@ -639,7 +656,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do ) check_tokens_pagination( - erc_20_tokens |> Kernel.++(erc_1155_tokens) |> Enum.reverse(), + erc_1155_tokens |> Enum.reverse() |> Kernel.++(erc_20_tokens), conn, %{ "type" => "[erc-20,ERC-1155]" @@ -652,7 +669,6 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do for i <- 0..50 do insert(:token, fiat_value: i) end - |> Enum.reverse() check_tokens_pagination(tokens, conn) end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/verified_contracts_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/verified_contracts_controller_test.exs index 0a68febfa183..55936934dcdb 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/verified_contracts_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/verified_contracts_controller_test.exs @@ -65,7 +65,9 @@ defmodule BlockScoutWeb.VerifiedContractsControllerTest do expected_path = verified_contracts_path(conn, :index, %{ smart_contract_id: id, - items_count: "50" + items_count: "50", + coin_balance: nil, + tx_count: nil }) assert Map.get(json_response(conn, 200), "next_page_path") == expected_path diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 63d5935a3e6f..5fed39e9b6b9 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -55,7 +55,6 @@ defmodule Explorer.Chain do InternalTransaction, Log, PendingBlockOperation, - Search, SmartContract, Token, Token.Instance, @@ -160,7 +159,7 @@ defmodule Explorer.Chain do """ @type necessity_by_association :: %{association => necessity} - @typep necessity_by_association_option :: {:necessity_by_association, necessity_by_association} + @type necessity_by_association_option :: {:necessity_by_association, necessity_by_association} @type paging_options :: {:paging_options, PagingOptions.t()} @typep balance_by_day :: %{date: String.t(), value: Wei.t()} @type api? :: {:api?, true | false} @@ -202,7 +201,7 @@ defmodule Explorer.Chain do InternalTransaction |> InternalTransaction.where_nonpending_block() |> InternalTransaction.where_address_fields_match(hash, :to_address_hash) - |> InternalTransaction.where_block_number_in_period(from_block, to_block) + |> where_block_number_in_period(from_block, to_block) |> common_where_limit_order(paging_options) |> wrapped_union_subquery() @@ -210,7 +209,7 @@ defmodule Explorer.Chain do InternalTransaction |> InternalTransaction.where_nonpending_block() |> InternalTransaction.where_address_fields_match(hash, :from_address_hash) - |> InternalTransaction.where_block_number_in_period(from_block, to_block) + |> where_block_number_in_period(from_block, to_block) |> common_where_limit_order(paging_options) |> wrapped_union_subquery() @@ -218,7 +217,7 @@ defmodule Explorer.Chain do InternalTransaction |> InternalTransaction.where_nonpending_block() |> InternalTransaction.where_address_fields_match(hash, :created_contract_address_hash) - |> InternalTransaction.where_block_number_in_period(from_block, to_block) + |> where_block_number_in_period(from_block, to_block) |> common_where_limit_order(paging_options) |> wrapped_union_subquery() @@ -234,7 +233,7 @@ defmodule Explorer.Chain do InternalTransaction |> InternalTransaction.where_nonpending_block() |> InternalTransaction.where_address_fields_match(hash, direction) - |> InternalTransaction.where_block_number_in_period(from_block, to_block) + |> where_block_number_in_period(from_block, to_block) |> common_where_limit_order(paging_options) |> preload(:block) |> join_associations(necessity_by_association) @@ -262,234 +261,30 @@ defmodule Explorer.Chain do ) end - @doc """ - Fetches the transactions related to the address with the given hash, including - transactions that only have the address in the `token_transfers` related table - and rewards for block validation. - - This query is divided into multiple subqueries intentionally in order to - improve the listing performance. - - The `token_transfers` table tends to grow exponentially, and the query results - with a `transactions` `join` statement takes too long. - - To solve this the `transaction_hashes` are fetched in a separate query, and - paginated through the `block_number` already present in the `token_transfers` - table. - - ## Options - - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is - `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the - `t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`. - * `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and - `:key` (a tuple of the lowest/oldest `{block_number, index}`) and. Results will be the transactions older than - the `block_number` and `index` that are passed. - - """ - @spec address_to_transactions_with_rewards(Hash.Address.t(), [paging_options | necessity_by_association_option]) :: - [ - Transaction.t() - ] - def address_to_transactions_with_rewards(address_hash, options \\ []) when is_list(options) do - paging_options = Keyword.get(options, :paging_options, @default_paging_options) - - if Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] do - cond do - Keyword.get(options, :direction) == :from -> - address_to_transactions_without_rewards(address_hash, options) - - address_has_rewards?(address_hash) -> - address_with_rewards(address_hash, options, paging_options) - - true -> - address_to_transactions_without_rewards(address_hash, options) - end - else - address_to_transactions_without_rewards(address_hash, options) - end - end - - defp address_with_rewards(address_hash, options, paging_options) do - %{payout_key: block_miner_payout_address} = Reward.get_validator_payout_key_by_mining_from_db(address_hash, options) - - if block_miner_payout_address && address_hash == block_miner_payout_address do - transactions_with_rewards_results(address_hash, options, paging_options) - else - address_to_transactions_without_rewards(address_hash, options) - end - end - - defp transactions_with_rewards_results(address_hash, options, paging_options) do - blocks_range = address_to_transactions_tasks_range_of_blocks(address_hash, options) - - rewards_task = - Task.async(fn -> Reward.fetch_emission_rewards_tuples(address_hash, paging_options, blocks_range, options) end) - - [rewards_task | address_to_transactions_tasks(address_hash, options, true)] - |> wait_for_address_transactions() - |> Enum.sort_by(fn item -> - case item do - {%Reward{} = emission_reward, _} -> - {-emission_reward.block.number, 1} - - item -> - process_item(item) - end - end) - |> Enum.dedup_by(fn item -> - case item do - {%Reward{} = emission_reward, _} -> - {emission_reward.block_hash, emission_reward.address_hash, emission_reward.address_type} - - transaction -> - transaction.hash - end - end) - |> Enum.take(paging_options.page_size) - end - - defp process_item(item) do - block_number = if item.block_number, do: -item.block_number, else: 0 - index = if item.index, do: -item.index, else: 0 - {block_number, index} - end - - def address_to_transactions_without_rewards(address_hash, options, old_ui? \\ true) do - paging_options = Keyword.get(options, :paging_options, @default_paging_options) - - address_hash - |> address_to_transactions_tasks(options, old_ui?) - |> wait_for_address_transactions() - |> Enum.sort_by(&{&1.block_number, &1.index}, &>=/2) - |> Enum.dedup_by(& &1.hash) - |> Enum.take(paging_options.page_size) - end - def address_hashes_to_mined_transactions_without_rewards(address_hashes, options) do paging_options = Keyword.get(options, :paging_options, @default_paging_options) address_hashes |> address_hashes_to_mined_transactions_tasks(options) - |> wait_for_address_transactions() + |> Transaction.wait_for_address_transactions() |> Enum.sort_by(&{&1.block_number, &1.index}, &>=/2) |> Enum.dedup_by(& &1.hash) |> Enum.take(paging_options.page_size) end - defp address_to_transactions_tasks_query(options, only_mined? \\ false) do - from_block = from_block(options) - to_block = to_block(options) - - options - |> Keyword.get(:paging_options, @default_paging_options) - |> fetch_transactions(from_block, to_block, !only_mined?) - end - - defp transactions_block_numbers_at_address(address_hash, options) do - direction = Keyword.get(options, :direction) - - options - |> address_to_transactions_tasks_query() - |> Transaction.not_pending_transactions() - |> select([t], t.block_number) - |> Transaction.matching_address_queries_list(direction, address_hash) - end - - defp address_to_transactions_tasks(address_hash, options, old_ui?) do - direction = Keyword.get(options, :direction) - necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) - - options - |> address_to_transactions_tasks_query() - |> Transaction.not_dropped_or_replaced_transactions() - |> join_associations(necessity_by_association) - |> put_has_token_transfers_to_tx(old_ui?) - |> Transaction.matching_address_queries_list(direction, address_hash) - |> Enum.map(fn query -> Task.async(fn -> select_repo(options).all(query) end) end) - end - defp address_hashes_to_mined_transactions_tasks(address_hashes, options) do direction = Keyword.get(options, :direction) necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) options - |> address_to_transactions_tasks_query(true) + |> Transaction.address_to_transactions_tasks_query(true) |> Transaction.not_pending_transactions() |> join_associations(necessity_by_association) - |> put_has_token_transfers_to_tx(false) + |> Transaction.put_has_token_transfers_to_tx(false) |> Transaction.matching_address_queries_list(direction, address_hashes) |> Enum.map(fn query -> Task.async(fn -> select_repo(options).all(query) end) end) end - def address_to_transactions_tasks_range_of_blocks(address_hash, options) do - extremums_list = - address_hash - |> transactions_block_numbers_at_address(options) - |> Enum.map(fn query -> - extremum_query = - from( - q in subquery(query), - select: %{min_block_number: min(q.block_number), max_block_number: max(q.block_number)} - ) - - extremum_query - |> Repo.one!() - end) - - extremums_list - |> Enum.reduce(%{min_block_number: nil, max_block_number: 0}, fn %{ - min_block_number: min_number, - max_block_number: max_number - }, - extremums_result -> - current_min_number = Map.get(extremums_result, :min_block_number) - current_max_number = Map.get(extremums_result, :max_block_number) - - extremums_result - |> process_extremums_result_against_min_number(current_min_number, min_number) - |> process_extremums_result_against_max_number(current_max_number, max_number) - end) - end - - defp process_extremums_result_against_min_number(extremums_result, current_min_number, min_number) - when is_number(current_min_number) and - not (is_number(min_number) and min_number > 0 and min_number < current_min_number) do - extremums_result - end - - defp process_extremums_result_against_min_number(extremums_result, _current_min_number, min_number) do - extremums_result - |> Map.put(:min_block_number, min_number) - end - - defp process_extremums_result_against_max_number(extremums_result, current_max_number, max_number) - when is_number(max_number) and max_number > 0 and max_number > current_max_number do - extremums_result - |> Map.put(:max_block_number, max_number) - end - - defp process_extremums_result_against_max_number(extremums_result, _current_max_number, _max_number) do - extremums_result - end - - defp wait_for_address_transactions(tasks) do - tasks - |> Task.yield_many(:timer.seconds(20)) - |> Enum.flat_map(fn {_task, res} -> - case res do - {:ok, result} -> - result - - {:exit, reason} -> - raise "Query fetching address transactions terminated: #{inspect(reason)}" - - nil -> - raise "Query fetching address transactions timed out." - end - end) - end - @spec address_hash_to_token_transfers(Hash.Address.t(), Keyword.t()) :: [Transaction.t()] def address_hash_to_token_transfers(address_hash, options \\ []) do paging_options = Keyword.get(options, :paging_options, @default_paging_options) @@ -498,7 +293,7 @@ defmodule Explorer.Chain do direction |> Transaction.transactions_with_token_transfers_direction(address_hash) |> Transaction.preload_token_transfers(address_hash) - |> handle_paging_options(paging_options) + |> Transaction.handle_paging_options(paging_options) |> Repo.all() end @@ -633,7 +428,7 @@ defmodule Explorer.Chain do address_hash |> Transaction.transactions_with_token_transfers(token_hash) |> Transaction.preload_token_transfers(address_hash) - |> handle_paging_options(paging_options) + |> Transaction.handle_paging_options(paging_options) |> Repo.all() end @@ -836,7 +631,7 @@ defmodule Explorer.Chain do |> join(:inner, [transaction], block in assoc(transaction, :block)) |> where([_, block], block.hash == ^block_hash) |> join_associations(necessity_by_association) - |> put_has_token_transfers_to_tx(old_ui?) + |> Transaction.put_has_token_transfers_to_tx(old_ui?) |> (&if(old_ui?, do: preload(&1, [{:token_transfers, [:token, :from_address, :to_address]}]), else: &1)).() |> select_repo(options).all() |> (&if(old_ui?, @@ -856,7 +651,7 @@ defmodule Explorer.Chain do |> fetch_transactions_in_descending_order_by_block_and_index() |> where(execution_node_hash: ^execution_node_hash) |> join_associations(necessity_by_association) - |> put_has_token_transfers_to_tx(false) + |> Transaction.put_has_token_transfers_to_tx(false) |> (& &1).() |> select_repo(options).all() |> (&Enum.map(&1, fn tx -> preload_token_transfers(tx, @token_transfers_necessity_by_association, options) end)).() @@ -1718,7 +1513,7 @@ defmodule Explorer.Chain do def hashes_to_transactions(hashes, options \\ []) when is_list(hashes) and is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) - fetch_transactions() + Transaction.fetch_transactions() |> where([transaction], transaction.hash in ^hashes) |> join_associations(necessity_by_association) |> preload([{:token_transfers, [:token, :from_address, :to_address]}]) @@ -1986,41 +1781,59 @@ defmodule Explorer.Chain do end @doc """ - Lists the top `t:Explorer.Chain.Token.t/0`'s'. - + Lists the top `t:Explorer.Chain.Address.t/0`'s' in descending order based on coin balance and address hash. """ - @spec list_top_tokens(String.t()) :: [{Token.t(), non_neg_integer()}] - def list_top_tokens(filter, options \\ []) do + @spec list_top_addresses :: [{Address.t(), non_neg_integer()}] + def list_top_addresses(options \\ []) do paging_options = Keyword.get(options, :paging_options, @default_paging_options) - token_type = Keyword.get(options, :token_type, nil) - sorting = Keyword.get(options, :sorting, []) - fetch_top_tokens(filter, paging_options, token_type, sorting, options) + if is_nil(paging_options.key) do + paging_options.page_size + |> Accounts.take_enough() + |> case do + nil -> + get_addresses(options) + + accounts -> + Enum.map( + accounts, + &{&1, + if is_nil(&1.nonce) do + 0 + else + &1.nonce + 1 + end} + ) + end + else + fetch_top_addresses(options) + end end - defp fetch_top_tokens(filter, paging_options, token_type, sorting, options) do - base_query = Token.base_token_query(token_type, sorting) + defp get_addresses(options) do + accounts_with_n = fetch_top_addresses(options) - base_query_with_paging = - base_query - |> Token.page_tokens(paging_options, sorting) - |> limit(^paging_options.page_size) + accounts_with_n + |> Enum.map(fn {address, _n} -> address end) + |> Accounts.update() - query = - if filter && filter !== "" do - case Search.prepare_search_term(filter) do - {:some, filter_term} -> - base_query_with_paging - |> where(fragment("to_tsvector('english', symbol || ' ' || name) @@ to_tsquery(?)", ^filter_term)) + accounts_with_n + end - _ -> - base_query_with_paging - end - else - base_query_with_paging - end + defp fetch_top_addresses(options) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) - query + base_query = + from(a in Address, + where: a.fetched_coin_balance > ^0, + order_by: [desc: a.fetched_coin_balance, asc: a.hash], + preload: [:names, :smart_contract], + select: {a, fragment("coalesce(1 + ?, 0)", a.nonce)} + ) + + base_query + |> page_addresses(paging_options) + |> limit(^paging_options.page_size) |> select_repo(options).all() end @@ -2865,12 +2678,12 @@ defmodule Explorer.Chain do options ) do paging_options - |> fetch_transactions() + |> Transaction.fetch_transactions() |> where([transaction], not is_nil(transaction.block_number) and not is_nil(transaction.index)) |> apply_filter_by_method_id_to_transactions(method_id_filter) |> apply_filter_by_tx_type_to_transactions(type_filter) |> join_associations(necessity_by_association) - |> put_has_token_transfers_to_tx(old_ui?) + |> Transaction.put_has_token_transfers_to_tx(old_ui?) |> (&if(old_ui?, do: preload(&1, [{:token_transfers, [:token, :from_address, :to_address]}]), else: &1)).() |> select_repo(options).all() |> (&if(old_ui?, @@ -2915,7 +2728,7 @@ defmodule Explorer.Chain do type_filter = Keyword.get(options, :type) Transaction - |> page_pending_transaction(paging_options) + |> Transaction.page_pending_transaction(paging_options) |> limit(^paging_options.page_size) |> pending_transactions_query() |> apply_filter_by_method_id_to_transactions(method_id_filter) @@ -3518,24 +3331,6 @@ defmodule Explorer.Chain do |> limit(^paging_options.page_size) end - defp handle_paging_options(query, nil), do: query - - defp handle_paging_options(query, %PagingOptions{key: nil, page_size: nil}), do: query - - defp handle_paging_options(query, paging_options) do - query - |> page_transaction(paging_options) - |> limit(^paging_options.page_size) - end - - defp handle_verified_contracts_paging_options(query, nil), do: query - - defp handle_verified_contracts_paging_options(query, paging_options) do - query - |> page_verified_contracts(paging_options) - |> limit(^paging_options.page_size) - end - defp handle_withdrawals_paging_options(query, nil), do: query defp handle_withdrawals_paging_options(query, paging_options) do @@ -3551,7 +3346,7 @@ defmodule Explorer.Chain do query |> (&if(paging_options |> Map.get(:page_number, 1) |> process_page_number() == 1, do: &1, - else: page_transaction(&1, paging_options) + else: Transaction.page_transaction(&1, paging_options) )).() |> handle_page(paging_options) end @@ -3594,29 +3389,21 @@ defmodule Explorer.Chain do :required -> from(q in query, inner_join: a in assoc(q, ^association), + as: ^association, left_join: b in assoc(a, ^nested_preload), + as: ^nested_preload, preload: [{^association, {a, [{^nested_preload, b}]}}] ) end end - defp join_association(query, association, necessity) when is_atom(association) do - case necessity do - :optional -> - preload(query, ^association) - - :required -> - from(q in query, inner_join: a in assoc(q, ^association), preload: [{^association, a}]) - end - end - defp join_association(query, association, necessity) do case necessity do :optional -> preload(query, ^association) :required -> - from(q in query, inner_join: a in assoc(q, ^association), preload: [{^association, a}]) + from(q in query, inner_join: a in assoc(q, ^association), as: ^association, preload: [{^association, a}]) end end @@ -3708,46 +3495,6 @@ defmodule Explorer.Chain do where(query, [log], log.index > ^index) end - defp page_pending_transaction(query, %PagingOptions{key: nil}), do: query - - defp page_pending_transaction(query, %PagingOptions{key: {inserted_at, hash}}) do - where( - query, - [transaction], - (is_nil(transaction.block_number) and - (transaction.inserted_at < ^inserted_at or - (transaction.inserted_at == ^inserted_at and transaction.hash > ^hash))) or - not is_nil(transaction.block_number) - ) - end - - defp page_transaction(query, %PagingOptions{key: nil}), do: query - - defp page_transaction(query, %PagingOptions{is_pending_tx: true} = options), - do: page_pending_transaction(query, options) - - defp page_transaction(query, %PagingOptions{key: {block_number, index}, is_index_in_asc_order: true}) do - where( - query, - [transaction], - transaction.block_number < ^block_number or - (transaction.block_number == ^block_number and transaction.index > ^index) - ) - end - - defp page_transaction(query, %PagingOptions{key: {block_number, index}}) do - where( - query, - [transaction], - transaction.block_number < ^block_number or - (transaction.block_number == ^block_number and transaction.index < ^index) - ) - end - - defp page_transaction(query, %PagingOptions{key: {index}}) do - where(query, [transaction], transaction.index < ^index) - end - defp page_block_transactions(query, %PagingOptions{key: nil}), do: query defp page_block_transactions(query, %PagingOptions{key: {_block_number, index}, is_index_in_asc_order: true}) do @@ -3810,12 +3557,6 @@ defmodule Explorer.Chain do ) end - defp page_verified_contracts(query, %PagingOptions{key: nil}), do: query - - defp page_verified_contracts(query, %PagingOptions{key: {id}}) do - where(query, [contract], contract.id < ^id) - end - @doc """ Ensures the following conditions are true: @@ -4126,13 +3867,6 @@ defmodule Explorer.Chain do Repo.exists?(query) end - @spec address_has_rewards?(Address.t()) :: boolean() - def address_has_rewards?(address_hash) do - query = from(r in Reward, where: r.address_hash == ^address_hash) - - Repo.exists?(query) - end - @spec address_tokens_with_balance(Hash.Address.t(), [any()]) :: [] def address_tokens_with_balance(address_hash, paging_options \\ []) do address_hash @@ -5288,10 +5022,12 @@ defmodule Explorer.Chain do end end - defp from_block(options) do + @spec from_block(keyword) :: any + def from_block(options) do Keyword.get(options, :from_block) || nil end + @spec to_block(keyword) :: any def to_block(options) do Keyword.get(options, :to_block) || nil end @@ -5465,54 +5201,6 @@ defmodule Explorer.Chain do dynamic([tx, created_token: created_token], ^dynamic or not is_nil(created_token)) end - @spec verified_contracts([ - paging_options - | necessity_by_association_option - | {:filter, :solidity | :vyper} - | {:search, String.t() | {:api?, true | false}} - ]) :: [SmartContract.t()] - def verified_contracts(options \\ []) do - paging_options = Keyword.get(options, :paging_options, @default_paging_options) - necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) - filter = Keyword.get(options, :filter, nil) - search_string = Keyword.get(options, :search, nil) - - query = from(contract in SmartContract, select: contract, order_by: [desc: :id]) - - query - |> filter_contracts(filter) - |> search_contracts(search_string) - |> handle_verified_contracts_paging_options(paging_options) - |> join_associations(necessity_by_association) - |> select_repo(options).all() - end - - defp search_contracts(basic_query, nil), do: basic_query - - defp search_contracts(basic_query, search_string) do - from(contract in basic_query, - where: - ilike(contract.name, ^"%#{search_string}%") or - ilike(fragment("'0x' || encode(?, 'hex')", contract.address_hash), ^"%#{search_string}%") - ) - end - - defp filter_contracts(basic_query, :solidity) do - basic_query - |> where(is_vyper_contract: ^false) - end - - defp filter_contracts(basic_query, :vyper) do - basic_query - |> where(is_vyper_contract: ^true) - end - - defp filter_contracts(basic_query, :yul) do - from(query in basic_query, where: is_nil(query.abi)) - end - - defp filter_contracts(basic_query, _), do: basic_query - def count_verified_contracts do Repo.aggregate(SmartContract, :count, timeout: :infinity) end @@ -5763,20 +5451,6 @@ defmodule Explorer.Chain do limit(query, ^coin_balances_fetcher_limit) end - def put_has_token_transfers_to_tx(query, true), do: query - - def put_has_token_transfers_to_tx(query, false) do - from(tx in query, - select_merge: %{ - has_token_transfers: - fragment( - "(SELECT transaction_hash FROM token_transfers WHERE transaction_hash = ? LIMIT 1) IS NOT NULL", - tx.hash - ) - } - ) - end - @spec default_paging_options() :: map() def default_paging_options do @default_paging_options diff --git a/apps/explorer/lib/explorer/chain/block/reward.ex b/apps/explorer/lib/explorer/chain/block/reward.ex index 0ec4f05e881e..d45df2666669 100644 --- a/apps/explorer/lib/explorer/chain/block/reward.ex +++ b/apps/explorer/lib/explorer/chain/block/reward.ex @@ -6,7 +6,7 @@ defmodule Explorer.Chain.Block.Reward do use Explorer.Schema alias Explorer.Application.Constants - alias Explorer.{Chain, PagingOptions} + alias Explorer.{Chain, PagingOptions, Repo} alias Explorer.Chain.Block.Reward.AddressType alias Explorer.Chain.{Address, Block, Hash, Validator, Wei} alias Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand @@ -278,4 +278,14 @@ defmodule Explorer.Chain.Block.Reward do query end end + + @doc """ + Checks if an address has rewards + """ + @spec address_has_rewards?(Hash.Address.t()) :: boolean() + def address_has_rewards?(address_hash) do + query = from(r in __MODULE__, where: r.address_hash == ^address_hash) + + Repo.exists?(query) + end end diff --git a/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex index cd2595a7e3a7..a0404f477370 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex @@ -10,7 +10,7 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do alias Explorer.{Chain, Market, PagingOptions, Repo} alias Explorer.Market.MarketHistory - alias Explorer.Chain.{Address, Transaction, Wei} + alias Explorer.Chain.{Address, Hash, Transaction, Wei} alias Explorer.Chain.CSVExport.Helper @necessity_by_association [ @@ -21,7 +21,7 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do @paging_options %PagingOptions{page_size: Helper.limit()} - @spec export(Address.t(), String.t(), String.t(), String.t() | nil, String.t() | nil) :: Enumerable.t() + @spec export(Hash.Address.t(), String.t(), String.t(), String.t() | nil, String.t() | nil) :: Enumerable.t() def export(address_hash, from_period, to_period, filter_type \\ nil, filter_value \\ nil) do {from_block, to_block} = Helper.block_from_period(from_period, to_period) exchange_rate = Market.get_coin_exchange_rate() @@ -44,7 +44,7 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do else: &1 )).() - Chain.address_to_transactions_without_rewards(address_hash, options) + Transaction.address_to_transactions_without_rewards(address_hash, options) end defp to_csv_format(transactions, address_hash, exchange_rate) do diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index c86bfa699f53..82ce66e8c3dc 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -580,38 +580,6 @@ defmodule Explorer.Chain.InternalTransaction do ) end - def where_block_number_in_period(query, from_number, to_number) when is_nil(from_number) and not is_nil(to_number) do - where( - query, - [it], - it.block_number <= ^to_number - ) - end - - def where_block_number_in_period(query, from_number, to_number) when not is_nil(from_number) and is_nil(to_number) do - where( - query, - [it], - it.block_number > ^from_number - ) - end - - def where_block_number_in_period(query, from_number, to_number) when is_nil(from_number) and is_nil(to_number) do - where( - query, - [it], - 1 - ) - end - - def where_block_number_in_period(query, from_number, to_number) do - where( - query, - [it], - it.block_number > ^from_number and it.block_number <= ^to_number - ) - end - def where_block_number_is_not_null(query) do where(query, [t], not is_nil(t.block_number)) end diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index 9e9e198212c2..89b3a27e919a 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -14,7 +14,7 @@ defmodule Explorer.Chain.SmartContract do alias Ecto.{Changeset, Multi} alias Explorer.Counters.AverageBlockTime - alias Explorer.{Chain, Repo} + alias Explorer.{Chain, Repo, SortingHelper} alias Explorer.Chain.{ Address, @@ -53,6 +53,10 @@ defmodule Explorer.Chain.SmartContract do @burn_address_hash_string end + @default_sorting [desc: :id] + + @typep api? :: {:api?, true | false} + @typedoc """ The name of a parameter to a function or event. """ @@ -1242,4 +1246,56 @@ defmodule Explorer.Chain.SmartContract do if smart_contract, do: !smart_contract.partially_verified, else: false end + + @spec verified_contracts([ + Chain.paging_options() + | Chain.necessity_by_association_option() + | {:filter, :solidity | :vyper | :yul} + | {:search, String.t()} + | {:sorting, SortingHelper.sorting_params()} + | Chain.api?() + ]) :: [__MODULE__.t()] + def verified_contracts(options \\ []) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + sorting_options = Keyword.get(options, :sorting, []) + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + filter = Keyword.get(options, :filter, nil) + search_string = Keyword.get(options, :search, nil) + + query = from(contract in __MODULE__) + + query + |> filter_contracts(filter) + |> search_contracts(search_string) + |> SortingHelper.apply_sorting(sorting_options, @default_sorting) + |> SortingHelper.page_with_sorting(paging_options, sorting_options, @default_sorting) + |> Chain.join_associations(necessity_by_association) + |> Chain.select_repo(options).all() + end + + defp search_contracts(basic_query, nil), do: basic_query + + defp search_contracts(basic_query, search_string) do + from(contract in basic_query, + where: + ilike(contract.name, ^"%#{search_string}%") or + ilike(fragment("'0x' || encode(?, 'hex')", contract.address_hash), ^"%#{search_string}%") + ) + end + + defp filter_contracts(basic_query, :solidity) do + basic_query + |> where(is_vyper_contract: ^false) + end + + defp filter_contracts(basic_query, :vyper) do + basic_query + |> where(is_vyper_contract: ^true) + end + + defp filter_contracts(basic_query, :yul) do + from(query in basic_query, where: is_nil(query.abi)) + end + + defp filter_contracts(basic_query, _), do: basic_query end diff --git a/apps/explorer/lib/explorer/chain/token.ex b/apps/explorer/lib/explorer/chain/token.ex index fc12af368a0d..66c3fdd5e2de 100644 --- a/apps/explorer/lib/explorer/chain/token.ex +++ b/apps/explorer/lib/explorer/chain/token.ex @@ -23,10 +23,18 @@ defmodule Explorer.Chain.Token do import Ecto.{Changeset, Query} alias Ecto.Changeset - alias Explorer.{Chain, PagingOptions} - alias Explorer.Chain.{Address, Hash, Token} + alias Explorer.{Chain, SortingHelper} + alias Explorer.Chain.{Address, Hash, Search, Token} alias Explorer.SmartContract.Helper + @default_sorting [ + desc_nulls_last: :circulating_market_cap, + desc_nulls_last: :fiat_value, + desc_nulls_last: :holder_count, + asc: :name, + asc: :contract_address_hash + ] + @typedoc """ * `name` - Name of the token * `symbol` - Trading symbol of the token @@ -160,178 +168,45 @@ defmodule Explorer.Chain.Token do from(token in __MODULE__, where: token.contract_address_hash in ^contract_address_hashes) end - def base_token_query(type, sorting) do - query = from(t in Token, preload: [:contract_address]) - - query |> apply_filter(type) |> apply_sorting(sorting) - end - - defp apply_filter(query, empty_type) when empty_type in [nil, []], do: query - - defp apply_filter(query, token_types) when is_list(token_types) do - from(t in query, where: t.type in ^token_types) - end - - @default_sorting [ - desc_nulls_last: :circulating_market_cap, - desc_nulls_last: :holder_count, - asc: :name, - asc: :contract_address_hash - ] - - defp apply_sorting(query, sorting) when is_list(sorting) do - from(t in query, order_by: ^sorting_with_defaults(sorting)) - end - - defp sorting_with_defaults(sorting) when is_list(sorting) do - (sorting ++ @default_sorting) - |> Enum.uniq_by(fn {_, field} -> field end) - end - - def page_tokens(query, paging_options, sorting \\ []) - def page_tokens(query, %PagingOptions{key: nil}, _sorting), do: query - - def page_tokens( - query, - %PagingOptions{ - key: %{} = key - }, - sorting - ) do - dynamic_where = sorting |> sorting_with_defaults() |> do_page_tokens() - - from(token in query, - where: ^dynamic_where.(key) - ) - end - - defp do_page_tokens([{order, column} | rest]) do - fn key -> page_tokens_by_column(key, column, order, do_page_tokens(rest)) end - end - - defp do_page_tokens([]), do: nil - - defp page_tokens_by_column(%{fiat_value: nil} = key, :fiat_value, :desc_nulls_last, next_column) do - dynamic( - [t], - is_nil(t.fiat_value) and ^next_column.(key) - ) - end - - defp page_tokens_by_column(%{fiat_value: nil} = key, :fiat_value, :asc_nulls_first, next_column) do - next_column.(key) - end - - defp page_tokens_by_column(%{fiat_value: fiat_value} = key, :fiat_value, :desc_nulls_last, next_column) do - dynamic( - [t], - is_nil(t.fiat_value) or t.fiat_value < ^fiat_value or - (t.fiat_value == ^fiat_value and ^next_column.(key)) - ) - end - - defp page_tokens_by_column(%{fiat_value: fiat_value} = key, :fiat_value, :asc_nulls_first, next_column) do - dynamic( - [t], - not is_nil(t.fiat_value) and - (t.fiat_value > ^fiat_value or - (t.fiat_value == ^fiat_value and ^next_column.(key))) - ) - end - - defp page_tokens_by_column( - %{circulating_market_cap: nil} = key, - :circulating_market_cap, - :desc_nulls_last, - next_column - ) do - dynamic( - [t], - is_nil(t.circulating_market_cap) and ^next_column.(key) - ) - end - - defp page_tokens_by_column( - %{circulating_market_cap: nil} = key, - :circulating_market_cap, - :asc_nulls_first, - next_column - ) do - next_column.(key) - end - - defp page_tokens_by_column( - %{circulating_market_cap: circulating_market_cap} = key, - :circulating_market_cap, - :desc_nulls_last, - next_column - ) do - dynamic( - [t], - is_nil(t.circulating_market_cap) or t.circulating_market_cap < ^circulating_market_cap or - (t.circulating_market_cap == ^circulating_market_cap and ^next_column.(key)) - ) - end - - defp page_tokens_by_column( - %{circulating_market_cap: circulating_market_cap} = key, - :circulating_market_cap, - :asc_nulls_first, - next_column - ) do - dynamic( - [t], - not is_nil(t.circulating_market_cap) and - (t.circulating_market_cap > ^circulating_market_cap or - (t.circulating_market_cap == ^circulating_market_cap and ^next_column.(key))) - ) - end + @doc """ + Lists the top `t:__MODULE__.t/0`'s'. + """ + @spec list_top(String.t() | nil, [ + Chain.paging_options() + | {:sorting, SortingHelper.sorting_params()} + | {:token_type, [String.t()]} + ]) :: [Token.t()] + def list_top(filter, options \\ []) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + token_type = Keyword.get(options, :token_type, nil) + sorting = Keyword.get(options, :sorting, []) - defp page_tokens_by_column(%{holder_count: nil} = key, :holder_count, :desc_nulls_last, next_column) do - dynamic( - [t], - is_nil(t.holder_count) and ^next_column.(key) - ) - end + query = from(t in Token, preload: [:contract_address]) - defp page_tokens_by_column(%{holder_count: nil} = key, :holder_count, :asc_nulls_first, next_column) do - next_column.(key) - end + sorted_paginated_query = + query + |> apply_filter(token_type) + |> SortingHelper.apply_sorting(sorting, @default_sorting) + |> SortingHelper.page_with_sorting(paging_options, sorting, @default_sorting) - defp page_tokens_by_column(%{holder_count: holder_count} = key, :holder_count, :desc_nulls_last, next_column) do - dynamic( - [t], - is_nil(t.holder_count) or t.holder_count < ^holder_count or - (t.holder_count == ^holder_count and ^next_column.(key)) - ) - end + filtered_query = + case filter && filter !== "" && Search.prepare_search_term(filter) do + {:some, filter_term} -> + sorted_paginated_query + |> where(fragment("to_tsvector('english', symbol || ' ' || name) @@ to_tsquery(?)", ^filter_term)) - defp page_tokens_by_column(%{holder_count: holder_count} = key, :holder_count, :asc_nulls_first, next_column) do - dynamic( - [t], - not is_nil(t.holder_count) and - (t.holder_count > ^holder_count or - (t.holder_count == ^holder_count and ^next_column.(key))) - ) - end + _ -> + sorted_paginated_query + end - defp page_tokens_by_column(%{name: nil} = key, :name, :asc, next_column) do - dynamic( - [t], - is_nil(t.name) and ^next_column.(key) - ) + filtered_query + |> Chain.select_repo(options).all() end - defp page_tokens_by_column(%{name: name} = key, :name, :asc, next_column) do - dynamic( - [t], - is_nil(t.name) or - (t.name > ^name or (t.name == ^name and ^next_column.(key))) - ) - end + defp apply_filter(query, empty_type) when empty_type in [nil, []], do: query - defp page_tokens_by_column(%{contract_address_hash: contract_address_hash}, :contract_address_hash, :asc, nil) do - dynamic([t], t.contract_address_hash > ^contract_address_hash) + defp apply_filter(query, token_types) when is_list(token_types) do + from(t in query, where: t.type in ^token_types) end def get_by_contract_address_hash(hash, options) do diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 32dc58105f1e..0ed5b547fc5b 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -5,14 +5,12 @@ defmodule Explorer.Chain.Transaction do require Logger - import Ecto.Query, only: [from: 2, preload: 3, subquery: 1, where: 3] - alias ABI.FunctionSelector alias Ecto.Association.NotLoaded alias Ecto.Changeset - alias Explorer.Chain + alias Explorer.{Chain, Repo} alias Explorer.Chain.{ Address, @@ -31,9 +29,11 @@ defmodule Explorer.Chain.Transaction do Wei } + alias Explorer.Chain.Block.Reward alias Explorer.Chain.SmartContract.Proxy alias Explorer.Chain.Transaction.{Fork, Status} alias Explorer.Chain.Zkevm.BatchTransaction + alias Explorer.{PagingOptions, SortingHelper} alias Explorer.SmartContract.SigProviderInterface @optional_attrs ~w(max_priority_fee_per_gas max_fee_per_gas block_hash block_number created_contract_address_hash cumulative_gas_used earliest_processing_start @@ -1213,4 +1213,434 @@ defmodule Explorer.Chain.Transaction do end def bytes_to_address_hash(bytes), do: %Hash{byte_count: 20, bytes: bytes} + + @doc """ + Fetches the transactions related to the address with the given hash, including + transactions that only have the address in the `token_transfers` related table + and rewards for block validation. + + This query is divided into multiple subqueries intentionally in order to + improve the listing performance. + + The `token_transfers` table tends to grow exponentially, and the query results + with a `transactions` `join` statement takes too long. + + To solve this the `transaction_hashes` are fetched in a separate query, and + paginated through the `block_number` already present in the `token_transfers` + table. + + ## Options + + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the + `t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`. + * `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and + `:key` (a tuple of the lowest/oldest `{block_number, index}`) and. Results will be the transactions older than + the `block_number` and `index` that are passed. + + """ + @spec address_to_transactions_with_rewards(Hash.Address.t(), [ + Chain.paging_options() | Chain.necessity_by_association_option() + ]) :: [__MODULE__.t()] + def address_to_transactions_with_rewards(address_hash, options \\ []) when is_list(options) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + + case Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] && + Keyword.get(options, :direction) != :from && + Reward.address_has_rewards?(address_hash) && + Reward.get_validator_payout_key_by_mining_from_db(address_hash, options) do + %{payout_key: block_miner_payout_address} + when not is_nil(block_miner_payout_address) and address_hash == block_miner_payout_address -> + transactions_with_rewards_results(address_hash, options, paging_options) + + _ -> + address_to_transactions_without_rewards(address_hash, options) + end + end + + defp transactions_with_rewards_results(address_hash, options, paging_options) do + blocks_range = address_to_transactions_tasks_range_of_blocks(address_hash, options) + + rewards_task = + Task.async(fn -> Reward.fetch_emission_rewards_tuples(address_hash, paging_options, blocks_range, options) end) + + [rewards_task | address_to_transactions_tasks(address_hash, options, true)] + |> wait_for_address_transactions() + |> Enum.sort_by(fn item -> + case item do + {%Reward{} = emission_reward, _} -> + {-emission_reward.block.number, 1} + + item -> + process_item(item) + end + end) + |> Enum.dedup_by(fn item -> + case item do + {%Reward{} = emission_reward, _} -> + {emission_reward.block_hash, emission_reward.address_hash, emission_reward.address_type} + + transaction -> + transaction.hash + end + end) + |> Enum.take(paging_options.page_size) + end + + @doc false + def address_to_transactions_tasks_range_of_blocks(address_hash, options) do + extremums_list = + address_hash + |> transactions_block_numbers_at_address(options) + |> Enum.map(fn query -> + extremum_query = + from( + q in subquery(query), + select: %{min_block_number: min(q.block_number), max_block_number: max(q.block_number)} + ) + + extremum_query + |> Repo.one!() + end) + + extremums_list + |> Enum.reduce(%{min_block_number: nil, max_block_number: 0}, fn %{ + min_block_number: min_number, + max_block_number: max_number + }, + extremums_result -> + current_min_number = Map.get(extremums_result, :min_block_number) + current_max_number = Map.get(extremums_result, :max_block_number) + + extremums_result + |> process_extremums_result_against_min_number(current_min_number, min_number) + |> process_extremums_result_against_max_number(current_max_number, max_number) + end) + end + + defp transactions_block_numbers_at_address(address_hash, options) do + direction = Keyword.get(options, :direction) + + options + |> address_to_transactions_tasks_query(true) + |> not_pending_transactions() + |> select([t], t.block_number) + |> matching_address_queries_list(direction, address_hash) + end + + defp process_extremums_result_against_min_number(extremums_result, current_min_number, min_number) + when is_number(current_min_number) and + not (is_number(min_number) and min_number > 0 and min_number < current_min_number) do + extremums_result + end + + defp process_extremums_result_against_min_number(extremums_result, _current_min_number, min_number) do + extremums_result + |> Map.put(:min_block_number, min_number) + end + + defp process_extremums_result_against_max_number(extremums_result, current_max_number, max_number) + when is_number(max_number) and max_number > 0 and max_number > current_max_number do + extremums_result + |> Map.put(:max_block_number, max_number) + end + + defp process_extremums_result_against_max_number(extremums_result, _current_max_number, _max_number) do + extremums_result + end + + defp process_item(item) do + block_number = if item.block_number, do: -item.block_number, else: 0 + index = if item.index, do: -item.index, else: 0 + {block_number, index} + end + + @spec address_to_transactions_without_rewards( + Hash.Address.t(), + [ + Chain.paging_options() + | Chain.necessity_by_association_option() + | {:sorting, SortingHelper.sorting_params()} + ], + boolean() + ) :: [__MODULE__.t()] + def address_to_transactions_without_rewards(address_hash, options, old_ui? \\ true) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + + address_hash + |> address_to_transactions_tasks(options, old_ui?) + |> wait_for_address_transactions() + |> Enum.sort(compare_custom_sorting(Keyword.get(options, :sorting, []))) + |> Enum.dedup_by(& &1.hash) + |> Enum.take(paging_options.page_size) + end + + defp address_to_transactions_tasks(address_hash, options, old_ui?) do + direction = Keyword.get(options, :direction) + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + + options + |> address_to_transactions_tasks_query(false, old_ui?) + |> not_dropped_or_replaced_transactions() + |> Chain.join_associations(necessity_by_association) + |> put_has_token_transfers_to_tx(old_ui?) + |> matching_address_queries_list(direction, address_hash) + |> Enum.map(fn query -> Task.async(fn -> Chain.select_repo(options).all(query) end) end) + end + + @doc """ + Returns the address to transactions tasks query based on provided options. + Boolean `only_mined?` argument specifies if only mined transactions should be returned, + boolean `old_ui?` argument specifies if the query is for the old UI, i.e. is query dynamically sorted or no. + """ + @spec address_to_transactions_tasks_query(keyword, boolean, boolean) :: Ecto.Query.t() + def address_to_transactions_tasks_query(options, only_mined? \\ false, old_ui? \\ true) + + def address_to_transactions_tasks_query(options, only_mined?, true) do + from_block = Chain.from_block(options) + to_block = Chain.to_block(options) + + options + |> Keyword.get(:paging_options, Chain.default_paging_options()) + |> fetch_transactions(from_block, to_block, !only_mined?) + end + + def address_to_transactions_tasks_query(options, _only_mined?, false) do + from_block = Chain.from_block(options) + to_block = Chain.to_block(options) + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + sorting_options = Keyword.get(options, :sorting, []) + + fetch_transactions_with_custom_sorting(paging_options, from_block, to_block, sorting_options) + end + + @doc """ + Waits for the address transactions tasks to complete and returns the transactions flattened + in case of success or raises an error otherwise. + """ + @spec wait_for_address_transactions([Task.t()]) :: [__MODULE__.t()] + def wait_for_address_transactions(tasks) do + tasks + |> Task.yield_many(:timer.seconds(20)) + |> Enum.flat_map(fn {_task, res} -> + case res do + {:ok, result} -> + result + + {:exit, reason} -> + raise "Query fetching address transactions terminated: #{inspect(reason)}" + + nil -> + raise "Query fetching address transactions timed out." + end + end) + end + + defp compare_custom_sorting([{order, :value}]) do + fn a, b -> + case Decimal.compare(Wei.to(a.value, :wei), Wei.to(b.value, :wei)) do + :eq -> compare_default_sorting(a, b) + :gt -> order == :desc + :lt -> order == :asc + end + end + end + + defp compare_custom_sorting([{:dynamic, :fee, order, _dynamic_fee}]) do + fn a, b -> + case Decimal.compare(a |> Chain.fee(:wei) |> elem(1), b |> Chain.fee(:wei) |> elem(1)) do + :eq -> compare_default_sorting(a, b) + :gt -> order == :desc + :lt -> order == :asc + end + end + end + + defp compare_custom_sorting([]), do: &compare_default_sorting/2 + + defp compare_default_sorting(a, b) do + case { + compare(a.block_number, b.block_number), + compare(a.index, b.index), + DateTime.compare(a.inserted_at, b.inserted_at), + compare(Hash.to_integer(a.hash), Hash.to_integer(b.hash)) + } do + {:lt, _, _, _} -> false + {:eq, :lt, _, _} -> false + {:eq, :eq, :lt, _} -> false + {:eq, :eq, :eq, :gt} -> false + _ -> true + end + end + + defp compare(a, b) do + cond do + a < b -> :lt + a > b -> :gt + true -> :eq + end + end + + @doc """ + Creates a query to fetch transactions taking into account paging_options (possibly nil), + from_block (may be nil), to_block (may be nil) and boolean `with_pending?` that indicates if pending transactions should be included + into the query. + """ + @spec fetch_transactions(PagingOptions.t() | nil, non_neg_integer | nil, non_neg_integer | nil, boolean()) :: + Ecto.Query.t() + def fetch_transactions(paging_options \\ nil, from_block \\ nil, to_block \\ nil, with_pending? \\ false) do + __MODULE__ + |> order_for_transactions(with_pending?) + |> Chain.where_block_number_in_period(from_block, to_block) + |> handle_paging_options(paging_options) + end + + @default_sorting [ + desc: :block_number, + desc: :index, + desc: :inserted_at, + asc: :hash + ] + + @doc """ + Creates a query to fetch transactions taking into account paging_options (possibly nil), + from_block (may be nil), to_block (may be nil) and sorting_params. + """ + @spec fetch_transactions_with_custom_sorting( + PagingOptions.t() | nil, + non_neg_integer | nil, + non_neg_integer | nil, + SortingHelper.sorting_params() + ) :: Ecto.Query.t() + def fetch_transactions_with_custom_sorting(paging_options, from_block, to_block, sorting) do + query = from(transaction in __MODULE__) + + query + |> Chain.where_block_number_in_period(from_block, to_block) + |> SortingHelper.apply_sorting(sorting, @default_sorting) + |> SortingHelper.page_with_sorting(paging_options, sorting, @default_sorting) + end + + defp order_for_transactions(query, true) do + query + |> order_by([transaction], + desc: transaction.block_number, + desc: transaction.index, + desc: transaction.inserted_at, + asc: transaction.hash + ) + end + + defp order_for_transactions(query, _) do + query + |> order_by([transaction], desc: transaction.block_number, desc: transaction.index) + end + + @doc """ + Updates the provided query with necessary `where`s and `limit`s to take into account paging_options (may be nil). + """ + @spec handle_paging_options(Ecto.Query.t() | atom, nil | Explorer.PagingOptions.t()) :: Ecto.Query.t() + def handle_paging_options(query, nil), do: query + + def handle_paging_options(query, %PagingOptions{key: nil, page_size: nil}), do: query + + def handle_paging_options(query, paging_options) do + query + |> page_transaction(paging_options) + |> limit(^paging_options.page_size) + end + + @doc """ + Updates the provided query with necessary `where`s to take into account paging_options. + """ + @spec page_transaction(Ecto.Query.t() | atom, Explorer.PagingOptions.t()) :: Ecto.Query.t() + def page_transaction(query, %PagingOptions{key: nil}), do: query + + def page_transaction(query, %PagingOptions{is_pending_tx: true} = options), + do: page_pending_transaction(query, options) + + def page_transaction(query, %PagingOptions{key: {block_number, index}, is_index_in_asc_order: true}) do + where( + query, + [transaction], + transaction.block_number < ^block_number or + (transaction.block_number == ^block_number and transaction.index > ^index) + ) + end + + def page_transaction(query, %PagingOptions{key: {block_number, index}}) do + where( + query, + [transaction], + transaction.block_number < ^block_number or + (transaction.block_number == ^block_number and transaction.index < ^index) + ) + end + + def page_transaction(query, %PagingOptions{key: {index}}) do + where(query, [transaction], transaction.index < ^index) + end + + @doc """ + Updates the provided query with necessary `where`s to take into account paging_options. + """ + @spec page_pending_transaction(Ecto.Query.t() | atom, Explorer.PagingOptions.t()) :: Ecto.Query.t() + def page_pending_transaction(query, %PagingOptions{key: nil}), do: query + + def page_pending_transaction(query, %PagingOptions{key: {inserted_at, hash}}) do + where( + query, + [transaction], + (is_nil(transaction.block_number) and + (transaction.inserted_at < ^inserted_at or + (transaction.inserted_at == ^inserted_at and transaction.hash > ^hash))) or + not is_nil(transaction.block_number) + ) + end + + @doc """ + Adds a `has_token_transfers` field to the query via `select_merge` if second argument is `true` and returns + the query untouched otherwise. + """ + @spec put_has_token_transfers_to_tx(Ecto.Query.t() | atom, boolean) :: Ecto.Query.t() + def put_has_token_transfers_to_tx(query, true), do: query + + def put_has_token_transfers_to_tx(query, false) do + from(tx in query, + select_merge: %{ + has_token_transfers: + fragment( + "(SELECT transaction_hash FROM token_transfers WHERE transaction_hash = ? LIMIT 1) IS NOT NULL", + tx.hash + ) + } + ) + end + + @doc """ + Return the dynamic that calculates the fee for transactions. + """ + @spec dynamic_fee :: struct() + def dynamic_fee do + dynamic([tx], tx.gas_price * fragment("COALESCE(?, ?)", tx.gas_used, tx.gas)) + end + + @doc """ + Returns next page params based on the provided transaction. + """ + @spec address_transactions_next_page_params(Explorer.Chain.Transaction.t()) :: %{ + required(String.t()) => Decimal.t() | Wei.t() | non_neg_integer | DateTime.t() | Hash.t() + } + def address_transactions_next_page_params( + %__MODULE__{block_number: block_number, index: index, inserted_at: inserted_at, hash: hash, value: value} = tx + ) do + %{ + "fee" => tx |> Chain.fee(:wei) |> elem(1), + "value" => value, + "block_number" => block_number, + "index" => index, + "inserted_at" => inserted_at, + "hash" => hash + } + end end diff --git a/apps/explorer/lib/explorer/sorting_helper.ex b/apps/explorer/lib/explorer/sorting_helper.ex new file mode 100644 index 000000000000..f6a478d85d78 --- /dev/null +++ b/apps/explorer/lib/explorer/sorting_helper.ex @@ -0,0 +1,168 @@ +defmodule Explorer.SortingHelper do + @moduledoc """ + Module that order and paginate queries dynamically based on default and provided sorting parameters. + Example of sorting parameters: + ``` + [{:asc, :fetched_coin_balance, :address}, {:dynamic, :contract_code_size, :desc, dynamic([t], fragment(LENGTH(?), t.contract_source_code))}, desc: :id] + ``` + First list entry specify joined address table column as a column to order by and paginate, second entry + specifies name of a key in paging_options and arbitrary dynamic that will be used in ordering and pagination, + third entry specifies own column name to order by and paginate. + """ + require Explorer.SortingHelper + + alias Explorer.PagingOptions + + import Ecto.Query + + @typep ordering :: :asc | :asc_nulls_first | :asc_nulls_last | :desc | :desc_nulls_first | :desc_nulls_last + @typep column :: atom + @typep binding :: atom + @type sorting_params :: [ + {ordering, column} | {ordering, column, binding} | {:dynamic, column, ordering, Ecto.Query.dynamic_expr()} + ] + + @doc """ + Applies sorting to query based on default sorting params and sorting params from the client, + these params merged keeping provided one over default one. + """ + @spec apply_sorting(Ecto.Query.t(), sorting_params, sorting_params) :: Ecto.Query.t() + def apply_sorting(query, sorting, default_sorting) when is_list(sorting) and is_list(default_sorting) do + sorting |> merge_sorting_params_with_defaults(default_sorting) |> sorting_params_to_order_by(query) + end + + defp merge_sorting_params_with_defaults([], default_sorting) when is_list(default_sorting), do: default_sorting + + defp merge_sorting_params_with_defaults(sorting, default_sorting) + when is_list(sorting) and is_list(default_sorting) do + (sorting ++ default_sorting) + |> Enum.uniq_by(fn + {_, field} -> field + {_, field, as} -> {field, as} + {:dynamic, key_name, _, _} -> key_name + end) + end + + defp sorting_params_to_order_by(sorting_params, query) do + sorting_params + |> Enum.reduce(query, fn + {:dynamic, _key_name, order, dynamic}, query -> query |> order_by(^[{order, dynamic}]) + {order, column, binding}, query -> query |> order_by([{^order, field(as(^binding), ^column)}]) + {order, column}, query -> query |> order_by(^[{order, column}]) + end) + end + + @doc """ + Page the query based on paging options, default sorting params and sorting params from the client, + these params merged keeping provided one over default one. + """ + @spec page_with_sorting(Ecto.Query.t(), PagingOptions.t(), sorting_params, sorting_params) :: Ecto.Query.t() + def page_with_sorting(query, %PagingOptions{key: key, page_size: page_size}, sorting, default_sorting) + when not is_nil(key) do + sorting + |> merge_sorting_params_with_defaults(default_sorting) + |> do_page_with_sorting() + |> case do + nil -> query + dynamic_where -> query |> where(^dynamic_where.(key)) + end + |> limit_query(page_size) + end + + def page_with_sorting(query, %PagingOptions{page_size: page_size}, _sorting, _default_sorting) do + query |> limit_query(page_size) + end + + def page_with_sorting(query, _, _sorting, _default_sorting), do: query + + defp limit_query(query, limit) when is_integer(limit), do: query |> limit(^limit) + defp limit_query(query, _), do: query + + defp do_page_with_sorting([{order, column} | rest]) do + fn key -> page_by_column(key, column, order, do_page_with_sorting(rest)) end + end + + defp do_page_with_sorting([{:dynamic, key_name, order, dynamic} | rest]) do + fn key -> page_by_column(key, {:dynamic, key_name, dynamic}, order, do_page_with_sorting(rest)) end + end + + defp do_page_with_sorting([{order, column, binding} | rest]) do + fn key -> page_by_column(key, {column, binding}, order, do_page_with_sorting(rest)) end + end + + defp do_page_with_sorting([]), do: nil + + for {key_name, pattern, ecto_value} <- [ + {quote(do: key_name), quote(do: {:dynamic, key_name, dynamic}), quote(do: ^dynamic)}, + {quote(do: column), quote(do: {column, binding}), quote(do: field(as(^binding), ^column))}, + {quote(do: column), quote(do: column), quote(do: field(t, ^column))} + ] do + defp page_by_column(key, unquote(pattern), :desc_nulls_last, next_column) do + case key[unquote(key_name)] do + nil -> + dynamic([t], is_nil(unquote(ecto_value)) and ^apply_next_column(next_column, key)) + + value -> + dynamic( + [t], + is_nil(unquote(ecto_value)) or unquote(ecto_value) < ^value or + (unquote(ecto_value) == ^value and ^apply_next_column(next_column, key)) + ) + end + end + + defp page_by_column(key, unquote(pattern), :asc_nulls_first, next_column) do + case key[unquote(key_name)] do + nil -> + dynamic([t], not is_nil(unquote(ecto_value)) or ^apply_next_column(next_column, key)) + + value -> + dynamic( + [t], + not is_nil(unquote(ecto_value)) and + (unquote(ecto_value) > ^value or + (unquote(ecto_value) == ^value and ^apply_next_column(next_column, key))) + ) + end + end + + defp page_by_column(key, unquote(pattern), order, next_column) when order in ~w(asc asc_nulls_last)a do + case key[unquote(key_name)] do + nil -> + dynamic([t], is_nil(unquote(ecto_value)) and ^apply_next_column(next_column, key)) + + value -> + dynamic( + [t], + is_nil(unquote(ecto_value)) or + (unquote(ecto_value) > ^value or + (unquote(ecto_value) == ^value and ^apply_next_column(next_column, key))) + ) + end + end + + defp page_by_column(key, unquote(pattern), order, next_column) + when order in ~w(desc desc_nulls_first)a do + case key[unquote(key_name)] do + nil -> + dynamic([t], not is_nil(unquote(ecto_value)) or ^apply_next_column(next_column, key)) + + value -> + dynamic( + [t], + not is_nil(unquote(ecto_value)) and + (unquote(ecto_value) < ^value or + (unquote(ecto_value) == ^value and ^apply_next_column(next_column, key))) + ) + end + end + end + + defp apply_next_column(nil, _key) do + false + end + + defp apply_next_column(next_column, key) do + next_column.(key) + end +end diff --git a/apps/explorer/test/explorer/chain/smart_contract_test.exs b/apps/explorer/test/explorer/chain/smart_contract_test.exs index 030601f64634..8bd0b1e5818a 100644 --- a/apps/explorer/test/explorer/chain/smart_contract_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract_test.exs @@ -2,8 +2,9 @@ defmodule Explorer.Chain.SmartContractTest do use Explorer.DataCase, async: false import Mox - alias Explorer.Chain + alias Explorer.{Chain, PagingOptions} alias Explorer.Chain.{Address, SmartContract} + alias Explorer.Chain.Hash alias Explorer.Chain.SmartContract.Proxy doctest Explorer.Chain.SmartContract @@ -265,14 +266,6 @@ defmodule Explorer.Chain.SmartContractTest do end end - describe "address_hash_to_smart_contract/1" do - test "fetches a smart contract" do - smart_contract = insert(:smart_contract, contract_code_md5: "123") - - assert ^smart_contract = SmartContract.address_hash_to_smart_contract(smart_contract.address_hash) - end - end - def get_eip1967_implementation_zero_addresses do mock_empty_logic_storage_pointer_request() |> mock_empty_beacon_storage_pointer_request() diff --git a/apps/explorer/test/explorer/chain/transaction_test.exs b/apps/explorer/test/explorer/chain/transaction_test.exs index d204d2822325..6656cf4277ba 100644 --- a/apps/explorer/test/explorer/chain/transaction_test.exs +++ b/apps/explorer/test/explorer/chain/transaction_test.exs @@ -4,10 +4,13 @@ defmodule Explorer.Chain.TransactionTest do import Mox alias Ecto.Changeset - alias Explorer.Chain.Transaction + alias Explorer.Chain.{Address, InternalTransaction, Transaction} + alias Explorer.PagingOptions doctest Transaction + setup :set_mox_global + setup :verify_on_exit! describe "changeset/2" do @@ -311,6 +314,954 @@ defmodule Explorer.Chain.TransactionTest do end end + describe "address_to_transactions_tasks_range_of_blocks/2" do + test "returns empty extremums if no transactions" do + address = insert(:address) + + extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => nil, + :max_block_number => 0 + } + end + + test "returns correct extremums for from_address" do + address = insert(:address) + + :transaction + |> insert(from_address: address) + |> with_block(insert(:block, number: 1000)) + + extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => 1000, + :max_block_number => 1000 + } + end + + test "returns correct extremums for to_address" do + address = insert(:address) + + :transaction + |> insert(to_address: address) + |> with_block(insert(:block, number: 1000)) + + extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => 1000, + :max_block_number => 1000 + } + end + + test "returns correct extremums for created_contract_address" do + address = insert(:address) + + :transaction + |> insert(created_contract_address: address) + |> with_block(insert(:block, number: 1000)) + + extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => 1000, + :max_block_number => 1000 + } + end + + test "returns correct extremums for multiple number of transactions" do + address = insert(:address) + + :transaction + |> insert(created_contract_address: address) + |> with_block(insert(:block, number: 1000)) + + :transaction + |> insert(created_contract_address: address) + |> with_block(insert(:block, number: 999)) + + :transaction + |> insert(created_contract_address: address) + |> with_block(insert(:block, number: 1003)) + + :transaction + |> insert(from_address: address) + |> with_block(insert(:block, number: 1001)) + + :transaction + |> insert(from_address: address) + |> with_block(insert(:block, number: 1004)) + + :transaction + |> insert(to_address: address) + |> with_block(insert(:block, number: 1002)) + + :transaction + |> insert(to_address: address) + |> with_block(insert(:block, number: 998)) + + extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => 998, + :max_block_number => 1004 + } + end + end + + describe "address_to_transactions_with_rewards/2" do + test "without transactions" do + %Address{hash: address_hash} = insert(:address) + + assert Repo.aggregate(Transaction, :count, :hash) == 0 + + assert [] == Transaction.address_to_transactions_with_rewards(address_hash) + end + + test "with from transactions" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(from_address: address) + |> with_block() + + assert [transaction] == + Transaction.address_to_transactions_with_rewards(address_hash, direction: :from) + |> Repo.preload([:block, :to_address, :from_address]) + end + + test "with to transactions" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(to_address: address) + |> with_block() + + assert [transaction] == + Transaction.address_to_transactions_with_rewards(address_hash, direction: :to) + |> Repo.preload([:block, :to_address, :from_address]) + end + + test "with to and from transactions and direction: :from" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(from_address: address) + |> with_block() + + # only contains "from" transaction + assert [transaction] == + Transaction.address_to_transactions_with_rewards(address_hash, direction: :from) + |> Repo.preload([:block, :to_address, :from_address]) + end + + test "with to and from transactions and direction: :to" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(to_address: address) + |> with_block() + + assert [transaction] == + Transaction.address_to_transactions_with_rewards(address_hash, direction: :to) + |> Repo.preload([:block, :to_address, :from_address]) + end + + test "with to and from transactions and no :direction option" do + %Address{hash: address_hash} = address = insert(:address) + block = insert(:block) + + transaction1 = + :transaction + |> insert(to_address: address) + |> with_block(block) + + transaction2 = + :transaction + |> insert(from_address: address) + |> with_block(block) + + assert [transaction2, transaction1] == + Transaction.address_to_transactions_with_rewards(address_hash) + |> Repo.preload([:block, :to_address, :from_address]) + end + + test "does not include non-contract-creation parent transactions" do + transaction = + %Transaction{} = + :transaction + |> insert() + |> with_block() + + %InternalTransaction{created_contract_address: address} = + insert(:internal_transaction_create, + transaction: transaction, + index: 0, + block_number: transaction.block_number, + block_hash: transaction.block_hash, + block_index: 0, + transaction_index: transaction.index + ) + + assert [] == Transaction.address_to_transactions_with_rewards(address.hash) + end + + test "returns transactions that have token transfers for the given to_address" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(to_address: address, to_address_hash: address.hash) + |> with_block() + + insert( + :token_transfer, + to_address: address, + transaction: transaction + ) + + assert [transaction.hash] == + Transaction.address_to_transactions_with_rewards(address_hash) + |> Enum.map(& &1.hash) + end + + test "with transactions can be paginated" do + %Address{hash: address_hash} = address = insert(:address) + + second_page_hashes = + 2 + |> insert_list(:transaction, from_address: address) + |> with_block() + |> Enum.map(& &1.hash) + + %Transaction{block_number: block_number, index: index} = + :transaction + |> insert(from_address: address) + |> with_block() + + assert second_page_hashes == + address_hash + |> Transaction.address_to_transactions_with_rewards( + paging_options: %PagingOptions{ + key: {block_number, index}, + page_size: 2 + } + ) + |> Enum.map(& &1.hash) + |> Enum.reverse() + end + + test "returns results in reverse chronological order by block number and transaction index" do + %Address{hash: address_hash} = address = insert(:address) + + a_block = insert(:block, number: 6000) + + %Transaction{hash: first} = + :transaction + |> insert(to_address: address) + |> with_block(a_block) + + %Transaction{hash: second} = + :transaction + |> insert(to_address: address) + |> with_block(a_block) + + %Transaction{hash: third} = + :transaction + |> insert(to_address: address) + |> with_block(a_block) + + %Transaction{hash: fourth} = + :transaction + |> insert(to_address: address) + |> with_block(a_block) + + b_block = insert(:block, number: 2000) + + %Transaction{hash: fifth} = + :transaction + |> insert(to_address: address) + |> with_block(b_block) + + %Transaction{hash: sixth} = + :transaction + |> insert(to_address: address) + |> with_block(b_block) + + result = + address_hash + |> Transaction.address_to_transactions_with_rewards() + |> Enum.map(& &1.hash) + + assert [fourth, third, second, first, sixth, fifth] == result + end + + test "with emission rewards" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) + + Application.put_env(:explorer, Explorer.Chain.Block.Reward, + validators_contract_address: "0x0000000000000000000000000000000000000005", + keys_manager_contract_address: "0x0000000000000000000000000000000000000006" + ) + + consumer_pid = start_supervised!(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand) + :erlang.trace(consumer_pid, true, [:receive]) + + block = insert(:block) + + block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower) + block_miner_hash = block.miner_hash + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :validator + ) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :emission_funds + ) + + # isValidator => true + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, + [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]} + end + ) + + # getPayoutByMining => 0x0000000000000000000000000000000000000001 + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]} + end + ) + + res = Transaction.address_to_transactions_with_rewards(block.miner.hash) + assert [{_, _}] = res + + assert_receive {:trace, ^consumer_pid, :receive, {:"$gen_cast", {:fetch_or_update, ^block_miner_hash}}}, 1000 + :timer.sleep(500) + + on_exit(fn -> + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + + Application.put_env(:explorer, Explorer.Chain.Block.Reward, + validators_contract_address: nil, + keys_manager_contract_address: nil + ) + end) + end + + test "with emission rewards and transactions" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) + + Application.put_env(:explorer, Explorer.Chain.Block.Reward, + validators_contract_address: "0x0000000000000000000000000000000000000005", + keys_manager_contract_address: "0x0000000000000000000000000000000000000006" + ) + + consumer_pid = start_supervised!(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand) + :erlang.trace(consumer_pid, true, [:receive]) + + block = insert(:block) + + block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower) + block_miner_hash = block.miner_hash + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :validator + ) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :emission_funds + ) + + :transaction + |> insert(to_address: block.miner) + |> with_block(block) + |> Repo.preload(:token_transfers) + + # isValidator => true + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, + [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]} + end + ) + + # getPayoutByMining => 0x0000000000000000000000000000000000000001 + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]} + end + ) + + assert [_, {_, _}] = Transaction.address_to_transactions_with_rewards(block.miner.hash, direction: :to) + + assert_receive {:trace, ^consumer_pid, :receive, {:"$gen_cast", {:fetch_or_update, ^block_miner_hash}}}, 1000 + :timer.sleep(500) + + on_exit(fn -> + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + + Application.put_env(:explorer, Explorer.Chain.Block.Reward, + validators_contract_address: nil, + keys_manager_contract_address: nil + ) + end) + end + + test "with transactions if rewards are not in the range of blocks" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) + + block = insert(:block) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :validator + ) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :emission_funds + ) + + :transaction + |> insert(from_address: block.miner) + |> with_block() + |> Repo.preload(:token_transfers) + + assert [_] = Transaction.address_to_transactions_with_rewards(block.miner.hash, direction: :from) + + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + end + + test "with emissions rewards, but feature disabled" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + + block = insert(:block) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :validator + ) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :emission_funds + ) + + assert [] == Transaction.address_to_transactions_with_rewards(block.miner.hash) + end + end + + describe "address_to_transactions_tasks_range_of_blocks/2" do + test "returns empty extremums if no transactions" do + address = insert(:address) + + extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => nil, + :max_block_number => 0 + } + end + + test "returns correct extremums for from_address" do + address = insert(:address) + + :transaction + |> insert(from_address: address) + |> with_block(insert(:block, number: 1000)) + + extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => 1000, + :max_block_number => 1000 + } + end + + test "returns correct extremums for to_address" do + address = insert(:address) + + :transaction + |> insert(to_address: address) + |> with_block(insert(:block, number: 1000)) + + extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => 1000, + :max_block_number => 1000 + } + end + + test "returns correct extremums for created_contract_address" do + address = insert(:address) + + :transaction + |> insert(created_contract_address: address) + |> with_block(insert(:block, number: 1000)) + + extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => 1000, + :max_block_number => 1000 + } + end + + test "returns correct extremums for multiple number of transactions" do + address = insert(:address) + + :transaction + |> insert(created_contract_address: address) + |> with_block(insert(:block, number: 1000)) + + :transaction + |> insert(created_contract_address: address) + |> with_block(insert(:block, number: 999)) + + :transaction + |> insert(created_contract_address: address) + |> with_block(insert(:block, number: 1003)) + + :transaction + |> insert(from_address: address) + |> with_block(insert(:block, number: 1001)) + + :transaction + |> insert(from_address: address) + |> with_block(insert(:block, number: 1004)) + + :transaction + |> insert(to_address: address) + |> with_block(insert(:block, number: 1002)) + + :transaction + |> insert(to_address: address) + |> with_block(insert(:block, number: 998)) + + extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => 998, + :max_block_number => 1004 + } + end + end + + describe "address_to_transactions_with_rewards/2" do + test "without transactions" do + %Address{hash: address_hash} = insert(:address) + + assert Repo.aggregate(Transaction, :count, :hash) == 0 + + assert [] == Transaction.address_to_transactions_with_rewards(address_hash) + end + + test "with from transactions" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(from_address: address) + |> with_block() + + assert [transaction] == + Transaction.address_to_transactions_with_rewards(address_hash, direction: :from) + |> Repo.preload([:block, :to_address, :from_address]) + end + + test "with to transactions" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(to_address: address) + |> with_block() + + assert [transaction] == + Transaction.address_to_transactions_with_rewards(address_hash, direction: :to) + |> Repo.preload([:block, :to_address, :from_address]) + end + + test "with to and from transactions and direction: :from" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(from_address: address) + |> with_block() + + # only contains "from" transaction + assert [transaction] == + Transaction.address_to_transactions_with_rewards(address_hash, direction: :from) + |> Repo.preload([:block, :to_address, :from_address]) + end + + test "with to and from transactions and direction: :to" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(to_address: address) + |> with_block() + + assert [transaction] == + Transaction.address_to_transactions_with_rewards(address_hash, direction: :to) + |> Repo.preload([:block, :to_address, :from_address]) + end + + test "with to and from transactions and no :direction option" do + %Address{hash: address_hash} = address = insert(:address) + block = insert(:block) + + transaction1 = + :transaction + |> insert(to_address: address) + |> with_block(block) + + transaction2 = + :transaction + |> insert(from_address: address) + |> with_block(block) + + assert [transaction2, transaction1] == + Transaction.address_to_transactions_with_rewards(address_hash) + |> Repo.preload([:block, :to_address, :from_address]) + end + + test "does not include non-contract-creation parent transactions" do + transaction = + %Transaction{} = + :transaction + |> insert() + |> with_block() + + %InternalTransaction{created_contract_address: address} = + insert(:internal_transaction_create, + transaction: transaction, + index: 0, + block_number: transaction.block_number, + block_hash: transaction.block_hash, + block_index: 0, + transaction_index: transaction.index + ) + + assert [] == Transaction.address_to_transactions_with_rewards(address.hash) + end + + test "returns transactions that have token transfers for the given to_address" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(to_address: address, to_address_hash: address.hash) + |> with_block() + + insert( + :token_transfer, + to_address: address, + transaction: transaction + ) + + assert [transaction.hash] == + Transaction.address_to_transactions_with_rewards(address_hash) + |> Enum.map(& &1.hash) + end + + test "with transactions can be paginated" do + %Address{hash: address_hash} = address = insert(:address) + + second_page_hashes = + 2 + |> insert_list(:transaction, from_address: address) + |> with_block() + |> Enum.map(& &1.hash) + + %Transaction{block_number: block_number, index: index} = + :transaction + |> insert(from_address: address) + |> with_block() + + assert second_page_hashes == + address_hash + |> Transaction.address_to_transactions_with_rewards( + paging_options: %PagingOptions{ + key: {block_number, index}, + page_size: 2 + } + ) + |> Enum.map(& &1.hash) + |> Enum.reverse() + end + + test "returns results in reverse chronological order by block number and transaction index" do + %Address{hash: address_hash} = address = insert(:address) + + a_block = insert(:block, number: 6000) + + %Transaction{hash: first} = + :transaction + |> insert(to_address: address) + |> with_block(a_block) + + %Transaction{hash: second} = + :transaction + |> insert(to_address: address) + |> with_block(a_block) + + %Transaction{hash: third} = + :transaction + |> insert(to_address: address) + |> with_block(a_block) + + %Transaction{hash: fourth} = + :transaction + |> insert(to_address: address) + |> with_block(a_block) + + b_block = insert(:block, number: 2000) + + %Transaction{hash: fifth} = + :transaction + |> insert(to_address: address) + |> with_block(b_block) + + %Transaction{hash: sixth} = + :transaction + |> insert(to_address: address) + |> with_block(b_block) + + result = + address_hash + |> Transaction.address_to_transactions_with_rewards() + |> Enum.map(& &1.hash) + + assert [fourth, third, second, first, sixth, fifth] == result + end + + test "with emission rewards" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) + + Application.put_env(:explorer, Explorer.Chain.Block.Reward, + validators_contract_address: "0x0000000000000000000000000000000000000005", + keys_manager_contract_address: "0x0000000000000000000000000000000000000006" + ) + + consumer_pid = start_supervised!(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand) + :erlang.trace(consumer_pid, true, [:receive]) + + block = insert(:block) + + block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower) + block_miner_hash = block.miner_hash + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :validator + ) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :emission_funds + ) + + # isValidator => true + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, + [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]} + end + ) + + # getPayoutByMining => 0x0000000000000000000000000000000000000001 + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]} + end + ) + + res = Transaction.address_to_transactions_with_rewards(block.miner.hash) + assert [{_, _}] = res + + assert_receive {:trace, ^consumer_pid, :receive, {:"$gen_cast", {:fetch_or_update, ^block_miner_hash}}}, 1000 + :timer.sleep(500) + + on_exit(fn -> + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + + Application.put_env(:explorer, Explorer.Chain.Block.Reward, + validators_contract_address: nil, + keys_manager_contract_address: nil + ) + end) + end + + test "with emission rewards and transactions" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) + + Application.put_env(:explorer, Explorer.Chain.Block.Reward, + validators_contract_address: "0x0000000000000000000000000000000000000005", + keys_manager_contract_address: "0x0000000000000000000000000000000000000006" + ) + + consumer_pid = start_supervised!(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand) + :erlang.trace(consumer_pid, true, [:receive]) + + block = insert(:block) + + block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower) + block_miner_hash = block.miner_hash + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :validator + ) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :emission_funds + ) + + :transaction + |> insert(to_address: block.miner) + |> with_block(block) + |> Repo.preload(:token_transfers) + + # isValidator => true + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, + [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]} + end + ) + + # getPayoutByMining => 0x0000000000000000000000000000000000000001 + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]} + end + ) + + assert [_, {_, _}] = Transaction.address_to_transactions_with_rewards(block.miner.hash, direction: :to) + + assert_receive {:trace, ^consumer_pid, :receive, {:"$gen_cast", {:fetch_or_update, ^block_miner_hash}}}, 1000 + :timer.sleep(500) + + on_exit(fn -> + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + + Application.put_env(:explorer, Explorer.Chain.Block.Reward, + validators_contract_address: nil, + keys_manager_contract_address: nil + ) + end) + end + + test "with transactions if rewards are not in the range of blocks" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) + + block = insert(:block) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :validator + ) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :emission_funds + ) + + :transaction + |> insert(from_address: block.miner) + |> with_block() + |> Repo.preload(:token_transfers) + + assert [_] = Transaction.address_to_transactions_with_rewards(block.miner.hash, direction: :from) + + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + end + + test "with emissions rewards, but feature disabled" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + + block = insert(:block) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :validator + ) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :emission_funds + ) + + assert [] == Transaction.address_to_transactions_with_rewards(block.miner.hash) + end + end + # EIP-1967 + EIP-1822 defp request_zero_implementations do EthereumJSONRPC.Mox diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 96a787543a8c..492861338086 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -371,480 +371,6 @@ defmodule Explorer.ChainTest do end end - describe "address_to_transactions_with_rewards/2" do - test "without transactions" do - %Address{hash: address_hash} = insert(:address) - - assert Repo.aggregate(Transaction, :count, :hash) == 0 - - assert [] == Chain.address_to_transactions_with_rewards(address_hash) - end - - test "with from transactions" do - %Address{hash: address_hash} = address = insert(:address) - - transaction = - :transaction - |> insert(from_address: address) - |> with_block() - - assert [transaction] == - Chain.address_to_transactions_with_rewards(address_hash, direction: :from) - |> Repo.preload([:block, :to_address, :from_address]) - end - - test "with to transactions" do - %Address{hash: address_hash} = address = insert(:address) - - transaction = - :transaction - |> insert(to_address: address) - |> with_block() - - assert [transaction] == - Chain.address_to_transactions_with_rewards(address_hash, direction: :to) - |> Repo.preload([:block, :to_address, :from_address]) - end - - test "with to and from transactions and direction: :from" do - %Address{hash: address_hash} = address = insert(:address) - - transaction = - :transaction - |> insert(from_address: address) - |> with_block() - - # only contains "from" transaction - assert [transaction] == - Chain.address_to_transactions_with_rewards(address_hash, direction: :from) - |> Repo.preload([:block, :to_address, :from_address]) - end - - test "with to and from transactions and direction: :to" do - %Address{hash: address_hash} = address = insert(:address) - - transaction = - :transaction - |> insert(to_address: address) - |> with_block() - - assert [transaction] == - Chain.address_to_transactions_with_rewards(address_hash, direction: :to) - |> Repo.preload([:block, :to_address, :from_address]) - end - - test "with to and from transactions and no :direction option" do - %Address{hash: address_hash} = address = insert(:address) - block = insert(:block) - - transaction1 = - :transaction - |> insert(to_address: address) - |> with_block(block) - - transaction2 = - :transaction - |> insert(from_address: address) - |> with_block(block) - - assert [transaction2, transaction1] == - Chain.address_to_transactions_with_rewards(address_hash) - |> Repo.preload([:block, :to_address, :from_address]) - end - - test "does not include non-contract-creation parent transactions" do - transaction = - %Transaction{} = - :transaction - |> insert() - |> with_block() - - %InternalTransaction{created_contract_address: address} = - insert(:internal_transaction_create, - transaction: transaction, - index: 0, - block_number: transaction.block_number, - block_hash: transaction.block_hash, - block_index: 0, - transaction_index: transaction.index - ) - - assert [] == Chain.address_to_transactions_with_rewards(address.hash) - end - - test "returns transactions that have token transfers for the given to_address" do - %Address{hash: address_hash} = address = insert(:address) - - transaction = - :transaction - |> insert(to_address: address, to_address_hash: address.hash) - |> with_block() - - insert( - :token_transfer, - to_address: address, - transaction: transaction - ) - - assert [transaction.hash] == - Chain.address_to_transactions_with_rewards(address_hash) - |> Enum.map(& &1.hash) - end - - test "with transactions can be paginated" do - %Address{hash: address_hash} = address = insert(:address) - - second_page_hashes = - 2 - |> insert_list(:transaction, from_address: address) - |> with_block() - |> Enum.map(& &1.hash) - - %Transaction{block_number: block_number, index: index} = - :transaction - |> insert(from_address: address) - |> with_block() - - assert second_page_hashes == - address_hash - |> Chain.address_to_transactions_with_rewards( - paging_options: %PagingOptions{ - key: {block_number, index}, - page_size: 2 - } - ) - |> Enum.map(& &1.hash) - |> Enum.reverse() - end - - test "returns results in reverse chronological order by block number and transaction index" do - %Address{hash: address_hash} = address = insert(:address) - - a_block = insert(:block, number: 6000) - - %Transaction{hash: first} = - :transaction - |> insert(to_address: address) - |> with_block(a_block) - - %Transaction{hash: second} = - :transaction - |> insert(to_address: address) - |> with_block(a_block) - - %Transaction{hash: third} = - :transaction - |> insert(to_address: address) - |> with_block(a_block) - - %Transaction{hash: fourth} = - :transaction - |> insert(to_address: address) - |> with_block(a_block) - - b_block = insert(:block, number: 2000) - - %Transaction{hash: fifth} = - :transaction - |> insert(to_address: address) - |> with_block(b_block) - - %Transaction{hash: sixth} = - :transaction - |> insert(to_address: address) - |> with_block(b_block) - - result = - address_hash - |> Chain.address_to_transactions_with_rewards() - |> Enum.map(& &1.hash) - - assert [fourth, third, second, first, sixth, fifth] == result - end - - test "with emission rewards" do - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) - - Application.put_env(:explorer, Explorer.Chain.Block.Reward, - validators_contract_address: "0x0000000000000000000000000000000000000005", - keys_manager_contract_address: "0x0000000000000000000000000000000000000006" - ) - - consumer_pid = start_supervised!(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand) - :erlang.trace(consumer_pid, true, [:receive]) - - block = insert(:block) - - block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower) - block_miner_hash = block.miner_hash - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :validator - ) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :emission_funds - ) - - # isValidator => true - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> - {:ok, - [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]} - end - ) - - # getPayoutByMining => 0x0000000000000000000000000000000000000001 - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> - {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]} - end - ) - - res = Chain.address_to_transactions_with_rewards(block.miner.hash) - assert [{_, _}] = res - - assert_receive {:trace, ^consumer_pid, :receive, {:"$gen_cast", {:fetch_or_update, ^block_miner_hash}}}, 1000 - :timer.sleep(500) - - on_exit(fn -> - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) - - Application.put_env(:explorer, Explorer.Chain.Block.Reward, - validators_contract_address: nil, - keys_manager_contract_address: nil - ) - end) - end - - test "with emission rewards and transactions" do - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) - - Application.put_env(:explorer, Explorer.Chain.Block.Reward, - validators_contract_address: "0x0000000000000000000000000000000000000005", - keys_manager_contract_address: "0x0000000000000000000000000000000000000006" - ) - - consumer_pid = start_supervised!(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand) - :erlang.trace(consumer_pid, true, [:receive]) - - block = insert(:block) - - block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower) - block_miner_hash = block.miner_hash - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :validator - ) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :emission_funds - ) - - :transaction - |> insert(to_address: block.miner) - |> with_block(block) - |> Repo.preload(:token_transfers) - - # isValidator => true - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> - {:ok, - [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]} - end - ) - - # getPayoutByMining => 0x0000000000000000000000000000000000000001 - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> - {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]} - end - ) - - assert [_, {_, _}] = Chain.address_to_transactions_with_rewards(block.miner.hash, direction: :to) - - assert_receive {:trace, ^consumer_pid, :receive, {:"$gen_cast", {:fetch_or_update, ^block_miner_hash}}}, 1000 - :timer.sleep(500) - - on_exit(fn -> - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) - - Application.put_env(:explorer, Explorer.Chain.Block.Reward, - validators_contract_address: nil, - keys_manager_contract_address: nil - ) - end) - end - - test "with transactions if rewards are not in the range of blocks" do - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) - - block = insert(:block) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :validator - ) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :emission_funds - ) - - :transaction - |> insert(from_address: block.miner) - |> with_block() - |> Repo.preload(:token_transfers) - - assert [_] = Chain.address_to_transactions_with_rewards(block.miner.hash, direction: :from) - - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) - end - - test "with emissions rewards, but feature disabled" do - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) - - block = insert(:block) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :validator - ) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :emission_funds - ) - - assert [] == Chain.address_to_transactions_with_rewards(block.miner.hash) - end - end - - describe "address_to_transactions_tasks_range_of_blocks/2" do - test "returns empty extremums if no transactions" do - address = insert(:address) - - extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, []) - - assert extremums == %{ - :min_block_number => nil, - :max_block_number => 0 - } - end - - test "returns correct extremums for from_address" do - address = insert(:address) - - :transaction - |> insert(from_address: address) - |> with_block(insert(:block, number: 1000)) - - extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, []) - - assert extremums == %{ - :min_block_number => 1000, - :max_block_number => 1000 - } - end - - test "returns correct extremums for to_address" do - address = insert(:address) - - :transaction - |> insert(to_address: address) - |> with_block(insert(:block, number: 1000)) - - extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, []) - - assert extremums == %{ - :min_block_number => 1000, - :max_block_number => 1000 - } - end - - test "returns correct extremums for created_contract_address" do - address = insert(:address) - - :transaction - |> insert(created_contract_address: address) - |> with_block(insert(:block, number: 1000)) - - extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, []) - - assert extremums == %{ - :min_block_number => 1000, - :max_block_number => 1000 - } - end - - test "returns correct extremums for multiple number of transactions" do - address = insert(:address) - - :transaction - |> insert(created_contract_address: address) - |> with_block(insert(:block, number: 1000)) - - :transaction - |> insert(created_contract_address: address) - |> with_block(insert(:block, number: 999)) - - :transaction - |> insert(created_contract_address: address) - |> with_block(insert(:block, number: 1003)) - - :transaction - |> insert(from_address: address) - |> with_block(insert(:block, number: 1001)) - - :transaction - |> insert(from_address: address) - |> with_block(insert(:block, number: 1004)) - - :transaction - |> insert(to_address: address) - |> with_block(insert(:block, number: 1002)) - - :transaction - |> insert(to_address: address) - |> with_block(insert(:block, number: 998)) - - extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, []) - - assert extremums == %{ - :min_block_number => 998, - :max_block_number => 1004 - } - end - end - describe "total_transactions_sent_by_address/1" do test "increments +1 in the last nonce result" do address = insert(:address) @@ -5393,64 +4919,4 @@ defmodule Explorer.ChainTest do assert Chain.transaction_to_revert_reason(transaction) == "No credit of that type" end end - - describe "verified_contracts/2" do - test "without contracts" do - assert [] = Chain.verified_contracts() - end - - test "with contracts" do - %SmartContract{address_hash: hash} = insert(:smart_contract) - - assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts() - end - - test "with contracts can be paginated" do - second_page_contracts_ids = - 50 - |> insert_list(:smart_contract) - |> Enum.map(& &1.id) - - contract = insert(:smart_contract) - - assert second_page_contracts_ids == - [paging_options: %PagingOptions{key: {contract.id}, page_size: 50}] - |> Chain.verified_contracts() - |> Enum.map(& &1.id) - |> Enum.reverse() - end - - test "filters solidity" do - insert(:smart_contract, is_vyper_contract: true) - %SmartContract{address_hash: hash} = insert(:smart_contract, is_vyper_contract: false) - - assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(filter: :solidity) - end - - test "filters vyper" do - insert(:smart_contract, is_vyper_contract: false) - %SmartContract{address_hash: hash} = insert(:smart_contract, is_vyper_contract: true) - - assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(filter: :vyper) - end - - test "search by address" do - insert(:smart_contract) - insert(:smart_contract) - insert(:smart_contract) - %SmartContract{address_hash: hash} = insert(:smart_contract) - - assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(search: Hash.to_string(hash)) - end - - test "search by name" do - insert(:smart_contract) - insert(:smart_contract) - insert(:smart_contract) - contract_name = "qwertyufhgkhiop" - %SmartContract{address_hash: hash} = insert(:smart_contract, name: contract_name) - - assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(search: contract_name) - end - end end diff --git a/cspell.json b/cspell.json index 95077caf8ceb..b76de4bdc930 100644 --- a/cspell.json +++ b/cspell.json @@ -118,6 +118,7 @@ "decompiler", "Decompiler", "dedup", + "DefiLlama", "defmock", "defsupervisor", "dejob", @@ -232,6 +233,7 @@ "kittencream", "labeledby", "labelledby", + "lastmod", "lastname", "lastword", "lformat", @@ -256,6 +258,7 @@ "mconst", "mdef", "MDWW", + "meer", "Mendonça", "Menlo", "mergeable", @@ -316,6 +319,7 @@ "onconnect", "ondisconnect", "outcoming", + "overengineering", "pawesome", "pbcopy", "peeker", @@ -351,6 +355,7 @@ "purrstige", "qdai", "Qebz", + "qitmeer", "Qmbgk", "qrcode", "queriable", @@ -490,6 +495,7 @@ "upserting", "upserts", "urijs", + "urlset", "Utqn", "UUPS", "valign", @@ -528,17 +534,7 @@ "zindex", "zipcode", "zkbob", - "zkevm", - "erts", - "Asfpp", - "Nerg", - "secp", - "qwertyuioiuytrewertyuioiuytrertyuio", - "urlset", - "lastmod", - "qitmeer", - "meer", - "DefiLlama" + "zkevm" ], "enableFiletypes": [ "dotenv", From afb6ef3f85afdbe1717710b862838a9672ed91d5 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Mon, 4 Dec 2023 14:32:59 +0300 Subject: [PATCH 095/607] Take into account possible nil fee --- .../controllers/api/v2/address_controller.ex | 3 +- .../lib/block_scout_web/paging_helper.ex | 7 +- apps/explorer/lib/explorer/chain.ex | 79 ------------------- .../lib/explorer/chain/smart_contract.ex | 2 - .../lib/explorer/chain/transaction.ex | 15 +++- 5 files changed, 18 insertions(+), 88 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex index b6d875c7d91c..0c77e9ace6b0 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -23,10 +23,9 @@ defmodule BlockScoutWeb.API.V2.AddressController do alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.API.V2.{BlockView, TransactionView, WithdrawalView} alias Explorer.{Chain, Market} - alias Explorer.Chain.Address + alias Explorer.Chain.{Address, Transaction} alias Explorer.Chain.Address.Counters alias Explorer.Chain.Token.Instance - alias Explorer.Chain.Transaction alias Indexer.Fetcher.{CoinBalanceOnDemand, TokenBalanceOnDemand} @transaction_necessity_by_association [ diff --git a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex index 8c2d2b0cca73..406f47b6c761 100644 --- a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex @@ -226,7 +226,10 @@ defmodule BlockScoutWeb.PagingHelper do defp do_address_transaction_sorting("value", "asc"), do: [asc: :value] defp do_address_transaction_sorting("value", "desc"), do: [desc: :value] - defp do_address_transaction_sorting("fee", "asc"), do: [{:dynamic, :fee, :asc, Transaction.dynamic_fee()}] - defp do_address_transaction_sorting("fee", "desc"), do: [{:dynamic, :fee, :desc, Transaction.dynamic_fee()}] + defp do_address_transaction_sorting("fee", "asc"), do: [{:dynamic, :fee, :asc_nulls_first, Transaction.dynamic_fee()}] + + defp do_address_transaction_sorting("fee", "desc"), + do: [{:dynamic, :fee, :desc_nulls_last, Transaction.dynamic_fee()}] + defp do_address_transaction_sorting(_, _), do: [] end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 5fed39e9b6b9..6d3f48b91b41 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1780,63 +1780,6 @@ defmodule Explorer.Chain do |> Enum.into(%{}) end - @doc """ - Lists the top `t:Explorer.Chain.Address.t/0`'s' in descending order based on coin balance and address hash. - """ - @spec list_top_addresses :: [{Address.t(), non_neg_integer()}] - def list_top_addresses(options \\ []) do - paging_options = Keyword.get(options, :paging_options, @default_paging_options) - - if is_nil(paging_options.key) do - paging_options.page_size - |> Accounts.take_enough() - |> case do - nil -> - get_addresses(options) - - accounts -> - Enum.map( - accounts, - &{&1, - if is_nil(&1.nonce) do - 0 - else - &1.nonce + 1 - end} - ) - end - else - fetch_top_addresses(options) - end - end - - defp get_addresses(options) do - accounts_with_n = fetch_top_addresses(options) - - accounts_with_n - |> Enum.map(fn {address, _n} -> address end) - |> Accounts.update() - - accounts_with_n - end - - defp fetch_top_addresses(options) do - paging_options = Keyword.get(options, :paging_options, @default_paging_options) - - base_query = - from(a in Address, - where: a.fetched_coin_balance > ^0, - order_by: [desc: a.fetched_coin_balance, asc: a.hash], - preload: [:names, :smart_contract], - select: {a, fragment("coalesce(1 + ?, 0)", a.nonce)} - ) - - base_query - |> page_addresses(paging_options) - |> limit(^paging_options.page_size) - |> select_repo(options).all() - end - @doc """ Calls `reducer` on a stream of `t:Explorer.Chain.Block.t/0` without `t:Explorer.Chain.Block.Reward.t/0`. """ @@ -3279,28 +3222,6 @@ defmodule Explorer.Chain do end end - defp fetch_transactions(paging_options \\ nil, from_block \\ nil, to_block \\ nil, with_pending? \\ false) do - Transaction - |> order_for_transactions(with_pending?) - |> where_block_number_in_period(from_block, to_block) - |> handle_paging_options(paging_options) - end - - defp order_for_transactions(query, true) do - query - |> order_by([transaction], - desc: transaction.block_number, - desc: transaction.index, - desc: transaction.inserted_at, - asc: transaction.hash - ) - end - - defp order_for_transactions(query, _) do - query - |> order_by([transaction], desc: transaction.block_number, desc: transaction.index) - end - defp fetch_transactions_in_ascending_order_by_index(paging_options) do Transaction |> order_by([transaction], asc: transaction.index) diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index 89b3a27e919a..1d381ef92ec0 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -55,8 +55,6 @@ defmodule Explorer.Chain.SmartContract do @default_sorting [desc: :id] - @typep api? :: {:api?, true | false} - @typedoc """ The name of a parameter to a function or event. """ diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 0ed5b547fc5b..78ef9a202564 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -1448,10 +1448,19 @@ defmodule Explorer.Chain.Transaction do defp compare_custom_sorting([{:dynamic, :fee, order, _dynamic_fee}]) do fn a, b -> - case Decimal.compare(a |> Chain.fee(:wei) |> elem(1), b |> Chain.fee(:wei) |> elem(1)) do + nil_case = + case order do + :desc_nulls_last -> Decimal.new("-inf") + :asc_nulls_first -> Decimal.new("inf") + end + + a_fee = a |> Chain.fee(:wei) |> elem(1) || nil_case + b_fee = b |> Chain.fee(:wei) |> elem(1) || nil_case + + case Decimal.compare(a_fee, b_fee) do :eq -> compare_default_sorting(a, b) - :gt -> order == :desc - :lt -> order == :asc + :gt -> order == :desc_nulls_last + :lt -> order == :asc_nulls_first end end end From 37bebdd0f056817cd5ea0f4416b69c0220aec656 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Mon, 4 Dec 2023 16:38:38 +0300 Subject: [PATCH 096/607] Fix CI --- .dialyzer-ignore | 2 + CHANGELOG.md | 2 +- .../test/explorer/chain/transaction_test.exs | 474 ------------------ 3 files changed, 3 insertions(+), 475 deletions(-) diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 7ee843f9190e..44a7f6862aba 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -26,3 +26,5 @@ 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:1457 +lib/explorer/chain/transaction.ex:1458 diff --git a/CHANGELOG.md b/CHANGELOG.md index a19a159835d3..6473289e8db3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Features - [#8900](https://github.com/blockscout/blockscout/pull/8900) - Add Compound proxy contract pattern +- [#8611](https://github.com/blockscout/blockscout/pull/8611) - Implement sorting of smart contracts, address transactions ### Fixes @@ -29,7 +30,6 @@ - [#8768](https://github.com/blockscout/blockscout/pull/8768) - Add possibility to search tokens by address hash - [#8750](https://github.com/blockscout/blockscout/pull/8750) - Support new eth-bytecode-db request metadata fields - [#8634](https://github.com/blockscout/blockscout/pull/8634) - API v2: NFT for address -- [#8611](https://github.com/blockscout/blockscout/pull/8611) - Implement sorting of smart contracts, address transactions - [#8609](https://github.com/blockscout/blockscout/pull/8609) - Change logs format to JSON; Add endpoint url to the block_scout_web logging - [#8558](https://github.com/blockscout/blockscout/pull/8558) - Add CoinBalanceDailyUpdater diff --git a/apps/explorer/test/explorer/chain/transaction_test.exs b/apps/explorer/test/explorer/chain/transaction_test.exs index 6656cf4277ba..7a5ce5bbe51a 100644 --- a/apps/explorer/test/explorer/chain/transaction_test.exs +++ b/apps/explorer/test/explorer/chain/transaction_test.exs @@ -788,480 +788,6 @@ defmodule Explorer.Chain.TransactionTest do end end - describe "address_to_transactions_tasks_range_of_blocks/2" do - test "returns empty extremums if no transactions" do - address = insert(:address) - - extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) - - assert extremums == %{ - :min_block_number => nil, - :max_block_number => 0 - } - end - - test "returns correct extremums for from_address" do - address = insert(:address) - - :transaction - |> insert(from_address: address) - |> with_block(insert(:block, number: 1000)) - - extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) - - assert extremums == %{ - :min_block_number => 1000, - :max_block_number => 1000 - } - end - - test "returns correct extremums for to_address" do - address = insert(:address) - - :transaction - |> insert(to_address: address) - |> with_block(insert(:block, number: 1000)) - - extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) - - assert extremums == %{ - :min_block_number => 1000, - :max_block_number => 1000 - } - end - - test "returns correct extremums for created_contract_address" do - address = insert(:address) - - :transaction - |> insert(created_contract_address: address) - |> with_block(insert(:block, number: 1000)) - - extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) - - assert extremums == %{ - :min_block_number => 1000, - :max_block_number => 1000 - } - end - - test "returns correct extremums for multiple number of transactions" do - address = insert(:address) - - :transaction - |> insert(created_contract_address: address) - |> with_block(insert(:block, number: 1000)) - - :transaction - |> insert(created_contract_address: address) - |> with_block(insert(:block, number: 999)) - - :transaction - |> insert(created_contract_address: address) - |> with_block(insert(:block, number: 1003)) - - :transaction - |> insert(from_address: address) - |> with_block(insert(:block, number: 1001)) - - :transaction - |> insert(from_address: address) - |> with_block(insert(:block, number: 1004)) - - :transaction - |> insert(to_address: address) - |> with_block(insert(:block, number: 1002)) - - :transaction - |> insert(to_address: address) - |> with_block(insert(:block, number: 998)) - - extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) - - assert extremums == %{ - :min_block_number => 998, - :max_block_number => 1004 - } - end - end - - describe "address_to_transactions_with_rewards/2" do - test "without transactions" do - %Address{hash: address_hash} = insert(:address) - - assert Repo.aggregate(Transaction, :count, :hash) == 0 - - assert [] == Transaction.address_to_transactions_with_rewards(address_hash) - end - - test "with from transactions" do - %Address{hash: address_hash} = address = insert(:address) - - transaction = - :transaction - |> insert(from_address: address) - |> with_block() - - assert [transaction] == - Transaction.address_to_transactions_with_rewards(address_hash, direction: :from) - |> Repo.preload([:block, :to_address, :from_address]) - end - - test "with to transactions" do - %Address{hash: address_hash} = address = insert(:address) - - transaction = - :transaction - |> insert(to_address: address) - |> with_block() - - assert [transaction] == - Transaction.address_to_transactions_with_rewards(address_hash, direction: :to) - |> Repo.preload([:block, :to_address, :from_address]) - end - - test "with to and from transactions and direction: :from" do - %Address{hash: address_hash} = address = insert(:address) - - transaction = - :transaction - |> insert(from_address: address) - |> with_block() - - # only contains "from" transaction - assert [transaction] == - Transaction.address_to_transactions_with_rewards(address_hash, direction: :from) - |> Repo.preload([:block, :to_address, :from_address]) - end - - test "with to and from transactions and direction: :to" do - %Address{hash: address_hash} = address = insert(:address) - - transaction = - :transaction - |> insert(to_address: address) - |> with_block() - - assert [transaction] == - Transaction.address_to_transactions_with_rewards(address_hash, direction: :to) - |> Repo.preload([:block, :to_address, :from_address]) - end - - test "with to and from transactions and no :direction option" do - %Address{hash: address_hash} = address = insert(:address) - block = insert(:block) - - transaction1 = - :transaction - |> insert(to_address: address) - |> with_block(block) - - transaction2 = - :transaction - |> insert(from_address: address) - |> with_block(block) - - assert [transaction2, transaction1] == - Transaction.address_to_transactions_with_rewards(address_hash) - |> Repo.preload([:block, :to_address, :from_address]) - end - - test "does not include non-contract-creation parent transactions" do - transaction = - %Transaction{} = - :transaction - |> insert() - |> with_block() - - %InternalTransaction{created_contract_address: address} = - insert(:internal_transaction_create, - transaction: transaction, - index: 0, - block_number: transaction.block_number, - block_hash: transaction.block_hash, - block_index: 0, - transaction_index: transaction.index - ) - - assert [] == Transaction.address_to_transactions_with_rewards(address.hash) - end - - test "returns transactions that have token transfers for the given to_address" do - %Address{hash: address_hash} = address = insert(:address) - - transaction = - :transaction - |> insert(to_address: address, to_address_hash: address.hash) - |> with_block() - - insert( - :token_transfer, - to_address: address, - transaction: transaction - ) - - assert [transaction.hash] == - Transaction.address_to_transactions_with_rewards(address_hash) - |> Enum.map(& &1.hash) - end - - test "with transactions can be paginated" do - %Address{hash: address_hash} = address = insert(:address) - - second_page_hashes = - 2 - |> insert_list(:transaction, from_address: address) - |> with_block() - |> Enum.map(& &1.hash) - - %Transaction{block_number: block_number, index: index} = - :transaction - |> insert(from_address: address) - |> with_block() - - assert second_page_hashes == - address_hash - |> Transaction.address_to_transactions_with_rewards( - paging_options: %PagingOptions{ - key: {block_number, index}, - page_size: 2 - } - ) - |> Enum.map(& &1.hash) - |> Enum.reverse() - end - - test "returns results in reverse chronological order by block number and transaction index" do - %Address{hash: address_hash} = address = insert(:address) - - a_block = insert(:block, number: 6000) - - %Transaction{hash: first} = - :transaction - |> insert(to_address: address) - |> with_block(a_block) - - %Transaction{hash: second} = - :transaction - |> insert(to_address: address) - |> with_block(a_block) - - %Transaction{hash: third} = - :transaction - |> insert(to_address: address) - |> with_block(a_block) - - %Transaction{hash: fourth} = - :transaction - |> insert(to_address: address) - |> with_block(a_block) - - b_block = insert(:block, number: 2000) - - %Transaction{hash: fifth} = - :transaction - |> insert(to_address: address) - |> with_block(b_block) - - %Transaction{hash: sixth} = - :transaction - |> insert(to_address: address) - |> with_block(b_block) - - result = - address_hash - |> Transaction.address_to_transactions_with_rewards() - |> Enum.map(& &1.hash) - - assert [fourth, third, second, first, sixth, fifth] == result - end - - test "with emission rewards" do - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) - - Application.put_env(:explorer, Explorer.Chain.Block.Reward, - validators_contract_address: "0x0000000000000000000000000000000000000005", - keys_manager_contract_address: "0x0000000000000000000000000000000000000006" - ) - - consumer_pid = start_supervised!(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand) - :erlang.trace(consumer_pid, true, [:receive]) - - block = insert(:block) - - block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower) - block_miner_hash = block.miner_hash - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :validator - ) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :emission_funds - ) - - # isValidator => true - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> - {:ok, - [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]} - end - ) - - # getPayoutByMining => 0x0000000000000000000000000000000000000001 - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> - {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]} - end - ) - - res = Transaction.address_to_transactions_with_rewards(block.miner.hash) - assert [{_, _}] = res - - assert_receive {:trace, ^consumer_pid, :receive, {:"$gen_cast", {:fetch_or_update, ^block_miner_hash}}}, 1000 - :timer.sleep(500) - - on_exit(fn -> - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) - - Application.put_env(:explorer, Explorer.Chain.Block.Reward, - validators_contract_address: nil, - keys_manager_contract_address: nil - ) - end) - end - - test "with emission rewards and transactions" do - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) - - Application.put_env(:explorer, Explorer.Chain.Block.Reward, - validators_contract_address: "0x0000000000000000000000000000000000000005", - keys_manager_contract_address: "0x0000000000000000000000000000000000000006" - ) - - consumer_pid = start_supervised!(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand) - :erlang.trace(consumer_pid, true, [:receive]) - - block = insert(:block) - - block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower) - block_miner_hash = block.miner_hash - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :validator - ) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :emission_funds - ) - - :transaction - |> insert(to_address: block.miner) - |> with_block(block) - |> Repo.preload(:token_transfers) - - # isValidator => true - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> - {:ok, - [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]} - end - ) - - # getPayoutByMining => 0x0000000000000000000000000000000000000001 - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> - {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]} - end - ) - - assert [_, {_, _}] = Transaction.address_to_transactions_with_rewards(block.miner.hash, direction: :to) - - assert_receive {:trace, ^consumer_pid, :receive, {:"$gen_cast", {:fetch_or_update, ^block_miner_hash}}}, 1000 - :timer.sleep(500) - - on_exit(fn -> - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) - - Application.put_env(:explorer, Explorer.Chain.Block.Reward, - validators_contract_address: nil, - keys_manager_contract_address: nil - ) - end) - end - - test "with transactions if rewards are not in the range of blocks" do - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) - - block = insert(:block) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :validator - ) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :emission_funds - ) - - :transaction - |> insert(from_address: block.miner) - |> with_block() - |> Repo.preload(:token_transfers) - - assert [_] = Transaction.address_to_transactions_with_rewards(block.miner.hash, direction: :from) - - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) - end - - test "with emissions rewards, but feature disabled" do - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) - - block = insert(:block) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :validator - ) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :emission_funds - ) - - assert [] == Transaction.address_to_transactions_with_rewards(block.miner.hash) - end - end - # EIP-1967 + EIP-1822 defp request_zero_implementations do EthereumJSONRPC.Mox From 700e7810ea1ecd1913343f1ce590c95246ae5a5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 18:07:59 +0000 Subject: [PATCH 097/607] Bump postcss from 8.4.31 to 8.4.32 in /apps/block_scout_web/assets Bumps [postcss](https://github.com/postcss/postcss) from 8.4.31 to 8.4.32. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.31...8.4.32) --- updated-dependencies: - dependency-name: postcss dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 30 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index f0b0d1b85c88..141aa40e70d9 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -87,7 +87,7 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "mini-css-extract-plugin": "^2.7.6", - "postcss": "^8.4.31", + "postcss": "^8.4.32", "postcss-loader": "^7.3.3", "sass": "^1.69.5", "sass-loader": "^13.3.2", @@ -12983,9 +12983,9 @@ "integrity": "sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40=" }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, "funding": [ { @@ -13734,9 +13734,9 @@ } }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", + "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", "dev": true, "funding": [ { @@ -13753,7 +13753,7 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -27673,9 +27673,9 @@ "integrity": "sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40=" }, "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true }, "nanomorph": { @@ -28210,12 +28210,12 @@ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" }, "postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", + "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", "dev": true, "requires": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 8dc4f2ca26ab..d03b0e215efe 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -99,7 +99,7 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "mini-css-extract-plugin": "^2.7.6", - "postcss": "^8.4.31", + "postcss": "^8.4.32", "postcss-loader": "^7.3.3", "sass": "^1.69.5", "sass-loader": "^13.3.2", From 1382b28a460958b3028946c5b47af3f57ba15afa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 18:09:38 +0000 Subject: [PATCH 098/607] Bump @amplitude/analytics-browser in /apps/block_scout_web/assets Bumps [@amplitude/analytics-browser](https://github.com/amplitude/Amplitude-TypeScript) from 2.3.5 to 2.3.6. - [Release notes](https://github.com/amplitude/Amplitude-TypeScript/releases) - [Commits](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@2.3.5...@amplitude/analytics-browser@2.3.6) --- updated-dependencies: - dependency-name: "@amplitude/analytics-browser" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 94 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index f0b0d1b85c88..6dec5adf6466 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -7,7 +7,7 @@ "name": "blockscout", "license": "GPL-3.0", "dependencies": { - "@amplitude/analytics-browser": "^2.3.5", + "@amplitude/analytics-browser": "^2.3.6", "@fortawesome/fontawesome-free": "^6.4.2", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", @@ -116,15 +116,15 @@ } }, "node_modules/@amplitude/analytics-browser": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.5.tgz", - "integrity": "sha512-KL9Yv0lXvCsWrCwwWAMB0kzTswBlTLxxyOAS//z0378ckQvszLYvQqje3K5t0AlXrG728cLvKcBFveZ/UgUWfg==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.6.tgz", + "integrity": "sha512-P8N19qeoSDPO0crdQwa2yi+T0/UT4v9FqZrtXF3JW4kgxu++dTW/QkQ6bsv29EPg2N54xyr9IOt98q9EkZbA7A==", "dependencies": { - "@amplitude/analytics-client-common": "^2.0.8", - "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-client-common": "^2.0.9", + "@amplitude/analytics-core": "^2.1.2", "@amplitude/analytics-types": "^2.3.1", - "@amplitude/plugin-page-view-tracking-browser": "^2.0.15", - "@amplitude/plugin-web-attribution-browser": "^2.0.15", + "@amplitude/plugin-page-view-tracking-browser": "^2.0.16", + "@amplitude/plugin-web-attribution-browser": "^2.0.16", "tslib": "^2.4.1" } }, @@ -134,12 +134,12 @@ "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" }, "node_modules/@amplitude/analytics-client-common": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.8.tgz", - "integrity": "sha512-zKD/txmMFPfSVtT2gdZw+Tf07pZQEcPcB6X39+a+Wh8PjIIADYIeq6zL/2pn/9uwMkVz66sbKABKbq69XxPfCA==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.9.tgz", + "integrity": "sha512-2CSUMY60Jemks/XVro8ikuHBebENzb+IxnUz4YPx5fmBPW7QW+ysmD+LgECeLhv9jWIQoDhuciscPLuRENKerA==", "dependencies": { "@amplitude/analytics-connector": "^1.4.8", - "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-core": "^2.1.2", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" } @@ -155,9 +155,9 @@ "integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g==" }, "node_modules/@amplitude/analytics-core": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.1.tgz", - "integrity": "sha512-2dHHiOnK7J/0Uk3gqu70JEvCKSgNBAXIUvw6u7bEHCQHBBW3ulpsVRSQomBeruyBBLKjgarwgawGs3yJrjIDkA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.2.tgz", + "integrity": "sha512-vRkP2HHh43u7vilpUrP3Q8o/TMJ6U2B7nxPV9/aUS5V/im0Zigq4mPcn5uNY7FuTwsJM3zzRxK9J2WvfdG5bqA==", "dependencies": { "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" @@ -174,11 +174,11 @@ "integrity": "sha512-yojBG20qvph0rpCJKb4i/FJa+otqLINEwv//hfzvjnCOcPPyS0YscI8oiRBM0rG7kZIDgaL9a6jPwkqK4ACmcw==" }, "node_modules/@amplitude/plugin-page-view-tracking-browser": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.15.tgz", - "integrity": "sha512-iVviovZWROodoNs984dAslm3vCkMsl6bhIq5K0Tabt4ffi4ygIqlhdV8vj4Grr8u6mGtjgEzFchCkxdzb9TU1A==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.16.tgz", + "integrity": "sha512-o9YlpXm0BcnUEK47K5xAqEa/q7DOsKGb6F35gF5iGUFXTtBP+QVLTtNujfnfiO4V9g5JrkOhB/Wm2Sr34VNFhQ==", "dependencies": { - "@amplitude/analytics-client-common": "^2.0.8", + "@amplitude/analytics-client-common": "^2.0.9", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" } @@ -189,12 +189,12 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@amplitude/plugin-web-attribution-browser": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.15.tgz", - "integrity": "sha512-HSS6j2a40iSIKwug7ICzezXl6ag+cj7YU7cFbYiSF+cmNIVg4jPYgVCbTqq+IivOw+VW07pr0o+jz0ArL/Lyiw==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.16.tgz", + "integrity": "sha512-IEYDrtZGJvBRhie7u9xIWNZhd0Z7heRx6A/+dNdERrW/akC1iAVmUyBZPMOR5ywVK7nwsymlVpFbPccuMoYTmg==", "dependencies": { - "@amplitude/analytics-client-common": "^2.0.8", - "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-client-common": "^2.0.9", + "@amplitude/analytics-core": "^2.1.2", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" } @@ -17854,15 +17854,15 @@ "dev": true }, "@amplitude/analytics-browser": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.5.tgz", - "integrity": "sha512-KL9Yv0lXvCsWrCwwWAMB0kzTswBlTLxxyOAS//z0378ckQvszLYvQqje3K5t0AlXrG728cLvKcBFveZ/UgUWfg==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.6.tgz", + "integrity": "sha512-P8N19qeoSDPO0crdQwa2yi+T0/UT4v9FqZrtXF3JW4kgxu++dTW/QkQ6bsv29EPg2N54xyr9IOt98q9EkZbA7A==", "requires": { - "@amplitude/analytics-client-common": "^2.0.8", - "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-client-common": "^2.0.9", + "@amplitude/analytics-core": "^2.1.2", "@amplitude/analytics-types": "^2.3.1", - "@amplitude/plugin-page-view-tracking-browser": "^2.0.15", - "@amplitude/plugin-web-attribution-browser": "^2.0.15", + "@amplitude/plugin-page-view-tracking-browser": "^2.0.16", + "@amplitude/plugin-web-attribution-browser": "^2.0.16", "tslib": "^2.4.1" }, "dependencies": { @@ -17874,12 +17874,12 @@ } }, "@amplitude/analytics-client-common": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.8.tgz", - "integrity": "sha512-zKD/txmMFPfSVtT2gdZw+Tf07pZQEcPcB6X39+a+Wh8PjIIADYIeq6zL/2pn/9uwMkVz66sbKABKbq69XxPfCA==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.9.tgz", + "integrity": "sha512-2CSUMY60Jemks/XVro8ikuHBebENzb+IxnUz4YPx5fmBPW7QW+ysmD+LgECeLhv9jWIQoDhuciscPLuRENKerA==", "requires": { "@amplitude/analytics-connector": "^1.4.8", - "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-core": "^2.1.2", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" }, @@ -17897,9 +17897,9 @@ "integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g==" }, "@amplitude/analytics-core": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.1.tgz", - "integrity": "sha512-2dHHiOnK7J/0Uk3gqu70JEvCKSgNBAXIUvw6u7bEHCQHBBW3ulpsVRSQomBeruyBBLKjgarwgawGs3yJrjIDkA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.2.tgz", + "integrity": "sha512-vRkP2HHh43u7vilpUrP3Q8o/TMJ6U2B7nxPV9/aUS5V/im0Zigq4mPcn5uNY7FuTwsJM3zzRxK9J2WvfdG5bqA==", "requires": { "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" @@ -17918,11 +17918,11 @@ "integrity": "sha512-yojBG20qvph0rpCJKb4i/FJa+otqLINEwv//hfzvjnCOcPPyS0YscI8oiRBM0rG7kZIDgaL9a6jPwkqK4ACmcw==" }, "@amplitude/plugin-page-view-tracking-browser": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.15.tgz", - "integrity": "sha512-iVviovZWROodoNs984dAslm3vCkMsl6bhIq5K0Tabt4ffi4ygIqlhdV8vj4Grr8u6mGtjgEzFchCkxdzb9TU1A==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.16.tgz", + "integrity": "sha512-o9YlpXm0BcnUEK47K5xAqEa/q7DOsKGb6F35gF5iGUFXTtBP+QVLTtNujfnfiO4V9g5JrkOhB/Wm2Sr34VNFhQ==", "requires": { - "@amplitude/analytics-client-common": "^2.0.8", + "@amplitude/analytics-client-common": "^2.0.9", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" }, @@ -17935,12 +17935,12 @@ } }, "@amplitude/plugin-web-attribution-browser": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.15.tgz", - "integrity": "sha512-HSS6j2a40iSIKwug7ICzezXl6ag+cj7YU7cFbYiSF+cmNIVg4jPYgVCbTqq+IivOw+VW07pr0o+jz0ArL/Lyiw==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.16.tgz", + "integrity": "sha512-IEYDrtZGJvBRhie7u9xIWNZhd0Z7heRx6A/+dNdERrW/akC1iAVmUyBZPMOR5ywVK7nwsymlVpFbPccuMoYTmg==", "requires": { - "@amplitude/analytics-client-common": "^2.0.8", - "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-client-common": "^2.0.9", + "@amplitude/analytics-core": "^2.1.2", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" }, diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 8dc4f2ca26ab..96f7ce9b82c8 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^6.4.2", - "@amplitude/analytics-browser": "^2.3.5", + "@amplitude/analytics-browser": "^2.3.6", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", "assert": "^2.1.0", From e642e439a9cf4c392d86ea5e9aa09bcf6e570575 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 18:10:31 +0000 Subject: [PATCH 099/607] Bump eslint from 8.54.0 to 8.55.0 in /apps/block_scout_web/assets Bumps [eslint](https://github.com/eslint/eslint) from 8.54.0 to 8.55.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v8.54.0...v8.55.0) --- updated-dependencies: - dependency-name: eslint dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 46 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index f0b0d1b85c88..b22c7cbfbf9f 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -78,7 +78,7 @@ "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.8.1", "css-minimizer-webpack-plugin": "^5.0.1", - "eslint": "^8.54.0", + "eslint": "^8.55.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.0", "eslint-plugin-node": "^11.1.0", @@ -2054,9 +2054,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", - "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -2122,9 +2122,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", - "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", + "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -7306,15 +7306,15 @@ } }, "node_modules/eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", - "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", + "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.54.0", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.55.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -19245,9 +19245,9 @@ "dev": true }, "@eslint/eslintrc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", - "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -19294,9 +19294,9 @@ } }, "@eslint/js": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", - "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", + "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", "dev": true }, "@ethereumjs/common": { @@ -23254,15 +23254,15 @@ } }, "eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", - "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", + "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.54.0", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.55.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 8dc4f2ca26ab..703db72a53b0 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -90,7 +90,7 @@ "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.8.1", "css-minimizer-webpack-plugin": "^5.0.1", - "eslint": "^8.54.0", + "eslint": "^8.55.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.0", "eslint-plugin-node": "^11.1.0", From eafeca0df91ec2016de55ce489e885b1a838f656 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 18:11:18 +0000 Subject: [PATCH 100/607] Bump redux from 4.2.1 to 5.0.0 in /apps/block_scout_web/assets Bumps [redux](https://github.com/reduxjs/redux) from 4.2.1 to 5.0.0. - [Release notes](https://github.com/reduxjs/redux/releases) - [Changelog](https://github.com/reduxjs/redux/blob/master/CHANGELOG.md) - [Commits](https://github.com/reduxjs/redux/compare/v4.2.1...v5.0.0) --- updated-dependencies: - dependency-name: redux dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 20 +++++++------------ apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index f0b0d1b85c88..051235ef834d 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -58,7 +58,7 @@ "pikaday": "^1.8.2", "popper.js": "^1.14.7", "reduce-reducers": "^1.0.4", - "redux": "^4.2.1", + "redux": "^5.0.0", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", "sweetalert2": "^11.10.1", @@ -14800,12 +14800,9 @@ "integrity": "sha512-Mb2WZ2bJF597exiqX7owBzrqJ74DHLK3yOQjCyPAaNifRncE8OD0wFIuoMhXxTnHK07+8zZ2SJEKy/qtiyR7vw==" }, "node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.0.tgz", + "integrity": "sha512-blLIYmYetpZMET6Q6uCY7Jtl/Im5OBldy+vNPauA8vvsdqyt66oep4EUpAMWNHauTC6xa9JuRPhRB72rY82QGA==" }, "node_modules/regenerate": { "version": "1.4.2", @@ -28940,12 +28937,9 @@ "integrity": "sha512-Mb2WZ2bJF597exiqX7owBzrqJ74DHLK3yOQjCyPAaNifRncE8OD0wFIuoMhXxTnHK07+8zZ2SJEKy/qtiyR7vw==" }, "redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "requires": { - "@babel/runtime": "^7.9.2" - } + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.0.tgz", + "integrity": "sha512-blLIYmYetpZMET6Q6uCY7Jtl/Im5OBldy+vNPauA8vvsdqyt66oep4EUpAMWNHauTC6xa9JuRPhRB72rY82QGA==" }, "regenerate": { "version": "1.4.2", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 8dc4f2ca26ab..b40060da8a50 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -70,7 +70,7 @@ "pikaday": "^1.8.2", "popper.js": "^1.14.7", "reduce-reducers": "^1.0.4", - "redux": "^4.2.1", + "redux": "^5.0.0", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", "sweetalert2": "^11.10.1", From bd4629bdd33c5cd8ca9578e58bceb429390aaa70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 18:13:20 +0000 Subject: [PATCH 101/607] Bump @babel/core from 7.23.3 to 7.23.5 in /apps/block_scout_web/assets Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.23.3 to 7.23.5. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.5/packages/babel-core) --- updated-dependencies: - dependency-name: "@babel/core" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 174 +++++++++--------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 88 insertions(+), 88 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index f0b0d1b85c88..62d9e96daccc 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -71,7 +71,7 @@ "xss": "^1.0.14" }, "devDependencies": { - "@babel/core": "^7.23.3", + "@babel/core": "^7.23.5", "@babel/preset-env": "^7.23.3", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", @@ -229,11 +229,11 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dependencies": { - "@babel/highlight": "^7.22.13", + "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" }, "engines": { @@ -249,20 +249,20 @@ } }, "node_modules/@babel/core": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", - "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.5.tgz", + "integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.3", + "@babel/helpers": "^7.23.5", + "@babel/parser": "^7.23.5", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.3", - "@babel/types": "^7.23.3", + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -283,11 +283,11 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/@babel/generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", - "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz", + "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", "dependencies": { - "@babel/types": "^7.23.3", + "@babel/types": "^7.23.5", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -564,9 +564,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "engines": { "node": ">=6.9.0" } @@ -602,24 +602,24 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", - "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.5.tgz", + "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==", "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz", - "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, @@ -628,9 +628,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", - "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz", + "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1959,18 +1959,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", - "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz", + "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.3", - "@babel/types": "^7.23.3", + "@babel/parser": "^7.23.5", + "@babel/types": "^7.23.5", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1979,11 +1979,11 @@ } }, "node_modules/@babel/types": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", - "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz", + "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, @@ -17973,11 +17973,11 @@ } }, "@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "requires": { - "@babel/highlight": "^7.22.13", + "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" } }, @@ -17987,20 +17987,20 @@ "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==" }, "@babel/core": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", - "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.5.tgz", + "integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==", "requires": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.3", + "@babel/helpers": "^7.23.5", + "@babel/parser": "^7.23.5", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.3", - "@babel/types": "^7.23.3", + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -18016,11 +18016,11 @@ } }, "@babel/generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", - "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz", + "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", "requires": { - "@babel/types": "^7.23.3", + "@babel/types": "^7.23.5", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -18224,9 +18224,9 @@ } }, "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==" }, "@babel/helper-validator-identifier": { "version": "7.22.20", @@ -18250,29 +18250,29 @@ } }, "@babel/helpers": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", - "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.5.tgz", + "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==", "requires": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5" } }, "@babel/highlight": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz", - "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "requires": { - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", - "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==" + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz", + "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.23.3", @@ -19168,28 +19168,28 @@ } }, "@babel/traverse": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", - "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz", + "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==", "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.3", - "@babel/types": "^7.23.3", + "@babel/parser": "^7.23.5", + "@babel/types": "^7.23.5", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", - "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz", + "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", "requires": { - "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 8dc4f2ca26ab..84cfb5faf5f1 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -83,7 +83,7 @@ "xss": "^1.0.14" }, "devDependencies": { - "@babel/core": "^7.23.3", + "@babel/core": "^7.23.5", "@babel/preset-env": "^7.23.3", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", From f3e9680401dc4d1755871398ed4cf18bb8b66331 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 18:14:04 +0000 Subject: [PATCH 102/607] Bump photoswipe from 5.4.2 to 5.4.3 in /apps/block_scout_web/assets Bumps [photoswipe](https://github.com/dimsemenov/Photoswipe) from 5.4.2 to 5.4.3. - [Release notes](https://github.com/dimsemenov/Photoswipe/releases) - [Commits](https://github.com/dimsemenov/Photoswipe/commits) --- updated-dependencies: - dependency-name: photoswipe dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index f0b0d1b85c88..439d5ef4e52d 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -54,7 +54,7 @@ "path-parser": "^6.1.0", "phoenix": "file:../../../deps/phoenix", "phoenix_html": "file:../../../deps/phoenix_html", - "photoswipe": "^5.4.2", + "photoswipe": "^5.4.3", "pikaday": "^1.8.2", "popper.js": "^1.14.7", "reduce-reducers": "^1.0.4", @@ -13572,9 +13572,9 @@ "link": true }, "node_modules/photoswipe": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.4.2.tgz", - "integrity": "sha512-z5hr36nAIPOZbHJPbCJ/mQ3+ZlizttF9za5gKXKH/us1k4KNHaRbC63K1Px5sVVKUtGb/2+ixHpKqtwl0WAwvA==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.4.3.tgz", + "integrity": "sha512-9UC6oJBK4oXFZ5HcdlcvGkfEHsVrmE4csUdCQhEjHYb3PvPLO3PG7UhnPuOgjxwmhq5s17Un5NUdum01LgBDng==", "engines": { "node": ">= 0.12.0" } @@ -28108,9 +28108,9 @@ "version": "file:../../../deps/phoenix_html" }, "photoswipe": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.4.2.tgz", - "integrity": "sha512-z5hr36nAIPOZbHJPbCJ/mQ3+ZlizttF9za5gKXKH/us1k4KNHaRbC63K1Px5sVVKUtGb/2+ixHpKqtwl0WAwvA==" + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.4.3.tgz", + "integrity": "sha512-9UC6oJBK4oXFZ5HcdlcvGkfEHsVrmE4csUdCQhEjHYb3PvPLO3PG7UhnPuOgjxwmhq5s17Un5NUdum01LgBDng==" }, "picocolors": { "version": "1.0.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 8dc4f2ca26ab..5ed4338caa45 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -66,7 +66,7 @@ "path-parser": "^6.1.0", "phoenix": "file:../../../deps/phoenix", "phoenix_html": "file:../../../deps/phoenix_html", - "photoswipe": "^5.4.2", + "photoswipe": "^5.4.3", "pikaday": "^1.8.2", "popper.js": "^1.14.7", "reduce-reducers": "^1.0.4", From 9b43a5fb55a12eb0f1793e5c5a176f369b8d95c9 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Sat, 7 Oct 2023 23:37:48 +0300 Subject: [PATCH 103/607] Fix native coin exchange rate --- CHANGELOG.md | 1 + .../lib/explorer/exchange_rates/exchange_rates.ex | 6 ++++-- apps/explorer/lib/explorer/market/market.ex | 11 +++++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c13b70f75e4c..da9a3a4fa96e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - [#8915](https://github.com/blockscout/blockscout/pull/8915) - smart-contract: delete embeds_many relation on replace - [#8906](https://github.com/blockscout/blockscout/pull/8906) - Fix abi encoded string argument - [#8882](https://github.com/blockscout/blockscout/pull/8882) - Change order of proxy contracts patterns detection: existing popular EIPs to the top of the list +- [#8707](https://github.com/blockscout/blockscout/pull/8707) - Fix native coin exchange rate with `EXCHANGE_RATES_COINGECKO_COIN_ID` ### Chore diff --git a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex index 93155d93b851..7ffe82536881 100644 --- a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex +++ b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex @@ -84,9 +84,11 @@ defmodule Explorer.ExchangeRates do @doc """ Lists exchange rates for the tracked tickers. """ - @spec list :: [Token.t()] + @spec list :: [Token.t()] | nil def list do - list_from_store(store()) + if enabled?() do + list_from_store(store()) + end end @doc """ diff --git a/apps/explorer/lib/explorer/market/market.ex b/apps/explorer/lib/explorer/market/market.ex index 5e99a147c387..3691ac4eaaf5 100644 --- a/apps/explorer/lib/explorer/market/market.ex +++ b/apps/explorer/lib/explorer/market/market.ex @@ -54,7 +54,7 @@ defmodule Explorer.Market do """ @spec get_coin_exchange_rate() :: Token.t() | nil def get_coin_exchange_rate do - get_exchange_rate(Explorer.coin()) || get_native_coin_exchange_rate_from_db() || Token.null() + get_native_coin_exchange_rate_from_cache() || get_native_coin_exchange_rate_from_db() || Token.null() end @doc false @@ -138,8 +138,11 @@ defmodule Explorer.Market do ) end - @spec get_exchange_rate(String.t()) :: Token.t() | nil - defp get_exchange_rate(symbol) do - ExchangeRates.lookup(symbol) + @spec get_native_coin_exchange_rate_from_cache :: Token.t() | nil + defp get_native_coin_exchange_rate_from_cache do + case ExchangeRates.list() do + [native_coin] -> native_coin + _ -> nil + end end end From 68c3daf9a3290ec9ed5772a5d2548eb34fd75578 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 18:52:40 +0000 Subject: [PATCH 104/607] Bump ueberauth from 0.10.5 to 0.10.6 Bumps [ueberauth](https://github.com/ueberauth/ueberauth) from 0.10.5 to 0.10.6. - [Release notes](https://github.com/ueberauth/ueberauth/releases) - [Changelog](https://github.com/ueberauth/ueberauth/blob/master/CHANGELOG.md) - [Commits](https://github.com/ueberauth/ueberauth/compare/v0.10.5...v0.10.6) --- updated-dependencies: - dependency-name: ueberauth dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 343093379b85..3ccc63510368 100644 --- a/mix.lock +++ b/mix.lock @@ -104,7 +104,7 @@ "phoenix_html": {:hex, :phoenix_html, "3.0.4", "232d41884fe6a9c42d09f48397c175cd6f0d443aaa34c7424da47604201df2e1", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "ce17fd3cf815b2ed874114073e743507704b1f5288bb03c304a77458485efc8b"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, - "plug": {:hex, :plug, "1.15.1", "b7efd81c1a1286f13efb3f769de343236bd8b7d23b4a9f40d3002fc39ad8f74c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "459497bd94d041d98d948054ec6c0b76feacd28eec38b219ca04c0de13c79d30"}, + "plug": {:hex, :plug, "1.15.2", "94cf1fa375526f30ff8770837cb804798e0045fd97185f0bb9e5fcd858c792a3", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615"}, "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, @@ -136,7 +136,7 @@ "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, "toml": {:hex, :toml, "0.6.2", "38f445df384a17e5d382befe30e3489112a48d3ba4c459e543f748c2f25dd4d1", [:mix], [], "hexpm", "d013e45126d74c0c26a38d31f5e8e9b83ea19fc752470feb9a86071ca5a672fa"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, - "ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"}, + "ueberauth": {:hex, :ueberauth, "0.10.6", "8dbefd5aec30c5830af2b6ce6e03f62cc28ae0757f34e2986454f54b8dca3f65", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b0ad1c7508f3cfd5c2c1c668d1a32bafd77de4c56af82c7bfd7e54ed078a7928"}, "ueberauth_auth0": {:hex, :ueberauth_auth0, "2.1.0", "0632d5844049fa2f26823f15e1120aa32f27df6f27ce515a4b04641736594bf4", [:mix], [{:oauth2, "~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "8d3b30fa27c95c9e82c30c4afb016251405706d2e9627e603c3c9787fd1314fc"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "wallaby": {:hex, :wallaby, "0.30.6", "7dc4c1213f3b52c4152581d126632bc7e06892336d3a0f582853efeeabd45a71", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "50950c1d968549b54c20e16175c68c7fc0824138e2bb93feb11ef6add8eb23d4"}, From 2005afd9332f01c615fc3c5aac674328bcb191af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 18:53:34 +0000 Subject: [PATCH 105/607] Bump postgrex from 0.17.3 to 0.17.4 Bumps [postgrex](https://github.com/elixir-ecto/postgrex) from 0.17.3 to 0.17.4. - [Release notes](https://github.com/elixir-ecto/postgrex/releases) - [Changelog](https://github.com/elixir-ecto/postgrex/blob/master/CHANGELOG.md) - [Commits](https://github.com/elixir-ecto/postgrex/compare/v0.17.3...v0.17.4) --- updated-dependencies: - dependency-name: postgrex dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 343093379b85..c35432d85102 100644 --- a/mix.lock +++ b/mix.lock @@ -109,7 +109,7 @@ "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, - "postgrex": {:hex, :postgrex, "0.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"}, + "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, "prometheus": {:hex, :prometheus, "4.11.0", "b95f8de8530f541bd95951e18e355a840003672e5eda4788c5fa6183406ba29a", [:mix, :rebar3], [{:quantile_estimator, "~> 0.2.1", [hex: :quantile_estimator, repo: "hexpm", optional: false]}], "hexpm", "719862351aabf4df7079b05dc085d2bbcbe3ac0ac3009e956671b1d5ab88247d"}, "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"}, "prometheus_ex": {:git, "https://github.com/lanodan/prometheus.ex", "31f7fbe4b71b79ba27efc2a5085746c4011ceb8f", [branch: "fix/elixir-1.14"]}, From 171b6d6458213c3883b6ada9620b9da307f9d76a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 19:39:47 +0000 Subject: [PATCH 106/607] Bump @babel/preset-env in /apps/block_scout_web/assets Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.23.3 to 7.23.5. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.5/packages/babel-preset-env) --- updated-dependencies: - dependency-name: "@babel/preset-env" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 270 +++++++++--------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 136 insertions(+), 136 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index e28212c234b7..9c14d4eff86e 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -72,7 +72,7 @@ }, "devDependencies": { "@babel/core": "^7.23.5", - "@babel/preset-env": "^7.23.3", + "@babel/preset-env": "^7.23.5", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^11.0.0", @@ -241,9 +241,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", - "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", "engines": { "node": ">=6.9.0" } @@ -580,9 +580,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", "engines": { "node": ">=6.9.0" } @@ -991,9 +991,9 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.3.tgz", - "integrity": "sha512-59GsVNavGxAXCDDbakWSMJhajASb4kBCqDjqJsv+p5nKdbz7istmZ3HrX3L2LuiI80+zsOADCvooqQH3qGCucQ==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz", + "integrity": "sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -1041,9 +1041,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.3.tgz", - "integrity": "sha512-QPZxHrThbQia7UdvfpaRRlq/J9ciz1J4go0k+lPBXbgaNeY7IQrBj/9ceWjvMMI07/ZBzHl/F0R/2K0qH7jCVw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", + "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1072,9 +1072,9 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.3.tgz", - "integrity": "sha512-PENDVxdr7ZxKPyi5Ffc0LjXdnJyrJxyqF5T5YjlVg4a0VFfQHW0r8iAtRiDXkfHlu1wwcvdtnndGYIeJLSuRMQ==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", + "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", "dev": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.22.15", @@ -1089,9 +1089,9 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.3.tgz", - "integrity": "sha512-FGEQmugvAEu2QtgtU0uTASXevfLMFfBeVCIIdcQhn/uBQsMTjBajdnAtanQlOcuihWh10PZ7+HWvc7NtBwP74w==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", + "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", @@ -1174,9 +1174,9 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.3.tgz", - "integrity": "sha512-vTG+cTGxPFou12Rj7ll+eD5yWeNl5/8xvQvF08y5Gv3v4mZQoyFf8/n9zg4q5vvCWt5jmgymfzMAldO7orBn7A==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", + "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1206,9 +1206,9 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.3.tgz", - "integrity": "sha512-yCLhW34wpJWRdTxxWtFZASJisihrfyMOTOQexhVzA78jlU+dH7Dw+zQgcPepQ5F3C6bAIiblZZ+qBggJdHiBAg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", + "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1254,9 +1254,9 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.3.tgz", - "integrity": "sha512-H9Ej2OiISIZowZHaBwF0tsJOih1PftXJtE8EWqlEIwpc7LMTGq0rPOrywKLQ4nefzx8/HMR0D3JGXoMHYvhi0A==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", + "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1285,9 +1285,9 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.3.tgz", - "integrity": "sha512-+pD5ZbxofyOygEp+zZAfujY2ShNCXRpDRIPOiBmTO693hhyOEteZgl876Xs9SAHPQpcV0vz8LvA/T+w8AzyX8A==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", + "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1414,9 +1414,9 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.3.tgz", - "integrity": "sha512-xzg24Lnld4DYIdysyf07zJ1P+iIfJpxtVFOzX4g+bsJ3Ng5Le7rXx9KwqKzuyaUeRnt+I1EICwQITqc0E2PmpA==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", + "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1430,9 +1430,9 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.3.tgz", - "integrity": "sha512-s9GO7fIBi/BLsZ0v3Rftr6Oe4t0ctJ8h4CCXfPoEJwmvAPMyNrfkOOJzm6b9PX9YXcCJWWQd/sBF/N26eBiMVw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", + "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1446,9 +1446,9 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.3.tgz", - "integrity": "sha512-VxHt0ANkDmu8TANdE9Kc0rndo/ccsmfe2Cx2y5sI4hu3AukHQ5wAu4cM7j3ba8B9548ijVyclBU+nuDQftZsog==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", + "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", "dev": true, "dependencies": { "@babel/compat-data": "^7.23.3", @@ -1481,9 +1481,9 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.3.tgz", - "integrity": "sha512-LxYSb0iLjUamfm7f1D7GpiS4j0UAC8AOiehnsGAP8BEsIX8EOi3qV6bbctw8M7ZvLtcoZfZX5Z7rN9PlWk0m5A==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", + "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1497,9 +1497,9 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.3.tgz", - "integrity": "sha512-zvL8vIfIUgMccIAK1lxjvNv572JHFJIKb4MWBz5OGdBQA0fB0Xluix5rmOby48exiJc987neOmP/m9Fnpkz3Tg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", + "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1545,9 +1545,9 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.3.tgz", - "integrity": "sha512-a5m2oLNFyje2e/rGKjVfAELTVI5mbA0FeZpBnkOWWV7eSmKQ+T/XW0Vf+29ScLzSxX+rnsarvU0oie/4m6hkxA==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", + "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", @@ -1779,15 +1779,15 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.3.tgz", - "integrity": "sha512-ovzGc2uuyNfNAs/jyjIGxS8arOHS5FENZaNn4rtE7UdKMMkqHCvboHfcuhWLZNX5cB44QfcGNWjaevxMzzMf+Q==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.5.tgz", + "integrity": "sha512-0d/uxVD6tFGWXGDSfyMD1p2otoaKmu6+GD+NfAx0tMaH+dxORnp7T9TaVQ6mKyya7iBtCIVxHjWT7MuzzM9z+A==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.3", + "@babel/compat-data": "^7.23.5", "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", + "@babel/helper-validator-option": "^7.23.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", @@ -1811,25 +1811,25 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.4", "@babel/plugin-transform-async-to-generator": "^7.23.3", "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.3", - "@babel/plugin-transform-classes": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.5", "@babel/plugin-transform-computed-properties": "^7.23.3", "@babel/plugin-transform-destructuring": "^7.23.3", "@babel/plugin-transform-dotall-regex": "^7.23.3", "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", "@babel/plugin-transform-for-of": "^7.23.3", "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", "@babel/plugin-transform-member-expression-literals": "^7.23.3", "@babel/plugin-transform-modules-amd": "^7.23.3", "@babel/plugin-transform-modules-commonjs": "^7.23.3", @@ -1837,15 +1837,15 @@ "@babel/plugin-transform-modules-umd": "^7.23.3", "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.3", - "@babel/plugin-transform-numeric-separator": "^7.23.3", - "@babel/plugin-transform-object-rest-spread": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.23.4", "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.3", - "@babel/plugin-transform-optional-chaining": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", "@babel/plugin-transform-parameters": "^7.23.3", "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", "@babel/plugin-transform-property-literals": "^7.23.3", "@babel/plugin-transform-regenerator": "^7.23.3", "@babel/plugin-transform-reserved-words": "^7.23.3", @@ -17982,9 +17982,9 @@ } }, "@babel/compat-data": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", - "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==" + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==" }, "@babel/core": { "version": "7.23.5", @@ -18234,9 +18234,9 @@ "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" }, "@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==" + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==" }, "@babel/helper-wrap-function": { "version": "7.22.20", @@ -18511,9 +18511,9 @@ } }, "@babel/plugin-transform-async-generator-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.3.tgz", - "integrity": "sha512-59GsVNavGxAXCDDbakWSMJhajASb4kBCqDjqJsv+p5nKdbz7istmZ3HrX3L2LuiI80+zsOADCvooqQH3qGCucQ==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz", + "integrity": "sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw==", "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.22.20", @@ -18543,9 +18543,9 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.3.tgz", - "integrity": "sha512-QPZxHrThbQia7UdvfpaRRlq/J9ciz1J4go0k+lPBXbgaNeY7IQrBj/9ceWjvMMI07/ZBzHl/F0R/2K0qH7jCVw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", + "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" @@ -18562,9 +18562,9 @@ } }, "@babel/plugin-transform-class-static-block": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.3.tgz", - "integrity": "sha512-PENDVxdr7ZxKPyi5Ffc0LjXdnJyrJxyqF5T5YjlVg4a0VFfQHW0r8iAtRiDXkfHlu1wwcvdtnndGYIeJLSuRMQ==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", + "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", "dev": true, "requires": { "@babel/helper-create-class-features-plugin": "^7.22.15", @@ -18573,9 +18573,9 @@ } }, "@babel/plugin-transform-classes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.3.tgz", - "integrity": "sha512-FGEQmugvAEu2QtgtU0uTASXevfLMFfBeVCIIdcQhn/uBQsMTjBajdnAtanQlOcuihWh10PZ7+HWvc7NtBwP74w==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", + "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.22.5", @@ -18628,9 +18628,9 @@ } }, "@babel/plugin-transform-dynamic-import": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.3.tgz", - "integrity": "sha512-vTG+cTGxPFou12Rj7ll+eD5yWeNl5/8xvQvF08y5Gv3v4mZQoyFf8/n9zg4q5vvCWt5jmgymfzMAldO7orBn7A==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", + "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18648,9 +18648,9 @@ } }, "@babel/plugin-transform-export-namespace-from": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.3.tgz", - "integrity": "sha512-yCLhW34wpJWRdTxxWtFZASJisihrfyMOTOQexhVzA78jlU+dH7Dw+zQgcPepQ5F3C6bAIiblZZ+qBggJdHiBAg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", + "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18678,9 +18678,9 @@ } }, "@babel/plugin-transform-json-strings": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.3.tgz", - "integrity": "sha512-H9Ej2OiISIZowZHaBwF0tsJOih1PftXJtE8EWqlEIwpc7LMTGq0rPOrywKLQ4nefzx8/HMR0D3JGXoMHYvhi0A==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", + "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18697,9 +18697,9 @@ } }, "@babel/plugin-transform-logical-assignment-operators": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.3.tgz", - "integrity": "sha512-+pD5ZbxofyOygEp+zZAfujY2ShNCXRpDRIPOiBmTO693hhyOEteZgl876Xs9SAHPQpcV0vz8LvA/T+w8AzyX8A==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", + "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18778,9 +18778,9 @@ } }, "@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.3.tgz", - "integrity": "sha512-xzg24Lnld4DYIdysyf07zJ1P+iIfJpxtVFOzX4g+bsJ3Ng5Le7rXx9KwqKzuyaUeRnt+I1EICwQITqc0E2PmpA==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", + "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18788,9 +18788,9 @@ } }, "@babel/plugin-transform-numeric-separator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.3.tgz", - "integrity": "sha512-s9GO7fIBi/BLsZ0v3Rftr6Oe4t0ctJ8h4CCXfPoEJwmvAPMyNrfkOOJzm6b9PX9YXcCJWWQd/sBF/N26eBiMVw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", + "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18798,9 +18798,9 @@ } }, "@babel/plugin-transform-object-rest-spread": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.3.tgz", - "integrity": "sha512-VxHt0ANkDmu8TANdE9Kc0rndo/ccsmfe2Cx2y5sI4hu3AukHQ5wAu4cM7j3ba8B9548ijVyclBU+nuDQftZsog==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", + "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", "dev": true, "requires": { "@babel/compat-data": "^7.23.3", @@ -18821,9 +18821,9 @@ } }, "@babel/plugin-transform-optional-catch-binding": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.3.tgz", - "integrity": "sha512-LxYSb0iLjUamfm7f1D7GpiS4j0UAC8AOiehnsGAP8BEsIX8EOi3qV6bbctw8M7ZvLtcoZfZX5Z7rN9PlWk0m5A==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", + "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18831,9 +18831,9 @@ } }, "@babel/plugin-transform-optional-chaining": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.3.tgz", - "integrity": "sha512-zvL8vIfIUgMccIAK1lxjvNv572JHFJIKb4MWBz5OGdBQA0fB0Xluix5rmOby48exiJc987neOmP/m9Fnpkz3Tg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", + "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18861,9 +18861,9 @@ } }, "@babel/plugin-transform-private-property-in-object": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.3.tgz", - "integrity": "sha512-a5m2oLNFyje2e/rGKjVfAELTVI5mbA0FeZpBnkOWWV7eSmKQ+T/XW0Vf+29ScLzSxX+rnsarvU0oie/4m6hkxA==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", + "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.22.5", @@ -19010,15 +19010,15 @@ } }, "@babel/preset-env": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.3.tgz", - "integrity": "sha512-ovzGc2uuyNfNAs/jyjIGxS8arOHS5FENZaNn4rtE7UdKMMkqHCvboHfcuhWLZNX5cB44QfcGNWjaevxMzzMf+Q==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.5.tgz", + "integrity": "sha512-0d/uxVD6tFGWXGDSfyMD1p2otoaKmu6+GD+NfAx0tMaH+dxORnp7T9TaVQ6mKyya7iBtCIVxHjWT7MuzzM9z+A==", "dev": true, "requires": { - "@babel/compat-data": "^7.23.3", + "@babel/compat-data": "^7.23.5", "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", + "@babel/helper-validator-option": "^7.23.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", @@ -19042,25 +19042,25 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.4", "@babel/plugin-transform-async-to-generator": "^7.23.3", "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.3", - "@babel/plugin-transform-classes": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.5", "@babel/plugin-transform-computed-properties": "^7.23.3", "@babel/plugin-transform-destructuring": "^7.23.3", "@babel/plugin-transform-dotall-regex": "^7.23.3", "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", "@babel/plugin-transform-for-of": "^7.23.3", "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", "@babel/plugin-transform-member-expression-literals": "^7.23.3", "@babel/plugin-transform-modules-amd": "^7.23.3", "@babel/plugin-transform-modules-commonjs": "^7.23.3", @@ -19068,15 +19068,15 @@ "@babel/plugin-transform-modules-umd": "^7.23.3", "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.3", - "@babel/plugin-transform-numeric-separator": "^7.23.3", - "@babel/plugin-transform-object-rest-spread": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.23.4", "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.3", - "@babel/plugin-transform-optional-chaining": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", "@babel/plugin-transform-parameters": "^7.23.3", "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", "@babel/plugin-transform-property-literals": "^7.23.3", "@babel/plugin-transform-regenerator": "^7.23.3", "@babel/plugin-transform-reserved-words": "^7.23.3", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index ceb5eb89a79e..68469b197bc1 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -84,7 +84,7 @@ }, "devDependencies": { "@babel/core": "^7.23.5", - "@babel/preset-env": "^7.23.3", + "@babel/preset-env": "^7.23.5", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^11.0.0", From e54f0707f7767a4e7aadb051f3a4c339e1dc30df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 20:55:54 +0000 Subject: [PATCH 107/607] Bump @fortawesome/fontawesome-free in /apps/block_scout_web/assets Bumps [@fortawesome/fontawesome-free](https://github.com/FortAwesome/Font-Awesome) from 6.4.2 to 6.5.1. - [Release notes](https://github.com/FortAwesome/Font-Awesome/releases) - [Changelog](https://github.com/FortAwesome/Font-Awesome/blob/6.x/CHANGELOG.md) - [Commits](https://github.com/FortAwesome/Font-Awesome/compare/6.4.2...6.5.1) --- updated-dependencies: - dependency-name: "@fortawesome/fontawesome-free" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 5aa43ce8255a..f45405e27160 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -8,7 +8,7 @@ "license": "GPL-3.0", "dependencies": { "@amplitude/analytics-browser": "^2.3.6", - "@fortawesome/fontawesome-free": "^6.4.2", + "@fortawesome/fontawesome-free": "^6.5.1", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", "assert": "^2.1.0", @@ -2558,9 +2558,9 @@ } }, "node_modules/@fortawesome/fontawesome-free": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.2.tgz", - "integrity": "sha512-m5cPn3e2+FDCOgi1mz0RexTUvvQibBebOUlUlW0+YrMjDTPkiJ6VTKukA1GRsvRw+12KyJndNjj0O4AgTxm2Pg==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.1.tgz", + "integrity": "sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw==", "hasInstallScript": true, "engines": { "node": ">=6" @@ -19540,9 +19540,9 @@ } }, "@fortawesome/fontawesome-free": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.2.tgz", - "integrity": "sha512-m5cPn3e2+FDCOgi1mz0RexTUvvQibBebOUlUlW0+YrMjDTPkiJ6VTKukA1GRsvRw+12KyJndNjj0O4AgTxm2Pg==" + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.1.tgz", + "integrity": "sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw==" }, "@humanwhocodes/config-array": { "version": "0.11.13", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index e572395367c2..66e3f2686435 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -19,7 +19,7 @@ "eslint": "eslint js/**" }, "dependencies": { - "@fortawesome/fontawesome-free": "^6.4.2", + "@fortawesome/fontawesome-free": "^6.5.1", "@amplitude/analytics-browser": "^2.3.6", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", From a73132ffaf9a907452d6736407a7cd4be5986977 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 20:56:31 +0000 Subject: [PATCH 108/607] Bump gettext from 0.23.1 to 0.24.0 Bumps [gettext](https://github.com/elixir-gettext/gettext) from 0.23.1 to 0.24.0. - [Changelog](https://github.com/elixir-gettext/gettext/blob/main/CHANGELOG.md) - [Commits](https://github.com/elixir-gettext/gettext/compare/v0.23.1...v0.24.0) --- updated-dependencies: - dependency-name: gettext dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/mix.exs | 2 +- mix.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index f134b2b3373f..2270ffaabdbb 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -84,7 +84,7 @@ defmodule BlockScoutWeb.Mixfile do # HTML CSS selectors for Phoenix controller tests {:floki, "~> 0.31"}, {:flow, "~> 1.2"}, - {:gettext, "~> 0.23.1"}, + {:gettext, "~> 0.24.0"}, {:hammer, "~> 6.0"}, {:httpoison, "~> 2.0"}, {:indexer, in_umbrella: true, runtime: false}, diff --git a/mix.lock b/mix.lock index 3c484116e454..12d09b27706e 100644 --- a/mix.lock +++ b/mix.lock @@ -56,14 +56,14 @@ "ex_utils": {:hex, :ex_utils, "0.1.7", "2c133e0bcdc49a858cf8dacf893308ebc05bc5fba501dc3d2935e65365ec0bf3", [:mix], [], "hexpm", "66d4fe75285948f2d1e69c2a5ddd651c398c813574f8d36a9eef11dc20356ef6"}, "exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], [], "hexpm", "1222419f706e01bfa1095aec9acf6421367dcfab798a6f67c54cf784733cd6b5"}, "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm", "32e95820a97cffea67830e91514a2ad53b888850442d6d395f53a1ac60c82e07"}, - "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, + "expo": {:hex, :expo, "0.5.1", "249e826a897cac48f591deba863b26c16682b43711dd15ee86b92f25eafd96d9", [:mix], [], "hexpm", "68a4233b0658a3d12ee00d27d37d856b1ba48607e7ce20fd376958d0ba6ce92b"}, "exvcr": {:hex, :exvcr, "0.14.4", "1aa5fe7d3f10b117251c158f8d28b39f7fc73d0a7628b2d0b75bf8cfb1111576", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.16", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4e600568c02ed29d46bc2e2c74927d172ba06658aa8b14705c0207363c44cc94"}, "file_info": {:hex, :file_info, "0.0.4", "2e0e77f211e833f38ead22cb29ce53761d457d80b3ffe0ffe0eb93880b0963b2", [:mix], [{:mimetype_parser, "~> 0.1.2", [hex: :mimetype_parser, repo: "hexpm", optional: false]}], "hexpm", "50e7ad01c2c8b9339010675fe4dc4a113b8d6ca7eddce24d1d74fd0e762781a5"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"}, "flow": {:hex, :flow, "1.2.4", "1dd58918287eb286656008777cb32714b5123d3855956f29aa141ebae456922d", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "874adde96368e71870f3510b91e35bc31652291858c86c0e75359cbdd35eb211"}, "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, - "gettext": {:hex, :gettext, "0.23.1", "821e619a240e6000db2fc16a574ef68b3bd7fe0167ccc264a81563cc93e67a31", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "19d744a36b809d810d610b57c27b934425859d158ebd56561bc41f7eeb8795db"}, + "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~>2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "hammer": {:hex, :hammer, "6.1.0", "f263e3c3e9946bd410ea0336b2abe0cb6260af4afb3a221e1027540706e76c55", [:make, :mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b47e415a562a6d072392deabcd58090d8a41182cf9044cdd6b0d0faaaf68ba57"}, "hammer_backend_redis": {:hex, :hammer_backend_redis, "6.1.2", "eb296bb4924928e24135308b2afc189201fd09411c870c6bbadea444a49b2f2c", [:mix], [{:hammer, "~> 6.0", [hex: :hammer, repo: "hexpm", optional: false]}, {:redix, "~> 1.1", [hex: :redix, repo: "hexpm", optional: false]}], "hexpm", "217ea066278910543a5e9b577d5bf2425419446b94fe76bdd9f255f39feec9fa"}, From 56f5f899eededa76461e813535def351e96f7261 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Fri, 1 Dec 2023 19:42:39 +0600 Subject: [PATCH 109/607] Allow call type to be in lower case --- CHANGELOG.md | 1 + .../lib/ethereum_jsonrpc/geth.ex | 86 +++++++++---------- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6473289e8db3..73b6fac5dc4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Fixes +- [#8922](https://github.com/blockscout/blockscout/pull/8922) - Allow call type to be in lowercase - [#8917](https://github.com/blockscout/blockscout/pull/8917) - Proxy detection hotfix in API v2 - [#8915](https://github.com/blockscout/blockscout/pull/8915) - smart-contract: delete embeds_many relation on replace - [#8906](https://github.com/blockscout/blockscout/pull/8906) - Fix abi encoded string argument diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex index 4d864336f50c..8f852372e407 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex @@ -283,52 +283,48 @@ defmodule EthereumJSONRPC.Geth do [Map.put(last, "error", "execution stopped") | acc] end - defp parse_call_tracer_calls( - {%{"type" => type, "from" => from} = call, index}, - acc, - trace_address, - inner? - ) - when type in ~w(CALL CALLCODE DELEGATECALL STATICCALL CREATE CREATE2 SELFDESTRUCT REVERT STOP) do - new_trace_address = [index | trace_address] - - formatted_call = - %{ - "type" => if(type in ~w(CALL CALLCODE DELEGATECALL STATICCALL), do: "call", else: String.downcase(type)), - "callType" => String.downcase(type), - "from" => from, - "to" => Map.get(call, "to", "0x"), - "createdContractAddressHash" => Map.get(call, "to", "0x"), - "value" => Map.get(call, "value", "0x0"), - "gas" => Map.get(call, "gas", "0x0"), - "gasUsed" => Map.get(call, "gasUsed", "0x0"), - "input" => Map.get(call, "input", "0x"), - "init" => Map.get(call, "input", "0x"), - "createdContractCode" => Map.get(call, "output", "0x"), - "traceAddress" => if(inner?, do: Enum.reverse(new_trace_address), else: []), - "error" => call["error"] - } - |> case do - %{"error" => nil} = ok_call -> - ok_call - |> Map.delete("error") - # to handle staticcall, all other cases handled by EthereumJSONRPC.Geth.Call.elixir_to_internal_transaction_params/1 - |> Map.put("output", Map.get(call, "output", "0x")) - - error_call -> - error_call - end - - parse_call_tracer_calls( - Map.get(call, "calls", []), - [formatted_call | acc], - if(inner?, do: new_trace_address, else: []) - ) - end + defp parse_call_tracer_calls({%{"type" => upcase_type, "from" => from} = call, index}, acc, trace_address, inner?) do + case String.downcase(upcase_type) do + type when type in ~w(call callcode delegatecall staticcall create create2 selfdestruct revert stop) -> + new_trace_address = [index | trace_address] - defp parse_call_tracer_calls({call, _}, acc, _trace_address, _inner?) do - Logger.warning("Call from a callTracer with an unknown type: #{inspect(call)}") - acc + formatted_call = + %{ + "type" => if(type in ~w(call callcode delegatecall staticcall), do: "call", else: type), + "callType" => type, + "from" => from, + "to" => Map.get(call, "to", "0x"), + "createdContractAddressHash" => Map.get(call, "to", "0x"), + "value" => Map.get(call, "value", "0x0"), + "gas" => Map.get(call, "gas", "0x0"), + "gasUsed" => Map.get(call, "gasUsed", "0x0"), + "input" => Map.get(call, "input", "0x"), + "init" => Map.get(call, "input", "0x"), + "createdContractCode" => Map.get(call, "output", "0x"), + "traceAddress" => if(inner?, do: Enum.reverse(new_trace_address), else: []), + "error" => call["error"] + } + |> case do + %{"error" => nil} = ok_call -> + ok_call + |> Map.delete("error") + # to handle staticcall, all other cases handled by EthereumJSONRPC.Geth.Call.elixir_to_internal_transaction_params/1 + |> Map.put("output", Map.get(call, "output", "0x")) + + error_call -> + error_call + end + + parse_call_tracer_calls( + Map.get(call, "calls", []), + [formatted_call | acc], + if(inner?, do: new_trace_address, else: []) + ) + + _unknown_type -> + Logger.warning("Call from a callTracer with an unknown type: #{inspect(call)}") + acc + end end defp parse_call_tracer_calls(calls, acc, trace_address, _inner) when is_list(calls) do From ba0aae21afe89dbffb02740e60253c2b671e6fb4 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Tue, 5 Dec 2023 14:38:34 +0600 Subject: [PATCH 110/607] Add test for lowercase internal transaction type --- .../test/ethereum_jsonrpc/geth_test.exs | 82 +++++++++++++++++-- .../support/ethereum_jsonrpc/case/geth/mox.ex | 6 +- 2 files changed, 78 insertions(+), 10 deletions(-) diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs index 853a5643ae02..000b75623a03 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs @@ -12,9 +12,9 @@ defmodule EthereumJSONRPC.GethTest do describe "fetch_internal_transactions/2" do # Infura Mainnet does not support debug_traceTransaction, so this cannot be tested expect in Mox setup do - EthereumJSONRPC.Case.Geth.Mox.setup() initial_env = Application.get_all_env(:ethereum_jsonrpc) on_exit(fn -> Application.put_all_env([{:ethereum_jsonrpc, initial_env}]) end) + EthereumJSONRPC.Case.Geth.Mox.setup() end setup :verify_on_exit! @@ -26,7 +26,7 @@ defmodule EthereumJSONRPC.GethTest do transaction_hash = "0x32b17f27ddb546eab3c4c33f31eb22c1cb992d4ccc50dae26922805b717efe5c" tracer = File.read!("priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js") - expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id, params: [^transaction_hash, %{tracer: ^tracer}]}], _ -> + expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id, params: [^transaction_hash, %{"tracer" => ^tracer}]}], _ -> {:ok, [ %{ @@ -49,7 +49,7 @@ defmodule EthereumJSONRPC.GethTest do ]} end) - Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js") + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js", debug_trace_transaction_timeout: "5s") assert {:ok, [ @@ -98,7 +98,7 @@ defmodule EthereumJSONRPC.GethTest do tracer = File.read!("priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js") expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn - [%{id: id, params: [^transaction_hash, %{tracer: "callTracer"}]}], _ -> + [%{id: id, params: [^transaction_hash, %{"tracer" => "callTracer"}]}], _ -> {:ok, [ %{ @@ -222,7 +222,7 @@ defmodule EthereumJSONRPC.GethTest do end) expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn - [%{id: id, params: [^transaction_hash, %{tracer: ^tracer}]}], _ -> + [%{id: id, params: [^transaction_hash, %{"tracer" => ^tracer}]}], _ -> {:ok, [ %{ @@ -361,7 +361,7 @@ defmodule EthereumJSONRPC.GethTest do call_tracer_internal_txs = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) - Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js") + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js", debug_trace_transaction_timeout: "5s") assert call_tracer_internal_txs == Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) @@ -383,7 +383,7 @@ defmodule EthereumJSONRPC.GethTest do tracer = File.read!("priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js") expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn - [%{id: id, params: [^transaction_hash, %{tracer: "callTracer"}]}], _ -> + [%{id: id, params: [^transaction_hash, %{"tracer" => "callTracer"}]}], _ -> {:ok, [ %{ @@ -405,7 +405,7 @@ defmodule EthereumJSONRPC.GethTest do end) expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn - [%{id: id, params: [^transaction_hash, %{tracer: ^tracer}]}], _ -> + [%{id: id, params: [^transaction_hash, %{"tracer" => ^tracer}]}], _ -> {:ok, [ %{ @@ -431,7 +431,7 @@ defmodule EthereumJSONRPC.GethTest do call_tracer_internal_txs = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) - Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js") + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js", debug_trace_transaction_timeout: "5s") assert call_tracer_internal_txs == Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) @@ -482,6 +482,70 @@ defmodule EthereumJSONRPC.GethTest do } ]} = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) end + + test "uppercase type parsing result is the same as lowercase", %{ + json_rpc_named_arguments: json_rpc_named_arguments + } do + transaction_hash = "0xb342cafc6ac552c3be2090561453204c8784caf025ac8267320834e4cd163d96" + block_number = 3_287_375 + transaction_index = 13 + + transaction_params = %{ + block_number: block_number, + transaction_index: transaction_index, + hash_data: transaction_hash + } + + expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn + [%{id: id, params: [^transaction_hash, %{"tracer" => "callTracer"}]}], _ -> + {:ok, + [ + %{ + id: id, + result: %{ + "type" => "CREATE", + "from" => "0x117b358218da5a4f647072ddb50ded038ed63d17", + "to" => "0x205a6b72ce16736c9d87172568a9c0cb9304de0d", + "value" => "0x0", + "gas" => "0x106f5", + "gasUsed" => "0x106f5", + "input" => + "0x608060405234801561001057600080fd5b50610150806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea26469706673582212209a159a4f3847890f10bfb87871a61eba91c5dbf5ee3cf6398207e292eee22a1664736f6c63430008070033", + "output" => + "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea26469706673582212209a159a4f3847890f10bfb87871a61eba91c5dbf5ee3cf6398207e292eee22a1664736f6c63430008070033" + } + } + ]} + end) + + uppercase_result = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) + + expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn + [%{id: id, params: [^transaction_hash, %{"tracer" => "callTracer"}]}], _ -> + {:ok, + [ + %{ + id: id, + result: %{ + "type" => "create", + "from" => "0x117b358218da5a4f647072ddb50ded038ed63d17", + "to" => "0x205a6b72ce16736c9d87172568a9c0cb9304de0d", + "value" => "0x0", + "gas" => "0x106f5", + "gasUsed" => "0x106f5", + "input" => + "0x608060405234801561001057600080fd5b50610150806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea26469706673582212209a159a4f3847890f10bfb87871a61eba91c5dbf5ee3cf6398207e292eee22a1664736f6c63430008070033", + "output" => + "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea26469706673582212209a159a4f3847890f10bfb87871a61eba91c5dbf5ee3cf6398207e292eee22a1664736f6c63430008070033" + } + } + ]} + end) + + lowercase_result = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) + + assert uppercase_result == lowercase_result + end end describe "fetch_block_internal_transactions/1" do diff --git a/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/geth/mox.ex b/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/geth/mox.ex index d1f113223d68..41a2593cb946 100644 --- a/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/geth/mox.ex +++ b/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/geth/mox.ex @@ -6,7 +6,11 @@ defmodule EthereumJSONRPC.Case.Geth.Mox do def setup do %{ block_interval: 500, - json_rpc_named_arguments: [transport: EthereumJSONRPC.Mox, transport_options: [], variant: EthereumJSONRPC.Geth], + json_rpc_named_arguments: [ + transport: EthereumJSONRPC.Mox, + transport_options: [http_options: [timeout: 60000, recv_timeout: 60000]], + variant: EthereumJSONRPC.Geth + ], subscribe_named_arguments: [transport: EthereumJSONRPC.Mox, transport_options: []] } end From c44c3b5ed00a0ac6842d4cdbd7913958fb66f996 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Tue, 5 Dec 2023 14:40:49 +0600 Subject: [PATCH 111/607] Handle lowercase STOP opcode --- apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex index 8f852372e407..d38fb0716a72 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex @@ -279,7 +279,8 @@ defmodule EthereumJSONRPC.Geth do defp parse_call_tracer_calls([], acc, _trace_address, _inner?), do: acc defp parse_call_tracer_calls({%{"type" => 0}, _}, acc, _trace_address, _inner?), do: acc - defp parse_call_tracer_calls({%{"type" => "STOP"}, _}, [last | acc], _trace_address, _inner?) do + defp parse_call_tracer_calls({%{"type" => type}, _}, [last | acc], _trace_address, _inner?) + when type in ["STOP", "stop"] do [Map.put(last, "error", "execution stopped") | acc] end From 9b99970a7e10464e689c1e9b2b07fc278b440350 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Tue, 5 Dec 2023 14:55:13 +0600 Subject: [PATCH 112/607] Enable geth tests in CI --- .../test/ethereum_jsonrpc/geth_test.exs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs index 000b75623a03..a379ac57a816 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs @@ -5,8 +5,6 @@ defmodule EthereumJSONRPC.GethTest do alias EthereumJSONRPC.Geth - @moduletag :no_nethermind - setup :verify_on_exit! describe "fetch_internal_transactions/2" do @@ -359,6 +357,8 @@ defmodule EthereumJSONRPC.GethTest do ]} end) + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + call_tracer_internal_txs = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js", debug_trace_transaction_timeout: "5s") @@ -429,6 +429,8 @@ defmodule EthereumJSONRPC.GethTest do ]} end) + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + call_tracer_internal_txs = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js", debug_trace_transaction_timeout: "5s") @@ -467,6 +469,8 @@ defmodule EthereumJSONRPC.GethTest do ]} end) + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + assert {:ok, [ %{ @@ -518,6 +522,8 @@ defmodule EthereumJSONRPC.GethTest do ]} end) + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + uppercase_result = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn From c761c52eac0ad1ad79aae0db146fc6e745dd3896 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 29 Nov 2023 15:16:29 +0300 Subject: [PATCH 113/607] Solidityscan report API endpoint --- .dialyzer-ignore | 4 +- .github/workflows/config.yml | 26 +++++------ CHANGELOG.md | 4 +- .../api/v2/smart_contract_controller.ex | 45 ++++++++++++++++++- .../smart_contracts_api_v2_router.ex | 1 + .../block_scout_web/views/api/v2/helper.ex | 7 +-- .../views/api/v2/transaction_view.ex | 4 +- .../account/notifier/forbidden_address.ex | 22 ++++----- apps/explorer/lib/explorer/chain/address.ex | 10 +++++ .../lib/explorer/exchange_rates/source.ex | 11 ++--- apps/explorer/lib/explorer/helper.ex | 10 +++++ .../smart_contract/compiler_version.ex | 8 +--- .../third_party_integrations/solidityscan.ex | 40 +++++++++++++++++ .../third_party_integrations/sourcify.ex | 23 ++++------ config/runtime.exs | 4 ++ cspell.json | 13 +++++- 16 files changed, 164 insertions(+), 68 deletions(-) create mode 100644 apps/explorer/lib/explorer/third_party_integrations/solidityscan.ex diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 44a7f6862aba..20e4865a428f 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -13,8 +13,8 @@ lib/block_scout_web/schema/types.ex:31 lib/phoenix/router.ex:324 lib/phoenix/router.ex:402 lib/explorer/smart_contract/reader.ex:435 -lib/explorer/exchange_rates/source.ex:139 -lib/explorer/exchange_rates/source.ex:142 +lib/explorer/exchange_rates/source.ex:134 +lib/explorer/exchange_rates/source.ex:137 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 diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 31e89c223fa4..8ec57498b3dc 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -72,7 +72,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps- @@ -130,7 +130,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -154,7 +154,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -183,7 +183,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -227,7 +227,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -253,7 +253,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -282,7 +282,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -330,7 +330,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -376,7 +376,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -438,7 +438,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -498,7 +498,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -569,7 +569,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -637,7 +637,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" diff --git a/CHANGELOG.md b/CHANGELOG.md index 9200b4a88ecb..c464c468cb52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,15 @@ ## Current -- [#8924](https://github.com/blockscout/blockscout/pull/8924) - Delete invalid current token balances in OnDemand fetcher - ### Features +- [#8908](https://github.com/blockscout/blockscout/pull/8908) - Solidityscan report API endpoint - [#8900](https://github.com/blockscout/blockscout/pull/8900) - Add Compound proxy contract pattern - [#8611](https://github.com/blockscout/blockscout/pull/8611) - Implement sorting of smart contracts, address transactions ### Fixes +- [#8924](https://github.com/blockscout/blockscout/pull/8924) - Delete invalid current token balances in OnDemand fetcher - [#8922](https://github.com/blockscout/blockscout/pull/8922) - Allow call type to be in lowercase - [#8917](https://github.com/blockscout/blockscout/pull/8917) - Proxy detection hotfix in API v2 - [#8915](https://github.com/blockscout/blockscout/pull/8915) - smart-contract: delete embeds_many relation on replace diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex index 3fe7b47c26a9..3c68d73a09f2 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex @@ -12,9 +12,10 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do alias BlockScoutWeb.{AccessHelper, AddressView} alias Ecto.Association.NotLoaded alias Explorer.Chain - alias Explorer.Chain.SmartContract + alias Explorer.Chain.{Address, SmartContract} alias Explorer.SmartContract.{Reader, Writer} alias Explorer.SmartContract.Solidity.PublishHelper + alias Explorer.ThirdPartyIntegrations.SolidityScan @smart_contract_address_options [ necessity_by_association: %{ @@ -190,6 +191,48 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do end end + @doc """ + /api/v2/smart-contracts/${address_hash_string}/solidityscan-report logic + """ + @spec solidityscan_report(Plug.Conn.t(), map() | :atom) :: any() + def solidityscan_report(conn, %{"address_hash" => address_hash_string} = params) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), + {:ok, address} <- Chain.hash_to_address(address_hash), + {:is_smart_contract, true} <- {:is_smart_contract, Address.is_smart_contract(address)}, + response = SolidityScan.solidityscan_request(address_hash_string), + {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do + conn + |> put_status(200) + |> json(response) + else + {:format, :error} -> + conn + |> put_status(400) + |> json(%{status: "error", message: "Invalid address hash"}) + + {:restricted_access, true} -> + conn + |> put_status(403) + |> json(%{status: "error", message: "Access restricted"}) + + {:error, :not_found} -> + conn + |> put_status(404) + |> json(%{status: "error", message: "Address not found"}) + + {:is_smart_contract, false} -> + conn + |> put_status(404) + |> json(%{status: "error", message: "Smart-contract not found"}) + + {:is_empty_response, true} -> + conn + |> put_status(500) + |> json(%{status: "error", message: "Empty response"}) + end + end + def smart_contracts_list(conn, params) do full_options = [necessity_by_association: %{[address: :token] => :optional, [address: :names] => :optional, address: :required}] diff --git a/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex b/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex index d3f1d5e162f7..ad8bf8ad9609 100644 --- a/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex @@ -27,6 +27,7 @@ defmodule BlockScoutWeb.SmartContractsApiV2Router do get("/:address_hash/methods-read-proxy", V2.SmartContractController, :methods_read_proxy) get("/:address_hash/methods-write-proxy", V2.SmartContractController, :methods_write_proxy) post("/:address_hash/query-read-method", V2.SmartContractController, :query_read_method) + get("/:address_hash/solidityscan-report", V2.SmartContractController, :solidityscan_report) get("/verification/config", V2.VerificationController, :config) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex index 9b82e01ede77..9c490d9131fc 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex @@ -51,7 +51,7 @@ defmodule BlockScoutWeb.API.V2.Helper do defp address_with_info(%Address{} = address, _address_hash) do %{ "hash" => Address.checksum(address), - "is_contract" => is_smart_contract(address), + "is_contract" => Address.is_smart_contract(address), "name" => address_name(address), "implementation_name" => implementation_name(address), "is_verified" => is_verified(address) @@ -94,11 +94,6 @@ defmodule BlockScoutWeb.API.V2.Helper do def implementation_name(_), do: nil - def is_smart_contract(%Address{contract_code: nil}), do: false - def is_smart_contract(%Address{contract_code: _}), do: true - def is_smart_contract(%NotLoaded{}), do: nil - def is_smart_contract(_), do: false - def is_verified(%Address{smart_contract: nil}), do: false def is_verified(%Address{smart_contract: %{metadata_from_verified_twin: true}}), do: false def is_verified(%Address{smart_contract: %NotLoaded{}}), do: nil diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index c3e4d8288740..ac8e395011b6 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -707,7 +707,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do _, skip_sc_check? ) do - if skip_sc_check? || Helper.is_smart_contract(to_address) do + if skip_sc_check? || Address.is_smart_contract(to_address) do "0x" <> Base.encode16(method_id, case: :lower) else nil @@ -760,7 +760,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do defp tx_types(%Transaction{to_address: to_address} = tx, types, :contract_call) do types = - if Helper.is_smart_contract(to_address) do + if Address.is_smart_contract(to_address) do [:contract_call | types] else types diff --git a/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex b/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex index 6022c87fcc7d..b20cd898ed2a 100644 --- a/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex +++ b/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex @@ -5,16 +5,16 @@ defmodule Explorer.Account.Notifier.ForbiddenAddress do import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] + alias Explorer.Chain.Address + @blacklist [ burn_address_hash_string(), "0x000000000000000000000000000000000000dEaD" ] - alias Explorer.{AccessHelper, Repo} - alias Explorer.Chain.Token + alias Explorer.AccessHelper - import Ecto.Query, only: [from: 2] - import Explorer.Chain, only: [string_to_address_hash: 1] + import Explorer.Chain, only: [string_to_address_hash: 1, hash_to_address: 1] def check(address_string) when is_bitstring(address_string) do case format_address(address_string) do @@ -32,7 +32,7 @@ defmodule Explorer.Account.Notifier.ForbiddenAddress do {:error, "This address is blacklisted"} is_contract(address_hash) -> - {:error, "This address isn't personal"} + {:error, "This address isn't EOA"} match?({:restricted_access, true}, AccessHelper.restricted_access?(to_string(address_hash), %{})) -> {:error, "This address has restricted access"} @@ -43,14 +43,10 @@ defmodule Explorer.Account.Notifier.ForbiddenAddress do end defp is_contract(%Explorer.Chain.Hash{} = address_hash) do - query = - from( - token in Token, - where: token.contract_address_hash == ^address_hash - ) - - contract_addresses = Repo.all(query) - List.first(contract_addresses) + case hash_to_address(address_hash) do + {:error, :not_found} -> false + {:ok, address} -> Address.is_smart_contract(address) + end end defp format_address(address_hash_string) do diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index e8ea2300590a..96dc8c782c27 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -7,6 +7,7 @@ defmodule Explorer.Chain.Address do use Explorer.Schema + alias Ecto.Association.NotLoaded alias Ecto.Changeset alias Explorer.{Chain, PagingOptions} @@ -332,6 +333,15 @@ defmodule Explorer.Chain.Address do end end + @doc """ + Checks if given address is smart-contract + """ + @spec is_smart_contract(any()) :: boolean() | nil + def is_smart_contract(%__MODULE__{contract_code: nil}), do: false + def is_smart_contract(%__MODULE__{contract_code: _}), do: true + def is_smart_contract(%NotLoaded{}), do: nil + def is_smart_contract(_), do: false + defp get_addresses(options) do accounts_with_n = fetch_top_addresses(options) diff --git a/apps/explorer/lib/explorer/exchange_rates/source.ex b/apps/explorer/lib/explorer/exchange_rates/source.ex index befa6560d068..1323cd162834 100644 --- a/apps/explorer/lib/explorer/exchange_rates/source.ex +++ b/apps/explorer/lib/explorer/exchange_rates/source.ex @@ -6,6 +6,7 @@ defmodule Explorer.ExchangeRates.Source do alias Explorer.Chain.Hash alias Explorer.ExchangeRates.Source.CoinGecko alias Explorer.ExchangeRates.Token + alias Explorer.Helper alias HTTPoison.{Error, Response} @doc """ @@ -91,12 +92,6 @@ defmodule Explorer.ExchangeRates.Source do [{"Content-Type", "application/json"}] end - def decode_json(data) do - Jason.decode!(data) - rescue - _ -> data - end - def to_decimal(nil), do: nil def to_decimal(%Decimal{} = value), do: value @@ -145,7 +140,7 @@ defmodule Explorer.ExchangeRates.Source do end defp parse_http_success_response(body) do - body_json = decode_json(body) + body_json = Helper.decode_json(body) cond do is_map(body_json) -> @@ -160,7 +155,7 @@ defmodule Explorer.ExchangeRates.Source do end defp parse_http_error_response(body) do - body_json = decode_json(body) + body_json = Helper.decode_json(body) if is_map(body_json) do {:error, body_json["error"]} diff --git a/apps/explorer/lib/explorer/helper.ex b/apps/explorer/lib/explorer/helper.ex index 5004b1f26702..43b3dabd022d 100644 --- a/apps/explorer/lib/explorer/helper.ex +++ b/apps/explorer/lib/explorer/helper.ex @@ -58,4 +58,14 @@ defmodule Explorer.Helper do Enum.map(list, fn el -> Map.put(el, preload_field, associated_elements[el[foreign_key_field]]) end) end + + @doc """ + Decode json with rescue + """ + @spec decode_json(any()) :: any() + def decode_json(data) do + Jason.decode!(data) + rescue + _ -> data + end end diff --git a/apps/explorer/lib/explorer/smart_contract/compiler_version.ex b/apps/explorer/lib/explorer/smart_contract/compiler_version.ex index e3d2bcde8a07..248bfaa0781e 100644 --- a/apps/explorer/lib/explorer/smart_contract/compiler_version.ex +++ b/apps/explorer/lib/explorer/smart_contract/compiler_version.ex @@ -41,7 +41,7 @@ defmodule Explorer.SmartContract.CompilerVersion do {:ok, format_data(body, :solc)} {:ok, %{status_code: _status_code, body: body}} -> - {:error, decode_json(body)["error"]} + {:error, Helper.decode_json(body)["error"]} {:error, %{reason: reason}} -> {:error, reason} @@ -60,7 +60,7 @@ defmodule Explorer.SmartContract.CompilerVersion do {:ok, format_data(body, :vyper)} {:ok, %{status_code: _status_code, body: body}} -> - {:error, decode_json(body)["error"]} + {:error, Helper.decode_json(body)["error"]} {:error, %{reason: reason}} -> {:error, reason} @@ -140,10 +140,6 @@ defmodule Explorer.SmartContract.CompilerVersion do end end - defp decode_json(json) do - Jason.decode!(json) - end - @spec source_url(:solc | :vyper) :: String.t() defp source_url(compiler) do case compiler do diff --git a/apps/explorer/lib/explorer/third_party_integrations/solidityscan.ex b/apps/explorer/lib/explorer/third_party_integrations/solidityscan.ex new file mode 100644 index 000000000000..40a5bffb9784 --- /dev/null +++ b/apps/explorer/lib/explorer/third_party_integrations/solidityscan.ex @@ -0,0 +1,40 @@ +defmodule Explorer.ThirdPartyIntegrations.SolidityScan do + @moduledoc """ + Module for SolidityScan integration https://apidoc.solidityscan.com/solidityscan-security-api/solidityscan-other-apis/quickscan-api-v1 + """ + + alias Explorer.Helper + + @blockscout_platform_id "16" + @recv_timeout 60_000 + + @doc """ + Proxy request to solidityscan API endpoint for the given smart-contract + """ + @spec solidityscan_request(String.t()) :: any() + def solidityscan_request(address_hash_string) do + headers = [{"Authorization", "Token #{api_key()}"}] + + url = base_url(address_hash_string) + + case HTTPoison.get(url, headers, recv_timeout: @recv_timeout) do + {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> + Helper.decode_json(body) + + _ -> + nil + end + end + + defp base_url(address_hash_string) do + "https://api.solidityscan.com/api/v1/quickscan/#{@blockscout_platform_id}/#{chain_id()}/#{address_hash_string}" + end + + defp chain_id do + Application.get_env(:explorer, __MODULE__)[:chain_id] + end + + defp api_key do + Application.get_env(:explorer, __MODULE__)[:api_key] + end +end diff --git a/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex b/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex index b510fb355d49..95c22c84ceb8 100644 --- a/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex +++ b/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex @@ -4,6 +4,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do """ use Tesla + alias Explorer.Helper, as: ExplorerHelper alias Explorer.SmartContract.{Helper, RustVerifierInterface} alias HTTPoison.{Error, Response} alias Tesla.Multipart @@ -223,7 +224,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do end defp parse_verify_http_response(body) do - body_json = decode_json(body) + body_json = ExplorerHelper.decode_json(body) case body_json do # Success status from native Sourcify server @@ -246,7 +247,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do end defp parse_check_by_address_http_response(body) do - body_json = decode_json(body) + body_json = ExplorerHelper.decode_json(body) case body_json do [%{"status" => "perfect"}] -> @@ -264,11 +265,11 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do end defp parse_get_metadata_http_response(body) do - body_json = decode_json(body) + body_json = ExplorerHelper.decode_json(body) case body_json do %{"message" => message, "errors" => errors} -> - {:error, "#{message}: #{decode_json(errors)}"} + {:error, "#{message}: #{ExplorerHelper.decode_json(errors)}"} metadata -> {:ok, metadata} @@ -276,11 +277,11 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do end defp parse_get_metadata_any_http_response(body) do - body_json = decode_json(body) + body_json = ExplorerHelper.decode_json(body) case body_json do %{"message" => message, "errors" => errors} -> - {:error, "#{message}: #{decode_json(errors)}"} + {:error, "#{message}: #{ExplorerHelper.decode_json(errors)}"} %{"status" => status, "files" => metadata} -> {:ok, status, metadata} @@ -291,7 +292,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do end defp parse_http_error_response(body) do - body_json = decode_json(body) + body_json = ExplorerHelper.decode_json(body) if is_map(body_json) do {:error, body_json["error"]} @@ -350,7 +351,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do defp parse_json_from_sourcify_for_insertion(verification_metadata_json) do %{"name" => _, "content" => content} = verification_metadata_json - content_json = decode_json(content) + content_json = ExplorerHelper.decode_json(content) compiler_version = "v" <> (content_json |> Map.get("compiler") |> Map.get("version")) abi = content_json |> Map.get("output") |> Map.get("abi") settings = Map.get(content_json, "settings") @@ -401,12 +402,6 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do |> Map.put("contract_source_code", content) end - def decode_json(data) do - Jason.decode!(data) - rescue - _ -> data - end - defp config(module, key) do :explorer |> Application.get_env(module) diff --git a/config/runtime.exs b/config/runtime.exs index 81459925ee18..0f1ed1aa864a 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -368,6 +368,10 @@ config :explorer, Explorer.ThirdPartyIntegrations.Sourcify, chain_id: System.get_env("CHAIN_ID"), repo_url: System.get_env("SOURCIFY_REPO_URL") || "https://repo.sourcify.dev/contracts" +config :explorer, Explorer.ThirdPartyIntegrations.SolidityScan, + chain_id: System.get_env("SOLIDITYSCAN_CHAIN_ID"), + api_key: System.get_env("SOLIDITYSCAN_API_TOKEN") + enabled? = ConfigHelper.parse_bool_env_var("MICROSERVICE_SC_VERIFIER_ENABLED") # or "eth_bytecode_db" type = System.get_env("MICROSERVICE_SC_VERIFIER_TYPE", "sc_verifier") diff --git a/cspell.json b/cspell.json index b76de4bdc930..72bfc7609c21 100644 --- a/cspell.json +++ b/cspell.json @@ -534,7 +534,18 @@ "zindex", "zipcode", "zkbob", - "zkevm" + "zkevm", + "erts", + "Asfpp", + "Nerg", + "secp", + "qwertyuioiuytrewertyuioiuytrertyuio", + "urlset", + "lastmod", + "qitmeer", + "meer", + "DefiLlama", + "SOLIDITYSCAN" ], "enableFiletypes": [ "dotenv", From 5260f09ed710d0dd7bb7e5fd01c3ec1cc5f5635b Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 4 Dec 2023 22:42:29 +0300 Subject: [PATCH 114/607] Generalized decode_json function --- .../lib/explorer/exchange_rates/source.ex | 11 +------ apps/explorer/lib/explorer/helper.ex | 23 +++++++++++---- .../smart_contract/compiler_version.ex | 29 +++++++------------ .../token_instance/metadata_retriever.ex | 15 ++-------- 4 files changed, 32 insertions(+), 46 deletions(-) diff --git a/apps/explorer/lib/explorer/exchange_rates/source.ex b/apps/explorer/lib/explorer/exchange_rates/source.ex index 1323cd162834..7c58d7e7d710 100644 --- a/apps/explorer/lib/explorer/exchange_rates/source.ex +++ b/apps/explorer/lib/explorer/exchange_rates/source.ex @@ -142,16 +142,7 @@ defmodule Explorer.ExchangeRates.Source do defp parse_http_success_response(body) do body_json = Helper.decode_json(body) - cond do - is_map(body_json) -> - {:ok, body_json} - - is_list(body_json) -> - {:ok, body_json} - - true -> - {:ok, body} - end + {:ok, body_json} end defp parse_http_error_response(body) do diff --git a/apps/explorer/lib/explorer/helper.ex b/apps/explorer/lib/explorer/helper.ex index 43b3dabd022d..45d92a283068 100644 --- a/apps/explorer/lib/explorer/helper.ex +++ b/apps/explorer/lib/explorer/helper.ex @@ -60,12 +60,25 @@ defmodule Explorer.Helper do end @doc """ - Decode json with rescue + Decode json """ - @spec decode_json(any()) :: any() + @spec decode_json(any()) :: map() | list() | nil + def decode_json(nil), do: nil + def decode_json(data) do - Jason.decode!(data) - rescue - _ -> data + if String.valid?(data) do + safe_decode_json(data) + else + data + |> :unicode.characters_to_binary(:latin1) + |> safe_decode_json() + end + end + + defp safe_decode_json(data) do + case Jason.decode(data) do + {:ok, decoded} -> decoded + _ -> %{error: data} + end end end diff --git a/apps/explorer/lib/explorer/smart_contract/compiler_version.ex b/apps/explorer/lib/explorer/smart_contract/compiler_version.ex index 248bfaa0781e..0df293799cd1 100644 --- a/apps/explorer/lib/explorer/smart_contract/compiler_version.ex +++ b/apps/explorer/lib/explorer/smart_contract/compiler_version.ex @@ -31,33 +31,24 @@ defmodule Explorer.SmartContract.CompilerVersion do end defp fetch_solc_versions do - if RustVerifierInterface.enabled?() do - RustVerifierInterface.get_versions_list() - else - headers = [{"Content-Type", "application/json"}] - - case HTTPoison.get(source_url(:solc), headers) do - {:ok, %{status_code: 200, body: body}} -> - {:ok, format_data(body, :solc)} - - {:ok, %{status_code: _status_code, body: body}} -> - {:error, Helper.decode_json(body)["error"]} - - {:error, %{reason: reason}} -> - {:error, reason} - end - end + RustVerifierInterface.get_versions_list() + |> fetch_compiler_versions(:solc) end defp fetch_vyper_versions do + RustVerifierInterface.vyper_get_versions_list() + |> fetch_compiler_versions(:vyper) + end + + defp fetch_compiler_versions(compiler_list, compiler_type) do if RustVerifierInterface.enabled?() do - RustVerifierInterface.vyper_get_versions_list() + compiler_list else headers = [{"Content-Type", "application/json"}] - case HTTPoison.get(source_url(:vyper), headers) do + case HTTPoison.get(source_url(compiler_type), headers) do {:ok, %{status_code: 200, body: body}} -> - {:ok, format_data(body, :vyper)} + {:ok, format_data(body, compiler_type)} {:ok, %{status_code: _status_code, body: body}} -> {:error, Helper.decode_json(body)["error"]} diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex b/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex index 975b7bed5f87..191d81a15d4e 100644 --- a/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex +++ b/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex @@ -5,6 +5,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do require Logger + alias Explorer.Helper, as: ExplorerHelper alias Explorer.SmartContract.Reader alias HTTPoison.{Error, Response} @@ -130,7 +131,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do end defp fetch_json_from_uri({:ok, [json]}, hex_token_id) do - {:ok, json} = decode_json(json) + json = ExplorerHelper.decode_json(json) check_type(json, hex_token_id) rescue @@ -222,7 +223,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do check_type(json, nil) else - {:ok, json} = decode_json(body) + json = ExplorerHelper.decode_json(body) check_type(json, hex_token_id) end @@ -245,16 +246,6 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do content_type && String.starts_with?(content_type, "video/") end - defp decode_json(body) do - if String.valid?(body) do - Jason.decode(body) - else - body - |> :unicode.characters_to_binary(:latin1) - |> Jason.decode() - end - end - defp check_type(json, nil) when is_map(json) do {:ok, %{metadata: json}} end From 9ee7e9c7727e37e42f1906593326d601c84b1e1d Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 4 Dec 2023 23:42:09 +0300 Subject: [PATCH 115/607] Extend fallback controller for API v2 in order to handle responses from solidityscan api endpoint --- .../controllers/api/v2/fallback_controller.ex | 24 +++++++++++++++ .../api/v2/smart_contract_controller.ex | 29 ++----------------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex index 56e66c7d0a44..49851c4744e1 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex @@ -23,6 +23,9 @@ defmodule BlockScoutWeb.API.V2.FallbackController do @unauthorized "Unauthorized" @not_configured_api_key "API key not configured on the server" @wrong_api_key "Wrong API key" + @address_not_found "Address not found" + @address_is_not_smart_contract "Address is not smart-contract" + @empty_response "Empty response" def call(conn, {:format, _params}) do Logger.error(fn -> @@ -232,4 +235,25 @@ defmodule BlockScoutWeb.API.V2.FallbackController do |> put_view(ApiView) |> render(:message, %{message: @wrong_api_key}) end + + def call(conn, {:address, {:error, :not_found}}) do + conn + |> put_status(:not_found) + |> put_view(ApiView) + |> render(:message, %{message: @address_not_found}) + end + + def call(conn, {:is_smart_contract, false}) do + conn + |> put_status(:not_found) + |> put_view(ApiView) + |> render(:message, %{message: @address_is_not_smart_contract}) + end + + def call(conn, {:is_empty_response, true}) do + conn + |> put_status(500) + |> put_view(ApiView) + |> render(:message, %{message: @empty_response}) + end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex index 3c68d73a09f2..82c767cd8929 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex @@ -196,40 +196,15 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do """ @spec solidityscan_report(Plug.Conn.t(), map() | :atom) :: any() def solidityscan_report(conn, %{"address_hash" => address_hash_string} = params) do - with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + with {:format_address, {:ok, address_hash}} <- {:format_address, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - {:ok, address} <- Chain.hash_to_address(address_hash), + {:address, {:ok, address}} <- {:address, Chain.hash_to_address(address_hash)}, {:is_smart_contract, true} <- {:is_smart_contract, Address.is_smart_contract(address)}, response = SolidityScan.solidityscan_request(address_hash_string), {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do conn |> put_status(200) |> json(response) - else - {:format, :error} -> - conn - |> put_status(400) - |> json(%{status: "error", message: "Invalid address hash"}) - - {:restricted_access, true} -> - conn - |> put_status(403) - |> json(%{status: "error", message: "Access restricted"}) - - {:error, :not_found} -> - conn - |> put_status(404) - |> json(%{status: "error", message: "Address not found"}) - - {:is_smart_contract, false} -> - conn - |> put_status(404) - |> json(%{status: "error", message: "Smart-contract not found"}) - - {:is_empty_response, true} -> - conn - |> put_status(500) - |> json(%{status: "error", message: "Empty response"}) end end From 1c5da633156c5c1e79dd6829d6cdb7f7057d0b35 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Tue, 5 Dec 2023 12:31:00 +0300 Subject: [PATCH 116/607] Update apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex Co-authored-by: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> --- .../controllers/api/v2/smart_contract_controller.ex | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex index 82c767cd8929..21da41d4e3e7 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex @@ -194,7 +194,13 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do @doc """ /api/v2/smart-contracts/${address_hash_string}/solidityscan-report logic """ - @spec solidityscan_report(Plug.Conn.t(), map() | :atom) :: any() + @spec solidityscan_report(Plug.Conn.t(), map()) :: + {:address, {:error, :not_found}} + | {:format_address, :error} + | {:is_empty_response, true} + | {:is_smart_contract, false | nil} + | {:restricted_access, true} + | Plug.Conn.t() def solidityscan_report(conn, %{"address_hash" => address_hash_string} = params) do with {:format_address, {:ok, address_hash}} <- {:format_address, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), From 1076f441d7e5f05df1abcc279045808890247fbd Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 5 Dec 2023 12:45:29 +0300 Subject: [PATCH 117/607] Process reviewer comments --- .dialyzer-ignore | 2 -- .../controllers/api/v2/fallback_controller.ex | 2 +- .../controllers/api/v2/smart_contract_controller.ex | 2 +- apps/explorer/lib/explorer/exchange_rates/source.ex | 6 ------ .../lib/explorer/smart_contract/compiler_version.ex | 10 ++++------ 5 files changed, 6 insertions(+), 16 deletions(-) diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 20e4865a428f..1acfc060457c 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -13,8 +13,6 @@ lib/block_scout_web/schema/types.ex:31 lib/phoenix/router.ex:324 lib/phoenix/router.ex:402 lib/explorer/smart_contract/reader.ex:435 -lib/explorer/exchange_rates/source.ex:134 -lib/explorer/exchange_rates/source.ex:137 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 diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex index 49851c4744e1..691d8de75b91 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex @@ -243,7 +243,7 @@ defmodule BlockScoutWeb.API.V2.FallbackController do |> render(:message, %{message: @address_not_found}) end - def call(conn, {:is_smart_contract, false}) do + def call(conn, {:is_smart_contract, result}) when is_nil(result) or result == false do conn |> put_status(:not_found) |> put_view(ApiView) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex index 21da41d4e3e7..4986c3a19ad5 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex @@ -194,7 +194,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do @doc """ /api/v2/smart-contracts/${address_hash_string}/solidityscan-report logic """ - @spec solidityscan_report(Plug.Conn.t(), map()) :: + @spec solidityscan_report(Plug.Conn.t(), map()) :: {:address, {:error, :not_found}} | {:format_address, :error} | {:is_empty_response, true} diff --git a/apps/explorer/lib/explorer/exchange_rates/source.ex b/apps/explorer/lib/explorer/exchange_rates/source.ex index 7c58d7e7d710..8d50d4069bec 100644 --- a/apps/explorer/lib/explorer/exchange_rates/source.ex +++ b/apps/explorer/lib/explorer/exchange_rates/source.ex @@ -130,12 +130,6 @@ defmodule Explorer.ExchangeRates.Source do {:error, %Error{reason: reason}} -> {:error, reason} - - {:error, :nxdomain} -> - {:error, "Source is not responsive"} - - {:error, _} -> - {:error, "Source unknown response"} end end diff --git a/apps/explorer/lib/explorer/smart_contract/compiler_version.ex b/apps/explorer/lib/explorer/smart_contract/compiler_version.ex index 0df293799cd1..3a0e898ff5e2 100644 --- a/apps/explorer/lib/explorer/smart_contract/compiler_version.ex +++ b/apps/explorer/lib/explorer/smart_contract/compiler_version.ex @@ -31,18 +31,16 @@ defmodule Explorer.SmartContract.CompilerVersion do end defp fetch_solc_versions do - RustVerifierInterface.get_versions_list() - |> fetch_compiler_versions(:solc) + fetch_compiler_versions(&RustVerifierInterface.get_versions_list/0, :solc) end defp fetch_vyper_versions do - RustVerifierInterface.vyper_get_versions_list() - |> fetch_compiler_versions(:vyper) + fetch_compiler_versions(&RustVerifierInterface.vyper_get_versions_list/0, :vyper) end - defp fetch_compiler_versions(compiler_list, compiler_type) do + defp fetch_compiler_versions(compiler_list_fn, compiler_type) do if RustVerifierInterface.enabled?() do - compiler_list + compiler_list_fn.() else headers = [{"Content-Type", "application/json"}] From 9d4bdf963cfe00ef39ab6338366349b469451969 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 5 Dec 2023 14:33:49 +0300 Subject: [PATCH 118/607] Process nil response --- .../lib/explorer/third_party_integrations/sourcify.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex b/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex index 95c22c84ceb8..f8385a397b5b 100644 --- a/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex +++ b/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex @@ -297,7 +297,11 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do if is_map(body_json) do {:error, body_json["error"]} else - {:error, body} + if is_nil(body) do + {:error, "invalid http error json response"} + else + {:error, body} + end end end From 91d583bede36bee56ad22691d59774f9de63331a Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 5 Dec 2023 18:16:20 +0300 Subject: [PATCH 119/607] Enhance parse_http_error_response response --- .../explorer/third_party_integrations/sourcify.ex | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex b/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex index f8385a397b5b..605a50547ebd 100644 --- a/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex +++ b/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex @@ -291,20 +291,23 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do end end + @invalid_json_response "invalid http error json response" defp parse_http_error_response(body) do body_json = ExplorerHelper.decode_json(body) if is_map(body_json) do - {:error, body_json["error"]} + error = body_json["error"] + + parse_http_error_response_internal(error) else - if is_nil(body) do - {:error, "invalid http error json response"} - else - {:error, body} - end + parse_http_error_response_internal(body) end end + defp parse_http_error_response_internal(nil), do: {:error, @invalid_json_response} + + defp parse_http_error_response_internal(data), do: {:error, data} + def parse_params_from_sourcify(address_hash_string, verification_metadata) do filtered_files = verification_metadata From b02558048aeebc7c707e18148e77c9379fc94fe4 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 27 Nov 2023 23:17:49 +0300 Subject: [PATCH 120/607] Fix method decoding by candidates --- CHANGELOG.md | 1 + .../lib/block_scout_web/views/address_view.ex | 2 +- .../views/api/v2/transaction_view.ex | 2 +- .../lib/explorer/chain/contract_method.ex | 13 ++ apps/explorer/lib/explorer/chain/log.ex | 163 ++++++++++-------- .../lib/explorer/chain/transaction.ex | 9 +- .../explorer/test/explorer/chain/log_test.exs | 6 +- 7 files changed, 112 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9200b4a88ecb..19b3408c33cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - [#8917](https://github.com/blockscout/blockscout/pull/8917) - Proxy detection hotfix in API v2 - [#8915](https://github.com/blockscout/blockscout/pull/8915) - smart-contract: delete embeds_many relation on replace - [#8906](https://github.com/blockscout/blockscout/pull/8906) - Fix abi encoded string argument +- [#8898](https://github.com/blockscout/blockscout/pull/8898) - Enhance method decoding by candidates from DB - [#8882](https://github.com/blockscout/blockscout/pull/8882) - Change order of proxy contracts patterns detection: existing popular EIPs to the top of the list - [#8707](https://github.com/blockscout/blockscout/pull/8707) - Fix native coin exchange rate with `EXCHANGE_RATES_COINGECKO_COIN_ID` diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex index af46f31ca25c..b77ca5abbb3a 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex @@ -498,7 +498,7 @@ defmodule BlockScoutWeb.AddressView do def contract_interaction_disabled?, do: Application.get_env(:block_scout_web, :contract)[:disable_interaction] def decode(log, transaction) do - {result, _contracts_acc, _events_acc} = Log.decode(log, transaction, [], true) + {result, _events_acc} = Log.decode(log, transaction, [], true) result end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index c3e4d8288740..aca45c2c9fa2 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -187,7 +187,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do def decode_logs(logs, skip_sig_provider?) do {result, _, _} = Enum.reduce(logs, {[], %{}, %{}}, fn log, {results, contracts_acc, events_acc} -> - {result, contracts_acc, events_acc} = + {result, events_acc} = Log.decode( log, %Transaction{hash: log.transaction_hash}, diff --git a/apps/explorer/lib/explorer/chain/contract_method.ex b/apps/explorer/lib/explorer/chain/contract_method.ex index 05a82406c95d..dd1cf0a80923 100644 --- a/apps/explorer/lib/explorer/chain/contract_method.ex +++ b/apps/explorer/lib/explorer/chain/contract_method.ex @@ -5,6 +5,7 @@ defmodule Explorer.Chain.ContractMethod do require Logger + import Ecto.Query, only: [from: 2] use Explorer.Schema alias Explorer.Chain.{Hash, MethodIdentifier, SmartContract} @@ -69,6 +70,18 @@ defmodule Explorer.Chain.ContractMethod do end end + @doc """ + Finds limited number of contract methods by selector id + """ + @spec find_contract_method_query(binary(), integer()) :: Ecto.Query.t() + def find_contract_method_query(method_id, limit) do + from( + contract_method in __MODULE__, + where: contract_method.identifier == ^method_id, + limit: ^limit + ) + end + defp abi_element_to_contract_method(element) do case ABI.parse_specification([element], include_events?: true) do [selector] -> diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 3b04958fdcb7..36a4a670ae51 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -7,7 +7,7 @@ defmodule Explorer.Chain.Log do alias ABI.{Event, FunctionSelector} alias Explorer.Chain - alias Explorer.Chain.{Address, Block, ContractMethod, Data, Hash, Transaction} + alias Explorer.Chain.{Address, Block, ContractMethod, Data, Hash, Log, Transaction} alias Explorer.Chain.SmartContract.Proxy alias Explorer.SmartContract.SigProviderInterface @@ -121,34 +121,55 @@ defmodule Explorer.Chain.Log do @doc """ Decode transaction log data. """ - + @spec decode(Log.t(), Transaction.t(), any(), boolean, map(), map()) :: + {{:ok, String.t(), String.t(), map()} + | {:error, atom()} + | {:error, atom(), list()} + | {{:error, :contract_not_verified, list()}, any()}, map()} def decode(log, transaction, options, skip_sig_provider?, contracts_acc \\ %{}, events_acc \\ %{}) do - case check_cache(contracts_acc, log.address_hash, options) do - {nil, contracts_acc} -> - {result, events_acc} = find_candidates(log, transaction, options, events_acc) - {result, contracts_acc, events_acc} - - {full_abi, contracts_acc} -> - with {:ok, selector, mapping} <- find_and_decode(full_abi, log, transaction), - identifier <- Base.encode16(selector.method_id, case: :lower), - text <- function_call(selector.function, mapping) do - {{:ok, identifier, text, mapping}, contracts_acc, events_acc} - else - {:error, :could_not_decode} -> - case find_candidates(log, transaction, options, events_acc) do - {{:error, :contract_not_verified, []}, events_acc} -> - {decode_event_via_sig_provider(log, transaction, false, skip_sig_provider?), contracts_acc, events_acc} + with full_abi <- check_cache(contracts_acc, log.address_hash, options), + {:no_abi, false} <- {:no_abi, is_nil(full_abi)}, + {:ok, selector, mapping} <- find_and_decode(full_abi, log, transaction.hash), + identifier <- Base.encode16(selector.method_id, case: :lower), + text <- function_call(selector.function, mapping) do + {{:ok, identifier, text, mapping}, events_acc} + else + {:error, _} = error -> + handle_method_decode_error(error, log, transaction, options, skip_sig_provider?, events_acc) + + {:no_abi, true} -> + handle_method_decode_error( + {:error, :could_not_decode}, + log, + transaction, + options, + skip_sig_provider?, + events_acc + ) + end + end - {{:error, :contract_not_verified, candidates}, events_acc} -> - {{:error, :contract_verified, candidates}, contracts_acc, events_acc} + defp handle_method_decode_error(error, log, transaction, options, skip_sig_provider?, events_acc) do + case error do + {:error, :could_not_decode} -> + case find_method_candidates(log, transaction, options, events_acc) do + {{:error, :contract_not_verified, []}, events_acc} -> + {decode_event_via_sig_provider(log, transaction, false, options, events_acc, skip_sig_provider?), + events_acc} - {_, events_acc} -> - {decode_event_via_sig_provider(log, transaction, false, skip_sig_provider?), contracts_acc, events_acc} - end + {{:error, :contract_not_verified, candidates}, events_acc} -> + {{:error, :contract_not_verified, candidates}, events_acc} - {:error, reason} -> - {{:error, reason}, contracts_acc, events_acc} + {_, events_acc} -> + {decode_event_via_sig_provider(log, transaction, false, options, events_acc, skip_sig_provider?), + events_acc} end + + {:error, :no_matching_function} -> + {decode_event_via_sig_provider(log, transaction, false, options, events_acc, skip_sig_provider?), events_acc} + + {:error, reason} -> + {{:error, reason}, events_acc} end end @@ -162,49 +183,42 @@ defmodule Explorer.Chain.Log do |> Keyword.merge(options) if !is_nil(address_hash) && Map.has_key?(acc, address_hash) do - {acc[address_hash], acc} + acc[address_hash] else case Chain.find_contract_address(address_hash, address_options, false) do {:ok, %{smart_contract: smart_contract}} -> - full_abi = Proxy.combine_proxy_implementation_abi(smart_contract, options) - {full_abi, Map.put(acc, address_hash, full_abi)} + Proxy.combine_proxy_implementation_abi(smart_contract, options) _ -> - {nil, Map.put(acc, address_hash, nil)} + nil end end end - defp find_candidates(log, transaction, options, events_acc) do - case log.first_topic do - "0x" <> hex_part -> - case Integer.parse(hex_part, 16) do - {number, ""} -> - <> = :binary.encode_unsigned(number) - check_events_cache(events_acc, method_id, log, transaction, options) - - _ -> - {{:error, :could_not_decode}, events_acc} - end + defp find_method_candidates(log, transaction, options, events_acc) do + with "0x" <> hex_part <- log.first_topic, + {number, ""} <- Integer.parse(hex_part, 16) do + <> = :binary.encode_unsigned(number) - _ -> - {{:error, :could_not_decode}, events_acc} + if Map.has_key?(events_acc, method_id) do + {events_acc[method_id], events_acc} + else + result = find_method_candidates_from_db(method_id, log, transaction, options, events_acc) + {result, Map.put(events_acc, method_id, result)} + end + else + _ -> {{:error, :could_not_decode}, events_acc} end end - defp find_candidates_query(method_id, log, transaction, options) do - candidates_query = - from( - contract_method in ContractMethod, - where: contract_method.identifier == ^method_id, - limit: 3 - ) + defp find_method_candidates_from_db(method_id, log, transaction, options, events_acc) do + candidates_query = ContractMethod.find_contract_method_query(method_id, 3) candidates = candidates_query |> Chain.select_repo(options).all() |> Enum.flat_map(fn contract_method -> - case find_and_decode([contract_method.abi], log, transaction) do + case find_and_decode([contract_method.abi], log, transaction.hash) do {:ok, selector, mapping} -> identifier = Base.encode16(selector.method_id, case: :lower) text = function_call(selector.function, mapping) @@ -218,21 +232,15 @@ defmodule Explorer.Chain.Log do |> Enum.take(1) {:error, :contract_not_verified, - if(candidates == [], do: decode_event_via_sig_provider(log, transaction, true), else: candidates)} - end - - defp check_events_cache(events_acc, method_id, log, transaction, options) do - if Map.has_key?(events_acc, method_id) do - {events_acc[method_id], events_acc} - else - result = find_candidates_query(method_id, log, transaction, options) - {result, Map.put(events_acc, method_id, result)} - end + if(candidates == [], + do: decode_event_via_sig_provider(log, transaction, true, options, events_acc), + else: candidates + )} end - @spec find_and_decode([map()], __MODULE__.t(), Transaction.t()) :: + @spec find_and_decode([map()], __MODULE__.t(), Hash.t()) :: {:error, any} | {:ok, ABI.FunctionSelector.t(), any} - def find_and_decode(abi, log, transaction) do + def find_and_decode(abi, log, transaction_hash) do with {%FunctionSelector{} = selector, mapping} <- abi |> ABI.parse_specification(include_events?: true) @@ -249,8 +257,8 @@ defmodule Explorer.Chain.Log do e -> Logger.warn(fn -> [ - "Could not decode input data for log from transaction: ", - Hash.to_iodata(transaction.hash), + "Could not decode input data for log from transaction hash: ", + Hash.to_iodata(transaction_hash), Exception.format(:error, e, __STACKTRACE__) ] end) @@ -262,12 +270,7 @@ defmodule Explorer.Chain.Log do text = mapping |> Stream.map(fn {name, type, indexed?, _value} -> - indexed_keyword = - if indexed? do - ["indexed "] - else - [] - end + indexed_keyword = if indexed?, do: ["indexed "], else: [] [type, " ", indexed_keyword, name] end) @@ -276,7 +279,14 @@ defmodule Explorer.Chain.Log do IO.iodata_to_binary([name, "(", text, ")"]) end - defp decode_event_via_sig_provider(log, transaction, only_candidates?, skip_sig_provider? \\ false) do + defp decode_event_via_sig_provider( + log, + transaction, + only_candidates?, + options, + events_acc, + skip_sig_provider? \\ false + ) do with true <- SigProviderInterface.enabled?(), false <- skip_sig_provider?, {:ok, result} <- @@ -292,7 +302,7 @@ defmodule Explorer.Chain.Log do true <- is_list(result), false <- Enum.empty?(result), abi <- [result |> List.first() |> Map.put("type", "event")], - {:ok, selector, mapping} <- find_and_decode(abi, log, transaction), + {:ok, selector, mapping} <- find_and_decode(abi, log, transaction.hash), identifier <- Base.encode16(selector.method_id, case: :lower), text <- function_call(selector.function, mapping) do if only_candidates? do @@ -305,7 +315,16 @@ defmodule Explorer.Chain.Log do if only_candidates? do [] else - {:error, :could_not_decode} + case find_method_candidates(log, transaction, options, events_acc) do + {{:error, :contract_not_verified, []}, _} -> + {:error, :could_not_decode} + + {{:error, :contract_not_verified, candidates}, _} -> + {:error, :contract_not_verified, candidates} + + {_, _} -> + {:error, :could_not_decode} + end end end end diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 78ef9a202564..1f7e67790bdc 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -774,12 +774,7 @@ defmodule Explorer.Chain.Transaction do if Map.has_key?(methods_acc, method_id) do {methods_acc[method_id], methods_acc} else - candidates_query = - from( - contract_method in ContractMethod, - where: contract_method.identifier == ^method_id, - limit: 1 - ) + candidates_query = ContractMethod.find_contract_method_query(method_id, 1) result = candidates_query @@ -1181,7 +1176,7 @@ defmodule Explorer.Chain.Transaction do Enum.map_reduce(transactions, %{}, fn transaction, tokens_acc -> case Log.fetch_log_by_tx_hash_and_first_topic(transaction.hash, @transaction_fee_event_signature, @api_true) do fee_log when not is_nil(fee_log) -> - {:ok, _selector, mapping} = Log.find_and_decode(@transaction_fee_event_abi, fee_log, transaction) + {:ok, _selector, mapping} = Log.find_and_decode(@transaction_fee_event_abi, fee_log, transaction.hash) [{"token", "address", false, token_address_hash}, _, _, _, _, _] = mapping diff --git a/apps/explorer/test/explorer/chain/log_test.exs b/apps/explorer/test/explorer/chain/log_test.exs index 6842a6b979fb..3454fe15c5e2 100644 --- a/apps/explorer/test/explorer/chain/log_test.exs +++ b/apps/explorer/test/explorer/chain/log_test.exs @@ -60,7 +60,7 @@ defmodule Explorer.Chain.LogTest do log = insert(:log, transaction: transaction) - assert {{:error, :could_not_decode}, _, _} = Log.decode(log, transaction, [], false) + assert {{:error, :could_not_decode}, _} = Log.decode(log, transaction, [], false) end test "that a contract call transaction that has a verified contract returns the decoded input data" do @@ -116,7 +116,7 @@ defmodule Explorer.Chain.LogTest do 152, 206, 227, 92, 181, 213, 23, 242, 210, 150, 162>>}}, {"_number", "uint256", false, 0}, {"_belly", "bool", true, true} - ]}, _, _} = Log.decode(log, transaction, [], false) + ]}, _} = Log.decode(log, transaction, [], false) end test "finds decoding candidates" do @@ -171,7 +171,7 @@ defmodule Explorer.Chain.LogTest do {"_number", "uint256", false, 0}, {"_belly", "bool", true, true} ]} - ]}, _, _} = Log.decode(log, transaction, [], false) + ]}, _} = Log.decode(log, transaction, [], false) end end From 120dbf63d86a4b95446adc5fa782d989da8b6987 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 4 Dec 2023 20:32:29 +0300 Subject: [PATCH 121/607] Return accumulators in order to reduce contract calls --- .../lib/block_scout_web/views/address_view.ex | 10 ++++++- .../views/api/v2/transaction_view.ex | 4 +-- apps/explorer/lib/explorer/chain/log.ex | 29 ++++++++++--------- .../explorer/chain/smart_contract/proxy.ex | 4 +++ .../explorer/test/explorer/chain/log_test.exs | 6 ++-- 5 files changed, 34 insertions(+), 19 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex index b77ca5abbb3a..ed3f54040657 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex @@ -497,8 +497,16 @@ defmodule BlockScoutWeb.AddressView do def contract_interaction_disabled?, do: Application.get_env(:block_scout_web, :contract)[:disable_interaction] + @doc """ + Decodes given log + """ + @spec decode(Log.t(), Transaction.t()) :: + {:ok, String.t(), String.t(), map()} + | {:error, atom()} + | {:error, atom(), list()} + | {{:error, :contract_not_verified, list()}, any()} def decode(log, transaction) do - {result, _events_acc} = Log.decode(log, transaction, [], true) + {result, _contracts_acc, _events_acc} = Log.decode(log, transaction, [], true) result end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index aca45c2c9fa2..54d15112a789 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -184,10 +184,10 @@ defmodule BlockScoutWeb.API.V2.TransactionView do } end - def decode_logs(logs, skip_sig_provider?) do + defp decode_logs(logs, skip_sig_provider?) do {result, _, _} = Enum.reduce(logs, {[], %{}, %{}}, fn log, {results, contracts_acc, events_acc} -> - {result, events_acc} = + {result, contracts_acc, events_acc} = Log.decode( log, %Transaction{hash: log.transaction_hash}, diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 36a4a670ae51..1f4230850594 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -125,17 +125,17 @@ defmodule Explorer.Chain.Log do {{:ok, String.t(), String.t(), map()} | {:error, atom()} | {:error, atom(), list()} - | {{:error, :contract_not_verified, list()}, any()}, map()} + | {{:error, :contract_not_verified, list()}, any()}, map(), map()} def decode(log, transaction, options, skip_sig_provider?, contracts_acc \\ %{}, events_acc \\ %{}) do - with full_abi <- check_cache(contracts_acc, log.address_hash, options), + with {full_abi, contracts_acc} <- check_cache(contracts_acc, log.address_hash, options), {:no_abi, false} <- {:no_abi, is_nil(full_abi)}, {:ok, selector, mapping} <- find_and_decode(full_abi, log, transaction.hash), identifier <- Base.encode16(selector.method_id, case: :lower), text <- function_call(selector.function, mapping) do - {{:ok, identifier, text, mapping}, events_acc} + {{:ok, identifier, text, mapping}, contracts_acc, events_acc} else {:error, _} = error -> - handle_method_decode_error(error, log, transaction, options, skip_sig_provider?, events_acc) + handle_method_decode_error(error, log, transaction, options, skip_sig_provider?, contracts_acc, events_acc) {:no_abi, true} -> handle_method_decode_error( @@ -144,32 +144,34 @@ defmodule Explorer.Chain.Log do transaction, options, skip_sig_provider?, + contracts_acc, events_acc ) end end - defp handle_method_decode_error(error, log, transaction, options, skip_sig_provider?, events_acc) do + defp handle_method_decode_error(error, log, transaction, options, skip_sig_provider?, contracts_acc, events_acc) do case error do {:error, :could_not_decode} -> case find_method_candidates(log, transaction, options, events_acc) do {{:error, :contract_not_verified, []}, events_acc} -> {decode_event_via_sig_provider(log, transaction, false, options, events_acc, skip_sig_provider?), - events_acc} + contracts_acc, events_acc} {{:error, :contract_not_verified, candidates}, events_acc} -> - {{:error, :contract_not_verified, candidates}, events_acc} + {{:error, :contract_not_verified, candidates}, contracts_acc, events_acc} {_, events_acc} -> {decode_event_via_sig_provider(log, transaction, false, options, events_acc, skip_sig_provider?), - events_acc} + contracts_acc, events_acc} end {:error, :no_matching_function} -> - {decode_event_via_sig_provider(log, transaction, false, options, events_acc, skip_sig_provider?), events_acc} + {decode_event_via_sig_provider(log, transaction, false, options, events_acc, skip_sig_provider?), contracts_acc, + events_acc} {:error, reason} -> - {{:error, reason}, events_acc} + {{:error, reason}, contracts_acc, events_acc} end end @@ -183,14 +185,15 @@ defmodule Explorer.Chain.Log do |> Keyword.merge(options) if !is_nil(address_hash) && Map.has_key?(acc, address_hash) do - acc[address_hash] + {acc[address_hash], acc} else case Chain.find_contract_address(address_hash, address_options, false) do {:ok, %{smart_contract: smart_contract}} -> - Proxy.combine_proxy_implementation_abi(smart_contract, options) + full_abi = Proxy.combine_proxy_implementation_abi(smart_contract, options) + {full_abi, Map.put(acc, address_hash, full_abi)} _ -> - nil + {nil, Map.put(acc, address_hash, nil)} end end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex index a5c9ce5a4e09..cbd6dd4ee55f 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -267,6 +267,10 @@ defmodule Explorer.Chain.SmartContract.Proxy do end) end + @doc """ + Returns combined ABI from proxy and implementation smart-contracts + """ + @spec combine_proxy_implementation_abi(any(), any()) :: SmartContract.abi() def combine_proxy_implementation_abi(smart_contract, options \\ []) def combine_proxy_implementation_abi(%SmartContract{abi: abi} = smart_contract, options) when not is_nil(abi) do diff --git a/apps/explorer/test/explorer/chain/log_test.exs b/apps/explorer/test/explorer/chain/log_test.exs index 3454fe15c5e2..6842a6b979fb 100644 --- a/apps/explorer/test/explorer/chain/log_test.exs +++ b/apps/explorer/test/explorer/chain/log_test.exs @@ -60,7 +60,7 @@ defmodule Explorer.Chain.LogTest do log = insert(:log, transaction: transaction) - assert {{:error, :could_not_decode}, _} = Log.decode(log, transaction, [], false) + assert {{:error, :could_not_decode}, _, _} = Log.decode(log, transaction, [], false) end test "that a contract call transaction that has a verified contract returns the decoded input data" do @@ -116,7 +116,7 @@ defmodule Explorer.Chain.LogTest do 152, 206, 227, 92, 181, 213, 23, 242, 210, 150, 162>>}}, {"_number", "uint256", false, 0}, {"_belly", "bool", true, true} - ]}, _} = Log.decode(log, transaction, [], false) + ]}, _, _} = Log.decode(log, transaction, [], false) end test "finds decoding candidates" do @@ -171,7 +171,7 @@ defmodule Explorer.Chain.LogTest do {"_number", "uint256", false, 0}, {"_belly", "bool", true, true} ]} - ]}, _} = Log.decode(log, transaction, [], false) + ]}, _, _} = Log.decode(log, transaction, [], false) end end From 16307adbe57db76ad7050e8fe650c402370f23d8 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 5 Dec 2023 19:03:01 +0300 Subject: [PATCH 122/607] Exclude sig provider from fallback method decoding using db --- apps/explorer/lib/explorer/chain/log.ex | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 1f4230850594..9f1458c93d6f 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -198,7 +198,7 @@ defmodule Explorer.Chain.Log do end end - defp find_method_candidates(log, transaction, options, events_acc) do + defp find_method_candidates(log, transaction, options, events_acc, skip_sig_provider \\ false) do with "0x" <> hex_part <- log.first_topic, {number, ""} <- Integer.parse(hex_part, 16) do <> = :binary.encode_unsigned(number) @@ -206,7 +206,7 @@ defmodule Explorer.Chain.Log do if Map.has_key?(events_acc, method_id) do {events_acc[method_id], events_acc} else - result = find_method_candidates_from_db(method_id, log, transaction, options, events_acc) + result = find_method_candidates_from_db(method_id, log, transaction, options, events_acc, skip_sig_provider) {result, Map.put(events_acc, method_id, result)} end else @@ -214,7 +214,7 @@ defmodule Explorer.Chain.Log do end end - defp find_method_candidates_from_db(method_id, log, transaction, options, events_acc) do + defp find_method_candidates_from_db(method_id, log, transaction, options, events_acc, skip_sig_provider \\ false) do candidates_query = ContractMethod.find_contract_method_query(method_id, 3) candidates = @@ -236,7 +236,11 @@ defmodule Explorer.Chain.Log do {:error, :contract_not_verified, if(candidates == [], - do: decode_event_via_sig_provider(log, transaction, true, options, events_acc), + do: + if(skip_sig_provider, + do: [], + else: decode_event_via_sig_provider(log, transaction, true, options, events_acc) + ), else: candidates )} end @@ -318,7 +322,9 @@ defmodule Explorer.Chain.Log do if only_candidates? do [] else - case find_method_candidates(log, transaction, options, events_acc) do + skip_sig_provider = true + + case find_method_candidates(log, transaction, options, events_acc, skip_sig_provider) do {{:error, :contract_not_verified, []}, _} -> {:error, :could_not_decode} From 7727d93d64396f4c83a69dd28a91b7ec231e5303 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 5 Dec 2023 19:08:05 +0300 Subject: [PATCH 123/607] Finally resolve decoding issue in right way --- .dialyzer-ignore | 4 +- .../templates/address_logs/_logs.html.eex | 75 +++++++++---------- .../views/api/v2/transaction_view.ex | 1 - apps/block_scout_web/priv/gettext/default.pot | 4 +- .../priv/gettext/en/LC_MESSAGES/default.po | 4 +- apps/explorer/lib/explorer/chain/log.ex | 42 +++-------- 6 files changed, 52 insertions(+), 78 deletions(-) diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 44a7f6862aba..8a8ea811eb21 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -26,5 +26,5 @@ 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:1457 -lib/explorer/chain/transaction.ex:1458 +lib/explorer/chain/transaction.ex:1452 +lib/explorer/chain/transaction.ex:1453 diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex index eaa5765bc29b..9e213f9457f0 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex @@ -27,46 +27,43 @@ ) %> - <%= case decoded_result do %> - <% {:error, :could_not_decode} -> %> -
<%= gettext "Decoded" %>
-
-
- <%= gettext "Failed to decode log data." %> -
- <% {:ok, method_id, text, mapping} -> %> -
<%= gettext "Decoded" %>
-
- - - - - - - - - -
Method Id0x<%= method_id %>
Call<%= text %>
- <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %> - <% {:error, :contract_not_verified, results} -> %> - <%= for {:ok, method_id, text, mapping} <- results do %> -
<%= gettext "Decoded" %>
-
- - - - - - - - - -
Method Id0x<%= method_id %>
Call<%= text %>
- <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %> - + <%= case decoded_result do %> + <% {:error, :could_not_decode} -> %> +
<%= gettext "Decoded" %>
+
+
+ <%= gettext "Failed to decode log data." %> +
+ <% {:ok, method_id, text, mapping} -> %> +
<%= gettext "Decoded" %>
+
+ + + + + + + + + +
Method Id0x<%= method_id %>
Call<%= text %>
+ <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %> + <% {:error, :contract_not_verified, results} -> %> + <%= for {:ok, method_id, text, mapping} <- results do %> +
<%= gettext "Decoded" %>
+
+ + + + + + + + + +
Method Id0x<%= method_id %>
Call<%= text %>
+ <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %> <% end %> - <% _ -> %> - <%= nil %> <% end %>
<%= gettext "Topics" %>
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index 54d15112a789..aaec94b44027 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -648,7 +648,6 @@ defmodule BlockScoutWeb.API.V2.TransactionView do defp format_decoded_input(_), do: nil defp format_decoded_log_input({:error, :could_not_decode}), do: nil - defp format_decoded_log_input({:error, :no_matching_function}), do: nil defp format_decoded_log_input({:ok, _method_id, _text, _mapping} = decoded), do: decoded defp format_decoded_log_input({:error, _, candidates}), do: Enum.at(candidates, 0) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 7ef46448585b..c67d50ebf77a 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -1049,7 +1049,7 @@ msgstr "" msgid "Daily Transactions" msgstr "" -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:101 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:98 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:121 @@ -2962,7 +2962,7 @@ msgstr "" msgid "Topic" msgstr "" -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:71 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:68 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:91 #, elixir-autogen, elixir-format msgid "Topics" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 2f650d468712..0797f9e126fd 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -1049,7 +1049,7 @@ msgstr "" msgid "Daily Transactions" msgstr "" -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:101 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:98 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:121 @@ -2962,7 +2962,7 @@ msgstr "" msgid "Topic" msgstr "" -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:71 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:68 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:91 #, elixir-autogen, elixir-format msgid "Topics" diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 9f1458c93d6f..2eb2c2ce9275 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -152,26 +152,17 @@ defmodule Explorer.Chain.Log do defp handle_method_decode_error(error, log, transaction, options, skip_sig_provider?, contracts_acc, events_acc) do case error do - {:error, :could_not_decode} -> - case find_method_candidates(log, transaction, options, events_acc) do + {:error, _reason} -> + case find_method_candidates(log, transaction, options, events_acc, skip_sig_provider?) do {{:error, :contract_not_verified, []}, events_acc} -> - {decode_event_via_sig_provider(log, transaction, false, options, events_acc, skip_sig_provider?), - contracts_acc, events_acc} + {decode_event_via_sig_provider(log, transaction, false, skip_sig_provider?), contracts_acc, events_acc} {{:error, :contract_not_verified, candidates}, events_acc} -> {{:error, :contract_not_verified, candidates}, contracts_acc, events_acc} {_, events_acc} -> - {decode_event_via_sig_provider(log, transaction, false, options, events_acc, skip_sig_provider?), - contracts_acc, events_acc} + {decode_event_via_sig_provider(log, transaction, false, skip_sig_provider?), contracts_acc, events_acc} end - - {:error, :no_matching_function} -> - {decode_event_via_sig_provider(log, transaction, false, options, events_acc, skip_sig_provider?), contracts_acc, - events_acc} - - {:error, reason} -> - {{:error, reason}, contracts_acc, events_acc} end end @@ -198,7 +189,7 @@ defmodule Explorer.Chain.Log do end end - defp find_method_candidates(log, transaction, options, events_acc, skip_sig_provider \\ false) do + 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 <> = :binary.encode_unsigned(number) @@ -206,7 +197,7 @@ defmodule Explorer.Chain.Log do if Map.has_key?(events_acc, method_id) do {events_acc[method_id], events_acc} else - result = find_method_candidates_from_db(method_id, log, transaction, options, events_acc, skip_sig_provider) + result = find_method_candidates_from_db(method_id, log, transaction, options, skip_sig_provider?) {result, Map.put(events_acc, method_id, result)} end else @@ -214,7 +205,7 @@ defmodule Explorer.Chain.Log do end end - defp find_method_candidates_from_db(method_id, log, transaction, options, events_acc, skip_sig_provider \\ false) do + defp find_method_candidates_from_db(method_id, log, transaction, options, skip_sig_provider?) do candidates_query = ContractMethod.find_contract_method_query(method_id, 3) candidates = @@ -237,9 +228,9 @@ defmodule Explorer.Chain.Log do {:error, :contract_not_verified, if(candidates == [], do: - if(skip_sig_provider, + if(skip_sig_provider?, do: [], - else: decode_event_via_sig_provider(log, transaction, true, options, events_acc) + else: decode_event_via_sig_provider(log, transaction, true) ), else: candidates )} @@ -290,8 +281,6 @@ defmodule Explorer.Chain.Log do log, transaction, only_candidates?, - options, - events_acc, skip_sig_provider? \\ false ) do with true <- SigProviderInterface.enabled?(), @@ -322,18 +311,7 @@ defmodule Explorer.Chain.Log do if only_candidates? do [] else - skip_sig_provider = true - - case find_method_candidates(log, transaction, options, events_acc, skip_sig_provider) do - {{:error, :contract_not_verified, []}, _} -> - {:error, :could_not_decode} - - {{:error, :contract_not_verified, candidates}, _} -> - {:error, :contract_not_verified, candidates} - - {_, _} -> - {:error, :could_not_decode} - end + {:error, :could_not_decode} end end end From 1c7e19bd242baa23245d925f2f84be6d026cf020 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 6 Dec 2023 11:56:46 +0300 Subject: [PATCH 124/607] Blockscout v5.3.3 --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/workflows/prerelease-main.yml | 2 +- .../publish-docker-image-every-push.yml | 2 +- .../publish-docker-image-for-core.yml | 2 +- .../publish-docker-image-for-eth-goerli.yml | 2 +- .../publish-docker-image-for-eth-sepolia.yml | 2 +- .../publish-docker-image-for-eth.yml | 2 +- .../publish-docker-image-for-fuse.yml | 2 +- .../publish-docker-image-for-immutable.yml | 2 +- .../publish-docker-image-for-l2-staging.yml | 2 +- .../publish-docker-image-for-lukso.yml | 2 +- .../publish-docker-image-for-optimism.yml | 2 +- .../publish-docker-image-for-polygon-edge.yml | 2 +- .../publish-docker-image-for-rsk.yml | 2 +- .../publish-docker-image-for-stability.yml | 2 +- .../publish-docker-image-for-suave.yml | 2 +- .../publish-docker-image-for-xdai.yml | 2 +- .../publish-docker-image-for-zkevm.yml | 2 +- .../publish-docker-image-for-zksync.yml | 2 +- ...publish-docker-image-staging-on-demand.yml | 2 +- .github/workflows/release-additional.yml | 2 +- .github/workflows/release-main.yml | 2 +- CHANGELOG.md | 39 +++++++++++++++++++ apps/block_scout_web/mix.exs | 2 +- apps/ethereum_jsonrpc/mix.exs | 2 +- .../helper.ex | 2 +- apps/explorer/mix.exs | 2 +- apps/indexer/mix.exs | 2 +- docker-compose/docker-compose.yml | 2 +- docker/Makefile | 2 +- mix.exs | 2 +- rel/config.exs | 2 +- 32 files changed, 70 insertions(+), 31 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 677dd9604aa1..00b3285f4567 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -65,7 +65,7 @@ body: attributes: label: Backend version description: The release version of the backend or branch/commit. - placeholder: v5.3.2 + placeholder: v5.3.3 validations: required: true diff --git a/.github/workflows/prerelease-main.yml b/.github/workflows/prerelease-main.yml index 57890b3ffb9b..e2fc82079bb7 100644 --- a/.github/workflows/prerelease-main.yml +++ b/.github/workflows/prerelease-main.yml @@ -16,7 +16,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 steps: - name: Check out the repo uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-every-push.yml b/.github/workflows/publish-docker-image-every-push.yml index a5ebce6f57f6..bd66d1110256 100644 --- a/.github/workflows/publish-docker-image-every-push.yml +++ b/.github/workflows/publish-docker-image-every-push.yml @@ -11,7 +11,7 @@ on: env: OTP_VERSION: '25.2.1' ELIXIR_VERSION: '1.14.5' - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 jobs: push_to_registry: diff --git a/.github/workflows/publish-docker-image-for-core.yml b/.github/workflows/publish-docker-image-for-core.yml index b90942317baf..184bed4b4a8e 100644 --- a/.github/workflows/publish-docker-image-for-core.yml +++ b/.github/workflows/publish-docker-image-for-core.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 DOCKER_CHAIN_NAME: poa steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-eth-goerli.yml b/.github/workflows/publish-docker-image-for-eth-goerli.yml index 28b51f91c9ed..174318b6c963 100644 --- a/.github/workflows/publish-docker-image-for-eth-goerli.yml +++ b/.github/workflows/publish-docker-image-for-eth-goerli.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 DOCKER_CHAIN_NAME: eth-goerli steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-eth-sepolia.yml b/.github/workflows/publish-docker-image-for-eth-sepolia.yml index 28bedb51ee75..d0d1a6281f04 100644 --- a/.github/workflows/publish-docker-image-for-eth-sepolia.yml +++ b/.github/workflows/publish-docker-image-for-eth-sepolia.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 DOCKER_CHAIN_NAME: eth-sepolia steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-eth.yml b/.github/workflows/publish-docker-image-for-eth.yml index bfd8ae15198d..af99aa32d184 100644 --- a/.github/workflows/publish-docker-image-for-eth.yml +++ b/.github/workflows/publish-docker-image-for-eth.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 DOCKER_CHAIN_NAME: mainnet steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-fuse.yml b/.github/workflows/publish-docker-image-for-fuse.yml index b0d1eb68ff1e..4b0221b8e685 100644 --- a/.github/workflows/publish-docker-image-for-fuse.yml +++ b/.github/workflows/publish-docker-image-for-fuse.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 DOCKER_CHAIN_NAME: fuse steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-immutable.yml b/.github/workflows/publish-docker-image-for-immutable.yml index d86f7025cbc8..6dd6dccde603 100644 --- a/.github/workflows/publish-docker-image-for-immutable.yml +++ b/.github/workflows/publish-docker-image-for-immutable.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 DOCKER_CHAIN_NAME: immutable steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-l2-staging.yml b/.github/workflows/publish-docker-image-for-l2-staging.yml index a6227eaee50a..744d4f4694c3 100644 --- a/.github/workflows/publish-docker-image-for-l2-staging.yml +++ b/.github/workflows/publish-docker-image-for-l2-staging.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 DOCKER_CHAIN_NAME: optimism-l2-advanced steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-lukso.yml b/.github/workflows/publish-docker-image-for-lukso.yml index f294165468c7..53f2f88e258f 100644 --- a/.github/workflows/publish-docker-image-for-lukso.yml +++ b/.github/workflows/publish-docker-image-for-lukso.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 DOCKER_CHAIN_NAME: lukso steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-optimism.yml b/.github/workflows/publish-docker-image-for-optimism.yml index c164e1b7d8eb..f1371cc268c3 100644 --- a/.github/workflows/publish-docker-image-for-optimism.yml +++ b/.github/workflows/publish-docker-image-for-optimism.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 DOCKER_CHAIN_NAME: optimism steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-polygon-edge.yml b/.github/workflows/publish-docker-image-for-polygon-edge.yml index 3b3f7604ca6e..ccd20ef78d10 100644 --- a/.github/workflows/publish-docker-image-for-polygon-edge.yml +++ b/.github/workflows/publish-docker-image-for-polygon-edge.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 DOCKER_CHAIN_NAME: polygon-edge steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-rsk.yml b/.github/workflows/publish-docker-image-for-rsk.yml index d47134f2ae40..d482fed9e90c 100644 --- a/.github/workflows/publish-docker-image-for-rsk.yml +++ b/.github/workflows/publish-docker-image-for-rsk.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 DOCKER_CHAIN_NAME: rsk steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-stability.yml b/.github/workflows/publish-docker-image-for-stability.yml index 089e5924e3d4..218b8fa3e067 100644 --- a/.github/workflows/publish-docker-image-for-stability.yml +++ b/.github/workflows/publish-docker-image-for-stability.yml @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 DOCKER_CHAIN_NAME: stability steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-suave.yml b/.github/workflows/publish-docker-image-for-suave.yml index b91bb91e546f..85836106b629 100644 --- a/.github/workflows/publish-docker-image-for-suave.yml +++ b/.github/workflows/publish-docker-image-for-suave.yml @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 DOCKER_CHAIN_NAME: suave steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-xdai.yml b/.github/workflows/publish-docker-image-for-xdai.yml index 947a675c57fe..7a69fd7e9f21 100644 --- a/.github/workflows/publish-docker-image-for-xdai.yml +++ b/.github/workflows/publish-docker-image-for-xdai.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 DOCKER_CHAIN_NAME: xdai steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-zkevm.yml b/.github/workflows/publish-docker-image-for-zkevm.yml index 32a17480e8e5..9fed1cc04083 100644 --- a/.github/workflows/publish-docker-image-for-zkevm.yml +++ b/.github/workflows/publish-docker-image-for-zkevm.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 DOCKER_CHAIN_NAME: zkevm steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-zksync.yml b/.github/workflows/publish-docker-image-for-zksync.yml index 8d02b444c17a..1688bb4f761e 100644 --- a/.github/workflows/publish-docker-image-for-zksync.yml +++ b/.github/workflows/publish-docker-image-for-zksync.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 DOCKER_CHAIN_NAME: zksync steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-staging-on-demand.yml b/.github/workflows/publish-docker-image-staging-on-demand.yml index ad6b96a3240b..246a4876362a 100644 --- a/.github/workflows/publish-docker-image-staging-on-demand.yml +++ b/.github/workflows/publish-docker-image-staging-on-demand.yml @@ -12,7 +12,7 @@ on: env: OTP_VERSION: '25.2.1' ELIXIR_VERSION: '1.14.5' - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 jobs: push_to_registry: diff --git a/.github/workflows/release-additional.yml b/.github/workflows/release-additional.yml index 72c287e93730..892a6b6ead38 100644 --- a/.github/workflows/release-additional.yml +++ b/.github/workflows/release-additional.yml @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 steps: - name: Check out the repo uses: actions/checkout@v4 diff --git a/.github/workflows/release-main.yml b/.github/workflows/release-main.yml index 60c840a7f70b..d8808170ab9e 100644 --- a/.github/workflows/release-main.yml +++ b/.github/workflows/release-main.yml @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 steps: - name: Check out the repo uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index cbc0ae33d974..87dce1db56cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ ### Features +### Fixes + +### Chore + +
+ Dependencies version bumps +
+ +## 5.3.3-beta + +### Features + - [#8908](https://github.com/blockscout/blockscout/pull/8908) - Solidityscan report API endpoint - [#8900](https://github.com/blockscout/blockscout/pull/8900) - Add Compound proxy contract pattern - [#8611](https://github.com/blockscout/blockscout/pull/8611) - Implement sorting of smart contracts, address transactions @@ -23,6 +35,33 @@ - [#8911](https://github.com/blockscout/blockscout/pull/8911) - Set client_connection_check_interval for main Postgres DB in docker-compose setup +
+ Dependencies version bumps + +- [#8863](https://github.com/blockscout/blockscout/pull/8863) - Bump core-js from 3.33.2 to 3.33.3 in /apps/block_scout_web/assets +- [#8864](https://github.com/blockscout/blockscout/pull/8864) - Bump @amplitude/analytics-browser from 2.3.3 to 2.3.5 in /apps/block_scout_web/assets +- [#8860](https://github.com/blockscout/blockscout/pull/8860) - Bump ecto_sql from 3.10.2 to 3.11.0 +- [#8896](https://github.com/blockscout/blockscout/pull/8896) - Bump httpoison from 2.2.0 to 2.2.1 +- [#8867](https://github.com/blockscout/blockscout/pull/8867) - Bump mixpanel-browser from 2.47.0 to 2.48.1 in /apps/block_scout_web/assets +- [#8865](https://github.com/blockscout/blockscout/pull/8865) - Bump eslint from 8.53.0 to 8.54.0 in /apps/block_scout_web/assets +- [#8866](https://github.com/blockscout/blockscout/pull/8866) - Bump sweetalert2 from 11.9.0 to 11.10.1 in /apps/block_scout_web/assets +- [#8897](https://github.com/blockscout/blockscout/pull/8897) - Bump prometheus from 4.10.0 to 4.11.0 +- [#8859](https://github.com/blockscout/blockscout/pull/8859) - Bump absinthe from 1.7.5 to 1.7.6 +- [#8858](https://github.com/blockscout/blockscout/pull/8858) - Bump ex_json_schema from 0.10.1 to 0.10.2 +- [#8943](https://github.com/blockscout/blockscout/pull/8943) - Bump postgrex from 0.17.3 to 0.17.4 +- [#8939](https://github.com/blockscout/blockscout/pull/8939) - Bump @babel/core from 7.23.3 to 7.23.5 in /apps/block_scout_web/assets +- [#8936](https://github.com/blockscout/blockscout/pull/8936) - Bump eslint from 8.54.0 to 8.55.0 in /apps/block_scout_web/assets +- [#8940](https://github.com/blockscout/blockscout/pull/8940) - Bump photoswipe from 5.4.2 to 5.4.3 in /apps/block_scout_web/assets +- [#8938](https://github.com/blockscout/blockscout/pull/8938) - Bump @babel/preset-env from 7.23.3 to 7.23.5 in /apps/block_scout_web/assets +- [#8935](https://github.com/blockscout/blockscout/pull/8935) - Bump @amplitude/analytics-browser from 2.3.5 to 2.3.6 in /apps/block_scout_web/assets +- [#8941](https://github.com/blockscout/blockscout/pull/8941) - Bump ueberauth from 0.10.5 to 0.10.6 +- [#8937](https://github.com/blockscout/blockscout/pull/8937) - Bump redux from 4.2.1 to 5.0.0 in /apps/block_scout_web/assets +- [#8942](https://github.com/blockscout/blockscout/pull/8942) - Bump gettext from 0.23.1 to 0.24.0 +- [#8934](https://github.com/blockscout/blockscout/pull/8934) - Bump @fortawesome/fontawesome-free from 6.4.2 to 6.5.1 in /apps/block_scout_web/assets +- [#8933](https://github.com/blockscout/blockscout/pull/8933) - Bump postcss from 8.4.31 to 8.4.32 in /apps/block_scout_web/assets + +
+ ## 5.3.2-beta ### Features diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index 2270ffaabdbb..18dd4fa02057 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -23,7 +23,7 @@ defmodule BlockScoutWeb.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "5.3.2", + version: "5.3.3", xref: [exclude: [Explorer.Chain.Zkevm.Reader]] ] end diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index 608b2bb28b38..9445c500133a 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -23,7 +23,7 @@ defmodule EthereumJsonrpc.MixProject do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "5.3.2" + version: "5.3.3" ] end diff --git a/apps/explorer/lib/explorer/token_instance_owner_address_migration/helper.ex b/apps/explorer/lib/explorer/token_instance_owner_address_migration/helper.ex index 7b0a092245f0..66beb03dd4ea 100644 --- a/apps/explorer/lib/explorer/token_instance_owner_address_migration/helper.ex +++ b/apps/explorer/lib/explorer/token_instance_owner_address_migration/helper.ex @@ -52,7 +52,7 @@ defmodule Explorer.TokenInstanceOwnerAddressMigration.Helper do ) token_transfer = - Repo.one(token_transfer_query) || + Repo.one(token_transfer_query, timeout: :timer.minutes(1)) || %{to_address_hash: @burn_address_hash, block_number: -1, log_index: -1} %{ diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index fd91cf515e59..492b774aabcc 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -24,7 +24,7 @@ defmodule Explorer.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "5.3.2", + version: "5.3.3", xref: [exclude: [BlockScoutWeb.WebRouter.Helpers]] ] end diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs index e3454f3318eb..fcb2d9513961 100644 --- a/apps/indexer/mix.exs +++ b/apps/indexer/mix.exs @@ -14,7 +14,7 @@ defmodule Indexer.MixProject do elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", start_permanent: Mix.env() == :prod, - version: "5.3.2" + version: "5.3.3" ] end diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 0ce792f80ded..3198bf8998c4 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -34,7 +34,7 @@ services: CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED: "" CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL: "" ADMIN_PANEL_ENABLED: "" - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 5.3.3 links: - db:database environment: diff --git a/docker/Makefile b/docker/Makefile index e320e4c98356..231bcfe7aed0 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -10,7 +10,7 @@ STATS_CONTAINER_NAME := stats STATS_DB_CONTAINER_NAME := stats-postgres PROXY_CONTAINER_NAME := proxy PG_CONTAINER_NAME := postgres -RELEASE_VERSION ?= '5.3.2' +RELEASE_VERSION ?= '5.3.3' TAG := $(RELEASE_VERSION)-commit-$(shell git log -1 --pretty=format:"%h") STABLE_TAG := $(RELEASE_VERSION) diff --git a/mix.exs b/mix.exs index f3a4838a8b3d..09f45065856d 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule BlockScout.Mixfile do [ # app: :block_scout, # aliases: aliases(config_env()), - version: "5.3.2", + version: "5.3.3", apps_path: "apps", deps: deps(), dialyzer: dialyzer(), diff --git a/rel/config.exs b/rel/config.exs index 86844487f4aa..62745d29f472 100644 --- a/rel/config.exs +++ b/rel/config.exs @@ -71,7 +71,7 @@ end # will be used by default release :blockscout do - set version: "5.3.2-beta" + set version: "5.3.3-beta" set applications: [ :runtime_tools, block_scout_web: :permanent, From f35cf942d339b8a009c7d961e813928e6ca1de72 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 6 Dec 2023 11:59:25 +0300 Subject: [PATCH 125/607] Rename pre-release CI --- .github/workflows/{prerelease-main.yml => prerelease.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{prerelease-main.yml => prerelease.yml} (95%) diff --git a/.github/workflows/prerelease-main.yml b/.github/workflows/prerelease.yml similarity index 95% rename from .github/workflows/prerelease-main.yml rename to .github/workflows/prerelease.yml index e2fc82079bb7..d3f7579dc033 100644 --- a/.github/workflows/prerelease-main.yml +++ b/.github/workflows/prerelease.yml @@ -1,4 +1,4 @@ -name: Pre-release main +name: Pre-release master on: workflow_dispatch: From 6fb7ffdd52b12f80de5d1d840a57fb147ace3e67 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 6 Dec 2023 12:23:43 +0300 Subject: [PATCH 126/607] Revert ueberauth update (back to 0.10.5) --- CHANGELOG.md | 1 - mix.lock | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87dce1db56cc..cc73d9452694 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,7 +54,6 @@ - [#8940](https://github.com/blockscout/blockscout/pull/8940) - Bump photoswipe from 5.4.2 to 5.4.3 in /apps/block_scout_web/assets - [#8938](https://github.com/blockscout/blockscout/pull/8938) - Bump @babel/preset-env from 7.23.3 to 7.23.5 in /apps/block_scout_web/assets - [#8935](https://github.com/blockscout/blockscout/pull/8935) - Bump @amplitude/analytics-browser from 2.3.5 to 2.3.6 in /apps/block_scout_web/assets -- [#8941](https://github.com/blockscout/blockscout/pull/8941) - Bump ueberauth from 0.10.5 to 0.10.6 - [#8937](https://github.com/blockscout/blockscout/pull/8937) - Bump redux from 4.2.1 to 5.0.0 in /apps/block_scout_web/assets - [#8942](https://github.com/blockscout/blockscout/pull/8942) - Bump gettext from 0.23.1 to 0.24.0 - [#8934](https://github.com/blockscout/blockscout/pull/8934) - Bump @fortawesome/fontawesome-free from 6.4.2 to 6.5.1 in /apps/block_scout_web/assets diff --git a/mix.lock b/mix.lock index 12d09b27706e..2db07ac8ff34 100644 --- a/mix.lock +++ b/mix.lock @@ -136,7 +136,7 @@ "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, "toml": {:hex, :toml, "0.6.2", "38f445df384a17e5d382befe30e3489112a48d3ba4c459e543f748c2f25dd4d1", [:mix], [], "hexpm", "d013e45126d74c0c26a38d31f5e8e9b83ea19fc752470feb9a86071ca5a672fa"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, - "ueberauth": {:hex, :ueberauth, "0.10.6", "8dbefd5aec30c5830af2b6ce6e03f62cc28ae0757f34e2986454f54b8dca3f65", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b0ad1c7508f3cfd5c2c1c668d1a32bafd77de4c56af82c7bfd7e54ed078a7928"}, + "ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"}, "ueberauth_auth0": {:hex, :ueberauth_auth0, "2.1.0", "0632d5844049fa2f26823f15e1120aa32f27df6f27ce515a4b04641736594bf4", [:mix], [{:oauth2, "~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "8d3b30fa27c95c9e82c30c4afb016251405706d2e9627e603c3c9787fd1314fc"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "wallaby": {:hex, :wallaby, "0.30.6", "7dc4c1213f3b52c4152581d126632bc7e06892336d3a0f582853efeeabd45a71", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "50950c1d968549b54c20e16175c68c7fc0824138e2bb93feb11ef6add8eb23d4"}, From 97097a23bf0818e366bdd589b49bd4f60b2e75eb Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 6 Dec 2023 13:55:12 +0300 Subject: [PATCH 127/607] Refine docker-compose config structure --- docker-compose/README.md | 22 +++--- .../docker-compose-no-build-nethermind.yml | 74 ------------------- docker-compose/docker-compose.yml | 22 +++--- ...compose-no-build-erigon.yml => erigon.yml} | 22 +++--- ...ernal-backend.yml => external-backend.yml} | 20 ++--- ...ld-no-db-container.yml => external-db.yml} | 18 ++--- ...nal-frontend.yml => external-frontend.yml} | 20 ++--- ...mpose-no-build-ganache.yml => ganache.yml} | 22 +++--- ...onsensus.yml => geth-clique-consensus.yml} | 22 +++--- ...ker-compose-no-build-geth.yml => geth.yml} | 22 +++--- ...ardhat-network.yml => hardhat-network.yml} | 22 +++--- docker-compose/microservices.yml | 34 +++++++++ ...docker-compose-backend.yml => backend.yml} | 2 +- .../{docker-compose-db.yml => db.yml} | 0 ...cker-compose-frontend.yml => frontend.yml} | 0 .../{docker-compose-nginx.yml => nginx.yml} | 0 .../{docker-compose-redis.yml => redis.yml} | 0 ...pose-sig-provider.yml => sig-provider.yml} | 0 ...rifier.yml => smart-contract-verifier.yml} | 0 .../{docker-compose-stats.yml => stats.yml} | 0 ...-compose-visualizer.yml => visualizer.yml} | 0 docker/Makefile | 14 ++-- 22 files changed, 150 insertions(+), 186 deletions(-) delete mode 100644 docker-compose/docker-compose-no-build-nethermind.yml rename docker-compose/{docker-compose-no-build-erigon.yml => erigon.yml} (61%) rename docker-compose/{docker-compose-no-build-external-backend.yml => external-backend.yml} (55%) rename docker-compose/{docker-compose-no-build-no-db-container.yml => external-db.yml} (62%) rename docker-compose/{docker-compose-no-build-external-frontend.yml => external-frontend.yml} (65%) rename docker-compose/{docker-compose-no-build-ganache.yml => ganache.yml} (66%) rename docker-compose/{docker-compose-no-build-geth-clique-consensus.yml => geth-clique-consensus.yml} (62%) rename docker-compose/{docker-compose-no-build-geth.yml => geth.yml} (61%) rename docker-compose/{docker-compose-no-build-hardhat-network.yml => hardhat-network.yml} (64%) create mode 100644 docker-compose/microservices.yml rename docker-compose/services/{docker-compose-backend.yml => backend.yml} (79%) rename docker-compose/services/{docker-compose-db.yml => db.yml} (100%) rename docker-compose/services/{docker-compose-frontend.yml => frontend.yml} (100%) rename docker-compose/services/{docker-compose-nginx.yml => nginx.yml} (100%) rename docker-compose/services/{docker-compose-redis.yml => redis.yml} (100%) rename docker-compose/services/{docker-compose-sig-provider.yml => sig-provider.yml} (100%) rename docker-compose/services/{docker-compose-smart-contract-verifier.yml => smart-contract-verifier.yml} (100%) rename docker-compose/services/{docker-compose-stats.yml => stats.yml} (100%) rename docker-compose/services/{docker-compose-visualizer.yml => visualizer.yml} (100%) diff --git a/docker-compose/README.md b/docker-compose/README.md index c212e1cde760..cc472ebd4c40 100644 --- a/docker-compose/README.md +++ b/docker-compose/README.md @@ -39,15 +39,19 @@ The repo contains built-in configs for different JSON RPC clients without need t **Note**: in all below examples, you can use `docker compose` instead of `docker-compose`, if compose v2 plugin is installed in Docker. -- Erigon: `docker-compose -f docker-compose-no-build-erigon.yml up -d` -- Geth (suitable for Reth as well): `docker-compose -f docker-compose-no-build-geth.yml up -d` -- Geth Clique: `docker-compose -f docker-compose-no-build-geth-clique-consensus.yml up -d` -- Nethermind, OpenEthereum: `docker-compose -f docker-compose-no-build-nethermind up -d` -- Ganache: `docker-compose -f docker-compose-no-build-ganache.yml up -d` -- HardHat network: `docker-compose -f docker-compose-no-build-hardhat-network.yml up -d` -- Running only explorer without DB: `docker-compose -f docker-compose-no-build-no-db-container.yml up -d`. In this case, one container is created - for the explorer itself. And it assumes that the DB credentials are provided through `DATABASE_URL` environment variable. -- Running explorer with external backend: `docker-compose -f docker-compose-no-build-external-backend.yml up -d` -- Running explorer with external frontend: `docker-compose -f docker-compose-no-build-external-frontend.yml up -d` +| __JSON RPC Client__ | __Docker compose launch command__ | +| -------- | ------- | +| Erigon | `docker-compose -f erigon.yml up -d` | +| Geth (suitable for Reth as well) | `docker-compose -f geth.yml up -d` | +| Geth Clique | `docker-compose -f geth-clique-consensus.yml up -d` | +| Nethermind, OpenEthereum | `docker-compose -f nethermind up -d` | +| Ganache | `docker-compose -f ganache.yml up -d` | +| HardHat network | `docker-compose -f hardhat-network.yml up -d` | + +- Running only explorer without DB: `docker-compose -f external-db.yml up -d`. In this case, no db container is created. And it assumes that the DB credentials are provided through `DATABASE_URL` environment variable on the backend container. +- Running explorer with external backend: `docker-compose -f external-backend.yml up -d` +- Running explorer with external frontend: `docker-compose -f external-frontend.yml up -d` +- Running all microservices: `docker-compose -f microservices.yml up -d` All of the configs assume the Ethereum JSON RPC is running at http://localhost:8545. diff --git a/docker-compose/docker-compose-no-build-nethermind.yml b/docker-compose/docker-compose-no-build-nethermind.yml deleted file mode 100644 index 77c5e5eef9b7..000000000000 --- a/docker-compose/docker-compose-no-build-nethermind.yml +++ /dev/null @@ -1,74 +0,0 @@ -version: '3.9' - -services: - redis_db: - extends: - file: ./services/docker-compose-redis.yml - service: redis_db - - db-init: - extends: - file: ./services/docker-compose-db.yml - service: db-init - - db: - extends: - file: ./services/docker-compose-db.yml - service: db - - backend: - depends_on: - - db - - redis_db - extends: - file: ./services/docker-compose-backend.yml - service: backend - links: - - db:database - environment: - ETHEREUM_JSONRPC_VARIANT: 'nethermind' - - visualizer: - extends: - file: ./services/docker-compose-visualizer.yml - service: visualizer - - sig-provider: - extends: - file: ./services/docker-compose-sig-provider.yml - service: sig-provider - - frontend: - depends_on: - - backend - extends: - file: ./services/docker-compose-frontend.yml - service: frontend - - stats-db-init: - extends: - file: ./services/docker-compose-stats.yml - service: stats-db-init - - stats-db: - depends_on: - - backend - extends: - file: ./services/docker-compose-stats.yml - service: stats-db - - stats: - depends_on: - - stats-db - extends: - file: ./services/docker-compose-stats.yml - service: stats - - proxy: - depends_on: - - backend - - frontend - - stats - extends: - file: ./services/docker-compose-nginx.yml - service: proxy diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 3198bf8998c4..f700df419c25 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -3,17 +3,17 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db db-init: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db-init db: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db backend: @@ -21,7 +21,7 @@ services: - db - redis_db extends: - file: ./services/docker-compose-backend.yml + file: ./services/backend.yml service: backend build: context: .. @@ -45,38 +45,38 @@ services: visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider frontend: depends_on: - backend extends: - file: ./services/docker-compose-frontend.yml + file: ./services/frontend.yml service: frontend stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: depends_on: - backend extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -85,5 +85,5 @@ services: - frontend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy diff --git a/docker-compose/docker-compose-no-build-erigon.yml b/docker-compose/erigon.yml similarity index 61% rename from docker-compose/docker-compose-no-build-erigon.yml rename to docker-compose/erigon.yml index 44e8f0441b78..0dbcef7f3e0e 100644 --- a/docker-compose/docker-compose-no-build-erigon.yml +++ b/docker-compose/erigon.yml @@ -3,17 +3,17 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db db-init: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db-init db: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db backend: @@ -21,7 +21,7 @@ services: - db - redis_db extends: - file: ./services/docker-compose-backend.yml + file: ./services/backend.yml service: backend links: - db:database @@ -30,38 +30,38 @@ services: visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider frontend: depends_on: - backend extends: - file: ./services/docker-compose-frontend.yml + file: ./services/frontend.yml service: frontend stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: depends_on: - backend extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -70,5 +70,5 @@ services: - frontend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy diff --git a/docker-compose/docker-compose-no-build-external-backend.yml b/docker-compose/external-backend.yml similarity index 55% rename from docker-compose/docker-compose-no-build-external-backend.yml rename to docker-compose/external-backend.yml index bd6acb24d8ee..db8df3758037 100644 --- a/docker-compose/docker-compose-no-build-external-backend.yml +++ b/docker-compose/external-backend.yml @@ -3,49 +3,49 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db db-init: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db-init db: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider frontend: extends: - file: ./services/docker-compose-frontend.yml + file: ./services/frontend.yml service: frontend stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -53,5 +53,5 @@ services: - frontend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy diff --git a/docker-compose/docker-compose-no-build-no-db-container.yml b/docker-compose/external-db.yml similarity index 62% rename from docker-compose/docker-compose-no-build-no-db-container.yml rename to docker-compose/external-db.yml index a9a95c458b88..b40151c51dec 100644 --- a/docker-compose/docker-compose-no-build-no-db-container.yml +++ b/docker-compose/external-db.yml @@ -3,52 +3,52 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db backend: depends_on: - redis_db extends: - file: ./services/docker-compose-backend.yml + file: ./services/backend.yml service: backend environment: ETHEREUM_JSONRPC_VARIANT: 'geth' visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider frontend: depends_on: - backend extends: - file: ./services/docker-compose-frontend.yml + file: ./services/frontend.yml service: frontend stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: depends_on: - backend extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -57,5 +57,5 @@ services: - frontend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy diff --git a/docker-compose/docker-compose-no-build-external-frontend.yml b/docker-compose/external-frontend.yml similarity index 65% rename from docker-compose/docker-compose-no-build-external-frontend.yml rename to docker-compose/external-frontend.yml index 71804ffeae73..f0d9f9f89298 100644 --- a/docker-compose/docker-compose-no-build-external-frontend.yml +++ b/docker-compose/external-frontend.yml @@ -3,17 +3,17 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db db-init: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db-init db: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db backend: @@ -21,7 +21,7 @@ services: - db - redis_db extends: - file: ./services/docker-compose-backend.yml + file: ./services/backend.yml service: backend links: - db:database @@ -34,31 +34,31 @@ services: visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: depends_on: - backend extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -66,5 +66,5 @@ services: - backend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy \ No newline at end of file diff --git a/docker-compose/docker-compose-no-build-ganache.yml b/docker-compose/ganache.yml similarity index 66% rename from docker-compose/docker-compose-no-build-ganache.yml rename to docker-compose/ganache.yml index f86be323868b..0aa51fce16a2 100644 --- a/docker-compose/docker-compose-no-build-ganache.yml +++ b/docker-compose/ganache.yml @@ -3,17 +3,17 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db db-init: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db-init db: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db backend: @@ -21,7 +21,7 @@ services: - db - redis_db extends: - file: ./services/docker-compose-backend.yml + file: ./services/backend.yml service: backend links: - db:database @@ -34,38 +34,38 @@ services: visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider frontend: depends_on: - backend extends: - file: ./services/docker-compose-frontend.yml + file: ./services/frontend.yml service: frontend stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: depends_on: - backend extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -74,5 +74,5 @@ services: - frontend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy diff --git a/docker-compose/docker-compose-no-build-geth-clique-consensus.yml b/docker-compose/geth-clique-consensus.yml similarity index 62% rename from docker-compose/docker-compose-no-build-geth-clique-consensus.yml rename to docker-compose/geth-clique-consensus.yml index 4f605567e431..ac1573849204 100644 --- a/docker-compose/docker-compose-no-build-geth-clique-consensus.yml +++ b/docker-compose/geth-clique-consensus.yml @@ -3,17 +3,17 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db db-init: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db-init db: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db backend: @@ -21,7 +21,7 @@ services: - db - redis_db extends: - file: ./services/docker-compose-backend.yml + file: ./services/backend.yml service: backend links: - db:database @@ -31,38 +31,38 @@ services: visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider frontend: depends_on: - backend extends: - file: ./services/docker-compose-frontend.yml + file: ./services/frontend.yml service: frontend stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: depends_on: - backend extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -71,5 +71,5 @@ services: - frontend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy diff --git a/docker-compose/docker-compose-no-build-geth.yml b/docker-compose/geth.yml similarity index 61% rename from docker-compose/docker-compose-no-build-geth.yml rename to docker-compose/geth.yml index 94ccea912a2a..611acec30606 100644 --- a/docker-compose/docker-compose-no-build-geth.yml +++ b/docker-compose/geth.yml @@ -3,17 +3,17 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db db-init: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db-init db: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db backend: @@ -21,7 +21,7 @@ services: - db - redis_db extends: - file: ./services/docker-compose-backend.yml + file: ./services/backend.yml service: backend links: - db:database @@ -30,38 +30,38 @@ services: visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider frontend: depends_on: - backend extends: - file: ./services/docker-compose-frontend.yml + file: ./services/frontend.yml service: frontend stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: depends_on: - backend extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -70,5 +70,5 @@ services: - frontend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy diff --git a/docker-compose/docker-compose-no-build-hardhat-network.yml b/docker-compose/hardhat-network.yml similarity index 64% rename from docker-compose/docker-compose-no-build-hardhat-network.yml rename to docker-compose/hardhat-network.yml index defe8e7b35ff..518a3b2af73b 100644 --- a/docker-compose/docker-compose-no-build-hardhat-network.yml +++ b/docker-compose/hardhat-network.yml @@ -3,17 +3,17 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db db-init: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db-init db: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db backend: @@ -21,7 +21,7 @@ services: - db - redis_db extends: - file: ./services/docker-compose-backend.yml + file: ./services/backend.yml service: backend links: - db:database @@ -32,38 +32,38 @@ services: visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider frontend: depends_on: - backend extends: - file: ./services/docker-compose-frontend.yml + file: ./services/frontend.yml service: frontend stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: depends_on: - backend extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -72,5 +72,5 @@ services: - frontend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy diff --git a/docker-compose/microservices.yml b/docker-compose/microservices.yml new file mode 100644 index 000000000000..bdbd891f7e04 --- /dev/null +++ b/docker-compose/microservices.yml @@ -0,0 +1,34 @@ +version: '3.9' + +services: + visualizer: + extends: + file: ./services/visualizer.yml + service: visualizer + + sig-provider: + extends: + file: ./services/sig-provider.yml + service: sig-provider + + sc-verifier: + extends: + file: ./services/smart-contract-verifier.yml + service: smart-contract-verifier + + stats-db-init: + extends: + file: ./services/stats.yml + service: stats-db-init + + stats-db: + extends: + file: ./services/stats.yml + service: stats-db + + stats: + depends_on: + - stats-db + extends: + file: ./services/stats.yml + service: stats diff --git a/docker-compose/services/docker-compose-backend.yml b/docker-compose/services/backend.yml similarity index 79% rename from docker-compose/services/docker-compose-backend.yml rename to docker-compose/services/backend.yml index d5f934fb653f..cf8a0871d366 100644 --- a/docker-compose/services/docker-compose-backend.yml +++ b/docker-compose/services/backend.yml @@ -2,7 +2,7 @@ version: '3.9' services: backend: - image: blockscout/blockscout:${DOCKER_TAG:-latest} + image: blockscout/${DOCKER_REPO:-blockscout}:${DOCKER_TAG:-latest} pull_policy: always restart: always stop_grace_period: 5m diff --git a/docker-compose/services/docker-compose-db.yml b/docker-compose/services/db.yml similarity index 100% rename from docker-compose/services/docker-compose-db.yml rename to docker-compose/services/db.yml diff --git a/docker-compose/services/docker-compose-frontend.yml b/docker-compose/services/frontend.yml similarity index 100% rename from docker-compose/services/docker-compose-frontend.yml rename to docker-compose/services/frontend.yml diff --git a/docker-compose/services/docker-compose-nginx.yml b/docker-compose/services/nginx.yml similarity index 100% rename from docker-compose/services/docker-compose-nginx.yml rename to docker-compose/services/nginx.yml diff --git a/docker-compose/services/docker-compose-redis.yml b/docker-compose/services/redis.yml similarity index 100% rename from docker-compose/services/docker-compose-redis.yml rename to docker-compose/services/redis.yml diff --git a/docker-compose/services/docker-compose-sig-provider.yml b/docker-compose/services/sig-provider.yml similarity index 100% rename from docker-compose/services/docker-compose-sig-provider.yml rename to docker-compose/services/sig-provider.yml diff --git a/docker-compose/services/docker-compose-smart-contract-verifier.yml b/docker-compose/services/smart-contract-verifier.yml similarity index 100% rename from docker-compose/services/docker-compose-smart-contract-verifier.yml rename to docker-compose/services/smart-contract-verifier.yml diff --git a/docker-compose/services/docker-compose-stats.yml b/docker-compose/services/stats.yml similarity index 100% rename from docker-compose/services/docker-compose-stats.yml rename to docker-compose/services/stats.yml diff --git a/docker-compose/services/docker-compose-visualizer.yml b/docker-compose/services/visualizer.yml similarity index 100% rename from docker-compose/services/docker-compose-visualizer.yml rename to docker-compose/services/visualizer.yml diff --git a/docker/Makefile b/docker/Makefile index 231bcfe7aed0..6f3039ec69ea 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -16,19 +16,19 @@ STABLE_TAG := $(RELEASE_VERSION) start: @echo "==> Starting blockscout db" - @docker-compose -f ../docker-compose/services/docker-compose-db.yml up -d + @docker-compose -f ../docker-compose/services/db.yml up -d @echo "==> Starting blockscout backend" - @docker-compose -f ../docker-compose/services/docker-compose-backend.yml up -d + @docker-compose -f ../docker-compose/services/backend.yml up -d @echo "==> Starting stats microservice" - @docker-compose -f ../docker-compose/services/docker-compose-stats.yml up -d + @docker-compose -f ../docker-compose/services/stats.yml up -d @echo "==> Starting visualizer microservice" - @docker-compose -f ../docker-compose/services/docker-compose-visualizer.yml up -d + @docker-compose -f ../docker-compose/services/visualizer.yml up -d @echo "==> Starting sig-provider microservice" - @docker-compose -f ../docker-compose/services/docker-compose-sig-provider.yml up -d + @docker-compose -f ../docker-compose/services/sig-provider.yml up -d @echo "==> Starting blockscout frontend" - @docker-compose -f ../docker-compose/services/docker-compose-frontend.yml up -d + @docker-compose -f ../docker-compose/services/frontend.yml up -d @echo "==> Starting Nginx proxy" - @docker-compose -f ../docker-compose/services/docker-compose-nginx.yml up -d + @docker-compose -f ../docker-compose/services/nginx.yml up -d BS_BACKEND_STARTED := $(shell docker ps --no-trunc --filter name=^/${BACKEND_CONTAINER_NAME}$ | grep ${BACKEND_CONTAINER_NAME}) BS_FRONTEND_STARTED := $(shell docker ps --no-trunc --filter name=^/${FRONTEND_CONTAINER_NAME}$ | grep ${FRONTEND_CONTAINER_NAME}) From 7051ae2b9ece9634f5f481ab0453609e93a86053 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 6 Dec 2023 20:26:42 +0300 Subject: [PATCH 128/607] Open ports of services --- .gitignore | 5 +++++ docker-compose/microservices.yml | 9 +++++++++ docker-compose/services/stats.yml | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9e53084c62bb..bfd06c0a3aac 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,8 @@ dump.rdb .vscode **.dec** + +*.env +*.env.example +*.env.local +*.env.staging diff --git a/docker-compose/microservices.yml b/docker-compose/microservices.yml index bdbd891f7e04..ee04947ddb4e 100644 --- a/docker-compose/microservices.yml +++ b/docker-compose/microservices.yml @@ -5,16 +5,23 @@ services: extends: file: ./services/visualizer.yml service: visualizer + ports: + - 8081:8050 sig-provider: extends: file: ./services/sig-provider.yml service: sig-provider + ports: + - 8083:8050 + sc-verifier: extends: file: ./services/smart-contract-verifier.yml service: smart-contract-verifier + ports: + - 8082:8050 stats-db-init: extends: @@ -32,3 +39,5 @@ services: extends: file: ./services/stats.yml service: stats + ports: + - 8080:8050 diff --git a/docker-compose/services/stats.yml b/docker-compose/services/stats.yml index 5077a5d893c2..0ba073432d33 100644 --- a/docker-compose/services/stats.yml +++ b/docker-compose/services/stats.yml @@ -49,6 +49,6 @@ services: - ../envs/common-stats.env environment: - STATS__DB_URL=postgres://stats:n0uejXPl61ci6ldCuE2gQU5Y@stats-db:5432/stats - - STATS__BLOCKSCOUT_DB_URL=postgresql://blockscout:ceWb1MeLBEeOIfk65gU8EjF8@db:5432/blockscout + - STATS__BLOCKSCOUT_DB_URL=${STATS__BLOCKSCOUT_DB_URL-postgresql://blockscout:ceWb1MeLBEeOIfk65gU8EjF8@db:5432/blockscout} - STATS__CREATE_DATABASE=true - STATS__RUN_MIGRATIONS=true From 209d9a7d55a792d05c38f7a7d83aa880c3c83927 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 6 Dec 2023 20:39:17 +0300 Subject: [PATCH 129/607] Change optimism branch name --- .github/workflows/config.yml | 4 ++-- .github/workflows/publish-docker-image-for-optimism.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 8ec57498b3dc..62206a6ab910 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -9,7 +9,7 @@ on: - production-eth-goerli-stg - production-eth-sepolia-stg - production-fuse-stg - - production-optimism-stg + - production-optimism - production-immutable-stg - production-iota-stg - production-lukso-stg @@ -28,7 +28,7 @@ on: pull_request: branches: - master - - production-optimism-stg + - production-optimism env: MIX_ENV: test diff --git a/.github/workflows/publish-docker-image-for-optimism.yml b/.github/workflows/publish-docker-image-for-optimism.yml index f1371cc268c3..c53a1edf9c3a 100644 --- a/.github/workflows/publish-docker-image-for-optimism.yml +++ b/.github/workflows/publish-docker-image-for-optimism.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: push: branches: - - production-optimism-stg + - production-optimism jobs: push_to_registry: name: Push Docker image to Docker Hub From c1e0c0c08fe38a92114cebbe470b01a24efb4d69 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 6 Dec 2023 21:15:31 +0300 Subject: [PATCH 130/607] Add CHANGELOG entry for #8956 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc73d9452694..2bf98880c023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Chore +- [#8956](https://github.com/blockscout/blockscout/pull/8956) - Refine docker-compose config structure +
Dependencies version bumps
From 0583b2450ff1bcd2a570c5aea04105eeee277241 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Wed, 6 Dec 2023 23:21:01 +0300 Subject: [PATCH 131/607] Skip failed instances --- .../helper.ex | 63 ++++++++++--------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/apps/explorer/lib/explorer/token_instance_owner_address_migration/helper.ex b/apps/explorer/lib/explorer/token_instance_owner_address_migration/helper.ex index 66beb03dd4ea..01bc78d39e28 100644 --- a/apps/explorer/lib/explorer/token_instance_owner_address_migration/helper.ex +++ b/apps/explorer/lib/explorer/token_instance_owner_address_migration/helper.ex @@ -34,38 +34,45 @@ defmodule Explorer.TokenInstanceOwnerAddressMigration.Helper do | {:error, any, any, map} def fetch_and_insert(batch) do changes = - Enum.map(batch, fn %{token_id: token_id, token_contract_address_hash: token_contract_address_hash} -> - token_transfer_query = - from(tt in TokenTransfer.only_consensus_transfers_query(), - where: - tt.token_contract_address_hash == ^token_contract_address_hash and - fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^token_id), - order_by: [desc: tt.block_number, desc: tt.log_index], - limit: 1, - select: %{ - token_contract_address_hash: tt.token_contract_address_hash, - token_ids: tt.token_ids, - to_address_hash: tt.to_address_hash, - block_number: tt.block_number, - log_index: tt.log_index - } - ) + batch + |> Enum.map(&process_instance/1) + |> Enum.reject(&is_nil/1) - token_transfer = - Repo.one(token_transfer_query, timeout: :timer.minutes(1)) || - %{to_address_hash: @burn_address_hash, block_number: -1, log_index: -1} + Chain.import(%{token_instances: %{params: changes}}) + end - %{ - token_contract_address_hash: token_contract_address_hash, - token_id: token_id, - token_type: "ERC-721", - owner_address_hash: token_transfer.to_address_hash, - owner_updated_at_block: token_transfer.block_number, - owner_updated_at_log_index: token_transfer.log_index + defp process_instance(%{token_id: token_id, token_contract_address_hash: token_contract_address_hash}) do + token_transfer_query = + from(tt in TokenTransfer.only_consensus_transfers_query(), + where: + tt.token_contract_address_hash == ^token_contract_address_hash and + fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^token_id), + order_by: [desc: tt.block_number, desc: tt.log_index], + limit: 1, + select: %{ + token_contract_address_hash: tt.token_contract_address_hash, + token_ids: tt.token_ids, + to_address_hash: tt.to_address_hash, + block_number: tt.block_number, + log_index: tt.log_index } - end) + ) - Chain.import(%{token_instances: %{params: changes}}) + token_transfer = + Repo.one(token_transfer_query, timeout: :timer.minutes(5)) || + %{to_address_hash: @burn_address_hash, block_number: -1, log_index: -1} + + %{ + token_contract_address_hash: token_contract_address_hash, + token_id: token_id, + token_type: "ERC-721", + owner_address_hash: token_transfer.to_address_hash, + owner_updated_at_block: token_transfer.block_number, + owner_updated_at_log_index: token_transfer.log_index + } + rescue + DBConnection.ConnectionError -> + nil end @spec unfilled_token_instances_exists? :: boolean From 77072e43d87ba134cebe5c6a4ff0adcdbfa4fdf3 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Thu, 7 Dec 2023 16:08:26 +0300 Subject: [PATCH 132/607] Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc73d9452694..db105345f644 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Fixes +- [#8959](https://github.com/blockscout/blockscout/pull/8959) - Skip failed instances in Token Instance Owner migrator + ### Chore
From 15ffea7ed2f2b63cba573f0114298d0ae520d84f Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 7 Dec 2023 19:12:58 +0300 Subject: [PATCH 133/607] Improve microservices.yml docker-compose --- docker-compose/microservices.yml | 14 ++-- .../proxy/microservices.conf.template | 65 +++++++++++++++++++ 2 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 docker-compose/proxy/microservices.conf.template diff --git a/docker-compose/microservices.yml b/docker-compose/microservices.yml index ee04947ddb4e..ee4a07bd6d1d 100644 --- a/docker-compose/microservices.yml +++ b/docker-compose/microservices.yml @@ -5,8 +5,6 @@ services: extends: file: ./services/visualizer.yml service: visualizer - ports: - - 8081:8050 sig-provider: extends: @@ -39,5 +37,13 @@ services: extends: file: ./services/stats.yml service: stats - ports: - - 8080:8050 + + proxy: + depends_on: + - visualizer + - stats + extends: + file: ./services/nginx.yml + service: proxy + volumes: + - "./proxy/microservices.conf.template:/etc/nginx/templates/default.conf.template" diff --git a/docker-compose/proxy/microservices.conf.template b/docker-compose/proxy/microservices.conf.template new file mode 100644 index 000000000000..cf359913cace --- /dev/null +++ b/docker-compose/proxy/microservices.conf.template @@ -0,0 +1,65 @@ +map $http_upgrade $connection_upgrade { + + default upgrade; + '' close; +} + +server { + listen 8080; + server_name localhost; + proxy_http_version 1.1; + proxy_hide_header Access-Control-Allow-Origin; + proxy_hide_header Access-Control-Allow-Methods; + add_header 'Access-Control-Allow-Origin' 'http://localhost' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Allow-Methods' 'PUT, GET, POST, OPTIONS, DELETE, PATCH' always; + + location / { + proxy_pass http://stats:8050/; + proxy_http_version 1.1; + proxy_set_header Host "$host"; + proxy_set_header X-Real-IP "$remote_addr"; + proxy_set_header X-Forwarded-For "$proxy_add_x_forwarded_for"; + proxy_set_header X-Forwarded-Proto "$scheme"; + proxy_set_header Upgrade "$http_upgrade"; + proxy_set_header Connection $connection_upgrade; + proxy_cache_bypass $http_upgrade; + } +} +server { + listen 8081; + server_name localhost; + proxy_http_version 1.1; + proxy_hide_header Access-Control-Allow-Origin; + proxy_hide_header Access-Control-Allow-Methods; + add_header 'Access-Control-Allow-Origin' 'http://localhost:3000' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Allow-Methods' 'PUT, GET, POST, OPTIONS, DELETE, PATCH' always; + add_header 'Access-Control-Allow-Headers' 'DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,x-csrf-token' always; + + location / { + proxy_pass http://visualizer:8050/; + proxy_http_version 1.1; + proxy_buffering off; + proxy_set_header Host "$host"; + proxy_set_header X-Real-IP "$remote_addr"; + proxy_connect_timeout 30m; + proxy_read_timeout 30m; + proxy_send_timeout 30m; + proxy_set_header X-Forwarded-For "$proxy_add_x_forwarded_for"; + proxy_set_header X-Forwarded-Proto "$scheme"; + proxy_set_header Upgrade "$http_upgrade"; + proxy_set_header Connection $connection_upgrade; + proxy_cache_bypass $http_upgrade; + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' 'http://localhost:3000' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Allow-Methods' 'PUT, GET, POST, OPTIONS, DELETE, PATCH' always; + add_header 'Access-Control-Allow-Headers' 'DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,x-csrf-token' always; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + add_header 'Content-Length' 0; + return 204; + } + } +} \ No newline at end of file From 9d8beda5cd6851a5ede0a5d0c0c6c60e53eb3445 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 7 Dec 2023 19:22:40 +0300 Subject: [PATCH 134/607] Fix microservices.yml docker-compose stats cors issue --- docker-compose/proxy/microservices.conf.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose/proxy/microservices.conf.template b/docker-compose/proxy/microservices.conf.template index cf359913cace..708812f57113 100644 --- a/docker-compose/proxy/microservices.conf.template +++ b/docker-compose/proxy/microservices.conf.template @@ -10,7 +10,7 @@ server { proxy_http_version 1.1; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; - add_header 'Access-Control-Allow-Origin' 'http://localhost' always; + add_header 'Access-Control-Allow-Origin' 'http://localhost:3000' always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Methods' 'PUT, GET, POST, OPTIONS, DELETE, PATCH' always; From f0b05faace772d845f4875d66d1e567b4231b1cb Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Fri, 8 Dec 2023 00:40:15 +0300 Subject: [PATCH 135/607] Add ACCOUNT_WATCHLIST_NOTIFICATIONS_LIMIT_FOR_30_DAYS --- CHANGELOG.md | 2 + .../lib/explorer/account/notifier/notify.ex | 10 ++-- .../account/watchlist_notification.ex | 25 ++++++++- ...20231207201701_add_watchlist_id_column.exs | 23 ++++++++ .../explorer/account/notifier/notify_test.exs | 56 +++++++++++++++++++ config/runtime.exs | 4 +- cspell.json | 3 +- 7 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 apps/explorer/priv/account/migrations/20231207201701_add_watchlist_id_column.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bf98880c023..ad24d7a0aa7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Features +- [#8966](https://github.com/blockscout/blockscout/pull/8966) - Add `ACCOUNT_WATCHLIST_NOTIFICATIONS_LIMIT_FOR_30_DAYS` + ### Fixes ### Chore diff --git a/apps/explorer/lib/explorer/account/notifier/notify.ex b/apps/explorer/lib/explorer/account/notifier/notify.ex index 70e633be8da8..f9c9232546ac 100644 --- a/apps/explorer/lib/explorer/account/notifier/notify.ex +++ b/apps/explorer/lib/explorer/account/notifier/notify.ex @@ -55,7 +55,8 @@ defmodule Explorer.Account.Notifier.Notify do defp notify_watchlists(nil), do: nil defp notify_watchlist(%WatchlistAddress{} = address, summary, direction) do - case ForbiddenAddress.check(address.address_hash) do + case !WatchlistNotification.limit_reached_for_watchlist_id?(address.watchlist_id) && + ForbiddenAddress.check(address.address_hash) do {:ok, _address_hash} -> with %WatchlistNotification{} = notification <- build_watchlist_notification( @@ -74,6 +75,9 @@ defmodule Explorer.Account.Notifier.Notify do {:error, _message} -> nil + + false -> + nil end end @@ -106,9 +110,6 @@ defmodule Explorer.Account.Notifier.Notify do Logger.info("--- email delivery response: FAILED", fetcher: :account) Logger.info(error, fetcher: :account) end - else - Logger.info("--- email delivery response: FAILED", fetcher: :account) - Logger.info("Email is not composed (is nil)", fetcher: :account) end end @@ -119,6 +120,7 @@ defmodule Explorer.Account.Notifier.Notify do if is_watched(address, summary, direction) do %WatchlistNotification{ watchlist_address_id: address.id, + watchlist_id: address.watchlist_id, transaction_hash: summary.transaction_hash, from_address_hash: summary.from_address_hash, to_address_hash: summary.to_address_hash, diff --git a/apps/explorer/lib/explorer/account/watchlist_notification.ex b/apps/explorer/lib/explorer/account/watchlist_notification.ex index 935e53218780..cc45561073ef 100644 --- a/apps/explorer/lib/explorer/account/watchlist_notification.ex +++ b/apps/explorer/lib/explorer/account/watchlist_notification.ex @@ -1,6 +1,6 @@ defmodule Explorer.Account.WatchlistNotification do @moduledoc """ - Stored notification about event + Stored notification about event related to WatchlistAddress """ @@ -9,7 +9,8 @@ defmodule Explorer.Account.WatchlistNotification do import Ecto.Changeset import Explorer.Chain, only: [hash_to_lower_case_string: 1] - alias Explorer.Account.WatchlistAddress + alias Explorer.Repo + alias Explorer.Account.{Watchlist, WatchlistAddress} schema "account_watchlist_notifications" do field(:amount, :decimal) @@ -24,6 +25,7 @@ defmodule Explorer.Account.WatchlistNotification do field(:subject_hash, Cloak.Ecto.SHA256) belongs_to(:watchlist_address, WatchlistAddress) + belongs_to(:watchlist, Watchlist) field(:from_address_hash, Explorer.Encrypted.AddressHash) field(:to_address_hash, Explorer.Encrypted.AddressHash) @@ -62,4 +64,23 @@ defmodule Explorer.Account.WatchlistNotification do |> put_change(:transaction_hash_hash, hash_to_lower_case_string(get_field(changeset, :transaction_hash))) |> put_change(:subject_hash, get_field(changeset, :subject)) end + + @doc """ + Check if amount of watchlist notifications for the last 30 days is less than ACCOUNT_WATCHLIST_NOTIFICATIONS_LIMIT_FOR_30_DAYS + """ + @spec limit_reached_for_watchlist_id?(integer) :: boolean + def limit_reached_for_watchlist_id?(watchlist_id) do + __MODULE__ + |> where( + [wn], + wn.watchlist_id == ^watchlist_id and + fragment("NOW() - ? at time zone 'UTC' <= interval '30 days'", wn.inserted_at) + ) + |> limit(^watchlist_notification_30_days_limit()) + |> Repo.account_repo().aggregate(:count) == watchlist_notification_30_days_limit() + end + + defp watchlist_notification_30_days_limit do + Application.get_env(:explorer, Explorer.Account)[:notifications_limit_for_30_days] + end end diff --git a/apps/explorer/priv/account/migrations/20231207201701_add_watchlist_id_column.exs b/apps/explorer/priv/account/migrations/20231207201701_add_watchlist_id_column.exs new file mode 100644 index 000000000000..346c9ec05ee8 --- /dev/null +++ b/apps/explorer/priv/account/migrations/20231207201701_add_watchlist_id_column.exs @@ -0,0 +1,23 @@ +defmodule Explorer.Repo.Account.Migrations.AddWatchlistIdColumn do + use Ecto.Migration + + def change do + execute(""" + ALTER TABLE public.account_watchlist_notifications + DROP CONSTRAINT account_watchlist_notifications_watchlist_address_id_fkey; + """) + + alter table(:account_watchlist_notifications) do + add(:watchlist_id, :bigserial) + end + + create(index(:account_watchlist_notifications, [:watchlist_id])) + + execute(""" + UPDATE account_watchlist_notifications awn + SET watchlist_id = awa.watchlist_id + FROM account_watchlist_addresses awa + WHERE awa.id = awn.watchlist_address_id + """) + end +end diff --git a/apps/explorer/test/explorer/account/notifier/notify_test.exs b/apps/explorer/test/explorer/account/notifier/notify_test.exs index bc7480cc3ec2..860b569f2a15 100644 --- a/apps/explorer/test/explorer/account/notifier/notify_test.exs +++ b/apps/explorer/test/explorer/account/notifier/notify_test.exs @@ -86,5 +86,61 @@ defmodule Explorer.Account.Notifier.NotifyTest do assert wn.tx_fee == fee assert wn.type == "COIN" end + + test "ignore new notification when limit is reached" do + old_envs = Application.get_env(:explorer, Explorer.Account) + + Application.put_env(:explorer, Explorer.Account, Keyword.put(old_envs, :notifications_limit_for_30_days, 1)) + + wa = + %WatchlistAddress{address_hash: address_hash} = + build(:account_watchlist_address, watch_coin_input: true) + |> Repo.account_repo().insert!() + + _watchlist_address = Repo.preload(wa, watchlist: :identity) + + tx = + %Transaction{ + from_address: _from_address, + to_address: _to_address, + block_number: _block_number, + hash: _tx_hash + } = with_block(insert(:transaction, to_address: %Chain.Address{hash: address_hash})) + + {_, fee} = Chain.fee(tx, :gwei) + amount = Wei.to(tx.value, :ether) + notify = Notify.call([tx]) + + wn = + WatchlistNotification + |> first + |> Repo.account_repo().one() + + assert notify == [[:ok]] + + assert wn.amount == amount + assert wn.direction == "incoming" + assert wn.method == "transfer" + assert wn.subject == "Coin transaction" + assert wn.tx_fee == fee + assert wn.type == "COIN" + address = Repo.get(Chain.Address, address_hash) + + tx = + %Transaction{ + from_address: _from_address, + to_address: _to_address, + block_number: _block_number, + hash: _tx_hash + } = with_block(insert(:transaction, to_address: address)) + + Notify.call([tx]) + + WatchlistNotification + |> first + |> Repo.account_repo().one!() + + Application.put_env(:explorer, Explorer.Account, old_envs) + end end end diff --git a/config/runtime.exs b/config/runtime.exs index 0f1ed1aa864a..ec0cd0427b01 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -407,7 +407,9 @@ config :explorer, Explorer.Account, ], resend_interval: ConfigHelper.parse_time_env_var("ACCOUNT_VERIFICATION_EMAIL_RESEND_INTERVAL", "5m"), private_tags_limit: ConfigHelper.parse_integer_env_var("ACCOUNT_PRIVATE_TAGS_LIMIT", 2000), - watchlist_addresses_limit: ConfigHelper.parse_integer_env_var("ACCOUNT_WATCHLIST_ADDRESSES_LIMIT", 15) + watchlist_addresses_limit: ConfigHelper.parse_integer_env_var("ACCOUNT_WATCHLIST_ADDRESSES_LIMIT", 15), + notifications_limit_for_30_days: + ConfigHelper.parse_integer_env_var("ACCOUNT_WATCHLIST_NOTIFICATIONS_LIMIT_FOR_30_DAYS", 1000) config :explorer, :token_id_migration, first_block: ConfigHelper.parse_integer_env_var("TOKEN_ID_MIGRATION_FIRST_BLOCK", 0), diff --git a/cspell.json b/cspell.json index 72bfc7609c21..3cdf520ecd49 100644 --- a/cspell.json +++ b/cspell.json @@ -545,7 +545,8 @@ "qitmeer", "meer", "DefiLlama", - "SOLIDITYSCAN" + "SOLIDITYSCAN", + "fkey" ], "enableFiletypes": [ "dotenv", From fd05765d19157d75388617632b59718bd647d7b2 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 11 Dec 2023 17:22:32 +0300 Subject: [PATCH 136/607] Update CHANGELOG --- CHANGELOG.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50d41ac0f9d4..a4ab195fdb21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,16 +4,10 @@ ### Features -- [#8966](https://github.com/blockscout/blockscout/pull/8966) - Add `ACCOUNT_WATCHLIST_NOTIFICATIONS_LIMIT_FOR_30_DAYS` - ### Fixes -- [#8959](https://github.com/blockscout/blockscout/pull/8959) - Skip failed instances in Token Instance Owner migrator - ### Chore -- [#8956](https://github.com/blockscout/blockscout/pull/8956) - Refine docker-compose config structure -
Dependencies version bumps
@@ -22,12 +16,14 @@ ### Features +- [#8966](https://github.com/blockscout/blockscout/pull/8966) - Add `ACCOUNT_WATCHLIST_NOTIFICATIONS_LIMIT_FOR_30_DAYS` - [#8908](https://github.com/blockscout/blockscout/pull/8908) - Solidityscan report API endpoint - [#8900](https://github.com/blockscout/blockscout/pull/8900) - Add Compound proxy contract pattern - [#8611](https://github.com/blockscout/blockscout/pull/8611) - Implement sorting of smart contracts, address transactions ### Fixes +- [#8959](https://github.com/blockscout/blockscout/pull/8959) - Skip failed instances in Token Instance Owner migrator - [#8924](https://github.com/blockscout/blockscout/pull/8924) - Delete invalid current token balances in OnDemand fetcher - [#8922](https://github.com/blockscout/blockscout/pull/8922) - Allow call type to be in lowercase - [#8917](https://github.com/blockscout/blockscout/pull/8917) - Proxy detection hotfix in API v2 @@ -39,6 +35,7 @@ ### Chore +- [#8956](https://github.com/blockscout/blockscout/pull/8956) - Refine docker-compose config structure - [#8911](https://github.com/blockscout/blockscout/pull/8911) - Set client_connection_check_interval for main Postgres DB in docker-compose setup
From e549fce72765ef4232aa7bacb20366b8ebe4d07f Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 11 Dec 2023 20:16:19 +0300 Subject: [PATCH 137/607] Remove -stg suffix from chain branch names --- .github/workflows/config.yml | 28 +++++++++---------- .../publish-docker-image-for-core.yml | 2 +- .../publish-docker-image-for-eth-goerli.yml | 2 +- .../publish-docker-image-for-eth-sepolia.yml | 2 +- .../publish-docker-image-for-eth.yml | 2 +- .../publish-docker-image-for-fuse.yml | 2 +- .../publish-docker-image-for-immutable.yml | 2 +- .../publish-docker-image-for-lukso.yml | 2 +- .../publish-docker-image-for-polygon-edge.yml | 2 +- .../publish-docker-image-for-rsk.yml | 2 +- .../publish-docker-image-for-stability.yml | 2 +- .../publish-docker-image-for-suave.yml | 2 +- .../publish-docker-image-for-xdai.yml | 2 +- .../publish-docker-image-for-zkevm.yml | 2 +- .../publish-docker-image-for-zksync.yml | 2 +- .github/workflows/release-main.yml | 18 ++++++------ 16 files changed, 37 insertions(+), 37 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 62206a6ab910..247f01b0e87d 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -4,21 +4,21 @@ on: push: branches: - master - - production-core-stg - - production-eth-stg-experimental - - production-eth-goerli-stg - - production-eth-sepolia-stg - - production-fuse-stg + - production-core + - production-eth-experimental + - production-eth-goerli + - production-eth-sepolia + - production-fuse - production-optimism - - production-immutable-stg - - production-iota-stg - - production-lukso-stg - - production-rsk-stg - - production-sokol-stg - - production-suave-stg - - production-xdai-stg - - production-zkevm-stg - - production-zksync-stg + - production-immutable + - production-iota + - production-lukso + - production-rsk + - production-sokol + - production-suave + - production-xdai + - production-zkevm + - production-zksync - staging-l2 paths-ignore: - 'CHANGELOG.md' diff --git a/.github/workflows/publish-docker-image-for-core.yml b/.github/workflows/publish-docker-image-for-core.yml index 184bed4b4a8e..158e5c015154 100644 --- a/.github/workflows/publish-docker-image-for-core.yml +++ b/.github/workflows/publish-docker-image-for-core.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: push: branches: - - production-core-stg + - production-core jobs: push_to_registry: name: Push Docker image to Docker Hub diff --git a/.github/workflows/publish-docker-image-for-eth-goerli.yml b/.github/workflows/publish-docker-image-for-eth-goerli.yml index 174318b6c963..20db375a476a 100644 --- a/.github/workflows/publish-docker-image-for-eth-goerli.yml +++ b/.github/workflows/publish-docker-image-for-eth-goerli.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: push: branches: - - production-eth-goerli-stg + - production-eth-goerli jobs: push_to_registry: name: Push Docker image to Docker Hub diff --git a/.github/workflows/publish-docker-image-for-eth-sepolia.yml b/.github/workflows/publish-docker-image-for-eth-sepolia.yml index d0d1a6281f04..d16b1fbc9d18 100644 --- a/.github/workflows/publish-docker-image-for-eth-sepolia.yml +++ b/.github/workflows/publish-docker-image-for-eth-sepolia.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: push: branches: - - production-eth-sepolia-stg + - production-eth-sepolia jobs: push_to_registry: name: Push Docker image to Docker Hub diff --git a/.github/workflows/publish-docker-image-for-eth.yml b/.github/workflows/publish-docker-image-for-eth.yml index af99aa32d184..0fd3120c0517 100644 --- a/.github/workflows/publish-docker-image-for-eth.yml +++ b/.github/workflows/publish-docker-image-for-eth.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: push: branches: - - production-eth-stg-experimental + - production-eth-experimental jobs: push_to_registry: name: Push Docker image to Docker Hub diff --git a/.github/workflows/publish-docker-image-for-fuse.yml b/.github/workflows/publish-docker-image-for-fuse.yml index 4b0221b8e685..fbd5fe81e212 100644 --- a/.github/workflows/publish-docker-image-for-fuse.yml +++ b/.github/workflows/publish-docker-image-for-fuse.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: push: branches: - - production-fuse-stg + - production-fuse jobs: push_to_registry: name: Push Docker image to Docker Hub diff --git a/.github/workflows/publish-docker-image-for-immutable.yml b/.github/workflows/publish-docker-image-for-immutable.yml index 6dd6dccde603..2fbccdf55586 100644 --- a/.github/workflows/publish-docker-image-for-immutable.yml +++ b/.github/workflows/publish-docker-image-for-immutable.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: push: branches: - - production-immutable-stg + - production-immutable jobs: push_to_registry: name: Push Docker image to Docker Hub diff --git a/.github/workflows/publish-docker-image-for-lukso.yml b/.github/workflows/publish-docker-image-for-lukso.yml index 53f2f88e258f..a094176964b1 100644 --- a/.github/workflows/publish-docker-image-for-lukso.yml +++ b/.github/workflows/publish-docker-image-for-lukso.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: push: branches: - - production-lukso-stg + - production-lukso jobs: push_to_registry: name: Push Docker image to Docker Hub diff --git a/.github/workflows/publish-docker-image-for-polygon-edge.yml b/.github/workflows/publish-docker-image-for-polygon-edge.yml index ccd20ef78d10..c54555d97cf8 100644 --- a/.github/workflows/publish-docker-image-for-polygon-edge.yml +++ b/.github/workflows/publish-docker-image-for-polygon-edge.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: push: branches: - - production-polygon-edge-stg + - production-polygon-edge jobs: push_to_registry: name: Push Docker image to Docker Hub diff --git a/.github/workflows/publish-docker-image-for-rsk.yml b/.github/workflows/publish-docker-image-for-rsk.yml index d482fed9e90c..324494c9eee7 100644 --- a/.github/workflows/publish-docker-image-for-rsk.yml +++ b/.github/workflows/publish-docker-image-for-rsk.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: push: branches: - - production-rsk-stg + - production-rsk jobs: push_to_registry: name: Push Docker image to Docker Hub diff --git a/.github/workflows/publish-docker-image-for-stability.yml b/.github/workflows/publish-docker-image-for-stability.yml index 218b8fa3e067..f6a4bedb37b8 100644 --- a/.github/workflows/publish-docker-image-for-stability.yml +++ b/.github/workflows/publish-docker-image-for-stability.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: push: branches: - - production-stability-stg + - production-stability env: OTP_VERSION: '25.2.1' ELIXIR_VERSION: '1.14.5' diff --git a/.github/workflows/publish-docker-image-for-suave.yml b/.github/workflows/publish-docker-image-for-suave.yml index 85836106b629..2e9b6fbd3ddc 100644 --- a/.github/workflows/publish-docker-image-for-suave.yml +++ b/.github/workflows/publish-docker-image-for-suave.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: push: branches: - - production-suave-stg + - production-suave env: OTP_VERSION: '25.2.1' ELIXIR_VERSION: '1.14.5' diff --git a/.github/workflows/publish-docker-image-for-xdai.yml b/.github/workflows/publish-docker-image-for-xdai.yml index 7a69fd7e9f21..7a672e81fcef 100644 --- a/.github/workflows/publish-docker-image-for-xdai.yml +++ b/.github/workflows/publish-docker-image-for-xdai.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: push: branches: - - production-xdai-stg + - production-xdai jobs: push_to_registry: name: Push Docker image to Docker Hub diff --git a/.github/workflows/publish-docker-image-for-zkevm.yml b/.github/workflows/publish-docker-image-for-zkevm.yml index 9fed1cc04083..b73fdcda97d4 100644 --- a/.github/workflows/publish-docker-image-for-zkevm.yml +++ b/.github/workflows/publish-docker-image-for-zkevm.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: push: branches: - - production-zkevm-stg + - production-zkevm jobs: push_to_registry: name: Push Docker image to Docker Hub diff --git a/.github/workflows/publish-docker-image-for-zksync.yml b/.github/workflows/publish-docker-image-for-zksync.yml index 1688bb4f761e..8a0490c88178 100644 --- a/.github/workflows/publish-docker-image-for-zksync.yml +++ b/.github/workflows/publish-docker-image-for-zksync.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: push: branches: - - production-zksync-stg + - production-zksync jobs: push_to_registry: name: Push Docker image to Docker Hub diff --git a/.github/workflows/release-main.yml b/.github/workflows/release-main.yml index d8808170ab9e..c1cbdac6b8d9 100644 --- a/.github/workflows/release-main.yml +++ b/.github/workflows/release-main.yml @@ -127,15 +127,15 @@ jobs: # runs-on: ubuntu-latest # env: # BRANCHES: | - # production-core-stg - # production-sokol-stg - # production-eth-stg-experimental - # production-eth-goerli-stg - # production-lukso-stg - # production-xdai-stg - # production-polygon-supernets-stg - # production-rsk-stg - # production-immutable-stg + # production-core + # production-sokol + # production-eth-experimental + # production-eth-goerli + # production-lukso + # production-xdai + # production-polygon-supernets + # production-rsk + # production-immutable # steps: # - uses: actions/checkout@v4 # - name: Set Git config From 4d357babb943e26c5b1c359c62734c99ac8beca7 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 11 Dec 2023 20:18:10 +0300 Subject: [PATCH 138/607] Rename release workflow --- .github/workflows/{release-main.yml => release.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{release-main.yml => release.yml} (99%) diff --git a/.github/workflows/release-main.yml b/.github/workflows/release.yml similarity index 99% rename from .github/workflows/release-main.yml rename to .github/workflows/release.yml index c1cbdac6b8d9..9a16fba81e7a 100644 --- a/.github/workflows/release-main.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ # separate terms of service, privacy policy, and support # documentation. -name: Release main +name: Release on: release: From 956a06db419f9cd44bab46f0aaa5f3fa886c9e98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:05:59 +0000 Subject: [PATCH 139/607] Bump exvcr from 0.14.4 to 0.15.0 Bumps [exvcr](https://github.com/parroty/exvcr) from 0.14.4 to 0.15.0. - [Release notes](https://github.com/parroty/exvcr/releases) - [Changelog](https://github.com/parroty/exvcr/blob/master/CHANGELOG.md) - [Commits](https://github.com/parroty/exvcr/commits) --- updated-dependencies: - dependency-name: exvcr dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 2db07ac8ff34..cac33555ec65 100644 --- a/mix.lock +++ b/mix.lock @@ -57,7 +57,7 @@ "exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], [], "hexpm", "1222419f706e01bfa1095aec9acf6421367dcfab798a6f67c54cf784733cd6b5"}, "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm", "32e95820a97cffea67830e91514a2ad53b888850442d6d395f53a1ac60c82e07"}, "expo": {:hex, :expo, "0.5.1", "249e826a897cac48f591deba863b26c16682b43711dd15ee86b92f25eafd96d9", [:mix], [], "hexpm", "68a4233b0658a3d12ee00d27d37d856b1ba48607e7ce20fd376958d0ba6ce92b"}, - "exvcr": {:hex, :exvcr, "0.14.4", "1aa5fe7d3f10b117251c158f8d28b39f7fc73d0a7628b2d0b75bf8cfb1111576", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.16", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4e600568c02ed29d46bc2e2c74927d172ba06658aa8b14705c0207363c44cc94"}, + "exvcr": {:hex, :exvcr, "0.15.0", "432a4f4b94494f996c96dd2b9b9d3306b70db269ddbdeb9e324a4371f62ce32d", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.16", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "8b7e451f5fd37d1dc1252d08e55291fcb80b55b00cfd84ea41bf64be23cb142c"}, "file_info": {:hex, :file_info, "0.0.4", "2e0e77f211e833f38ead22cb29ce53761d457d80b3ffe0ffe0eb93880b0963b2", [:mix], [{:mimetype_parser, "~> 0.1.2", [hex: :mimetype_parser, repo: "hexpm", optional: false]}], "hexpm", "50e7ad01c2c8b9339010675fe4dc4a113b8d6ca7eddce24d1d74fd0e762781a5"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"}, From 1dd1ed4159dda7fb2c51be30c60bde4892cd9340 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:10:55 +0000 Subject: [PATCH 140/607] Bump ex_doc from 0.30.9 to 0.31.0 Bumps [ex_doc](https://github.com/elixir-lang/ex_doc) from 0.30.9 to 0.31.0. - [Release notes](https://github.com/elixir-lang/ex_doc/releases) - [Changelog](https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md) - [Commits](https://github.com/elixir-lang/ex_doc/compare/v0.30.9...v0.31.0) --- updated-dependencies: - dependency-name: ex_doc dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- mix.exs | 2 +- mix.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mix.exs b/mix.exs index 09f45065856d..61b159e2c407 100644 --- a/mix.exs +++ b/mix.exs @@ -96,7 +96,7 @@ defmodule BlockScout.Mixfile do {:absinthe_plug, git: "https://github.com/blockscout/absinthe_plug.git", tag: "1.5.3", override: true}, {:tesla, "~> 1.8.0"}, # Documentation - {:ex_doc, "~> 0.30.1", only: :dev, runtime: false}, + {:ex_doc, "~> 0.31.0", only: :dev, runtime: false}, {:number, "~> 1.0.3"} ] end diff --git a/mix.lock b/mix.lock index 2db07ac8ff34..d1a958b57105 100644 --- a/mix.lock +++ b/mix.lock @@ -36,7 +36,7 @@ "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, "digital_token": {:hex, :digital_token, "0.6.0", "13e6de581f0b1f6c686f7c7d12ab11a84a7b22fa79adeb4b50eec1a2d278d258", [:mix], [{:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "2455d626e7c61a128b02a4a8caddb092548c3eb613ac6f6a85e4cbb6caddc4d1"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.37", "2ad73550e27c8946648b06905a57e4d454e4d7229c2dafa72a0348c99d8be5f7", [:mix], [], "hexpm", "6b19783f2802f039806f375610faa22da130b8edc21209d0bff47918bb48360e"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.0", "ff8614b4e70a774f9d39af809c426def80852048440e8785d93a6e91f48fec00", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7769dad267ef967310d6e988e92d772659b11b09a0c015f101ce0fff81ce1f81"}, "ecto_sql": {:hex, :ecto_sql, "3.11.0", "c787b24b224942b69c9ff7ab9107f258ecdc68326be04815c6cce2941b6fad1c", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "77aa3677169f55c2714dda7352d563002d180eb33c0dc29cd36d39c0a1a971f5"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, @@ -47,7 +47,7 @@ "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.10.2", "c8dbb3324ca35cea3679a96f4c774cdf4bdd425786a44c4f52aacb57a0cee446", [:mix], [{:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "0cc2124eccffa5438045c2504dd3365490b64065131f58ecc27f344db1edb4b8"}, "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.32.3", "b631ff94c982ec518e46bf4736000a30a33d6b58facc085d5f240305f512ad4a", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7b626ff1e59a0ec9c3c5db5ce9ca91a6995e2ab56426b71f3cbf67181ea225f5"}, "ex_cldr_units": {:hex, :ex_cldr_units, "3.16.4", "fee054e9ebed40ef05cbb405cb0c7e7c9fda201f8f03ec0d1e54e879af413246", [:mix], [{:cldr_utils, "~> 2.24", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.31", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7c15c6357dd555a5bc6c72fdeb243e4706a04065753dbd2f40150f062ca996c7"}, - "ex_doc": {:hex, :ex_doc, "0.30.9", "d691453495c47434c0f2052b08dd91cc32bc4e1a218f86884563448ee2502dd2", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "d7aaaf21e95dc5cddabf89063327e96867d00013963eadf2c6ad135506a8bc10"}, + "ex_doc": {:hex, :ex_doc, "0.31.0", "06eb1dfd787445d9cab9a45088405593dd3bb7fe99e097eaa71f37ba80c7a676", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5350cafa6b7f77bdd107aa2199fe277acf29d739aba5aee7e865fc680c62a110"}, "ex_json_schema": {:hex, :ex_json_schema, "0.10.2", "7c4b8c1481fdeb1741e2ce66223976edfb9bccebc8014f6aec35d4efe964fb71", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "37f43be60f8407659d4d0155a7e45e7f406dab1f827051d3d35858a709baf6a6"}, "ex_keccak": {:hex, :ex_keccak, "0.7.3", "33298f97159f6b0acd28f6e96ce5ea975a0f4a19f85fe615b4f4579b88b24d06", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6.1", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "4c5e6d9d5f77b64ab48769a0166a9814180d40ced68ed74ce60a5174ab55b3fc"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, @@ -76,9 +76,9 @@ "junit_formatter": {:hex, :junit_formatter, "3.3.1", "c729befb848f1b9571f317d2fefa648e9d4869befc4b2980daca7c1edc468e40", [:mix], [], "hexpm", "761fc5be4b4c15d8ba91a6dafde0b2c2ae6db9da7b8832a55b5a1deb524da72b"}, "logger_file_backend": {:hex, :logger_file_backend, "0.0.13", "df07b14970e9ac1f57362985d76e6f24e3e1ab05c248055b7d223976881977c2", [:mix], [], "hexpm", "71a453a7e6e899ae4549fb147b1c6621f4233f8f48f58ca10a64ec67b6c50018"}, "logger_json": {:hex, :logger_json, "5.1.2", "7dde5f6dff814aba033f045a3af9408f5459bac72357dc533276b47045371ecf", [:mix], [{:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "ed42047e5c57a60d0fa1450aef36bc016d0f9a5e6c0807ebb0c03d8895fb6ebc"}, - "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, + "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"}, "math": {:hex, :math, "0.7.0", "12af548c3892abf939a2e242216c3e7cbfb65b9b2fe0d872d05c6fb609f8127b", [:mix], [], "hexpm", "7987af97a0c6b58ad9db43eb5252a49fc1dfe1f6d98f17da9282e297f594ebc2"}, "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "memento": {:hex, :memento, "0.3.2", "38cfc8ff9bcb1adff7cbd0f3b78a762636b86dff764729d1c82d0464c539bdd0", [:mix], [], "hexpm", "25cf691a98a0cb70262f4a7543c04bab24648cb2041d937eb64154a8d6f8012b"}, From 1677d230d55d237b82742f733afd03306b0ede5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:00:31 +0000 Subject: [PATCH 141/607] Bump chart.js from 4.4.0 to 4.4.1 in /apps/block_scout_web/assets Bumps [chart.js](https://github.com/chartjs/Chart.js) from 4.4.0 to 4.4.1. - [Release notes](https://github.com/chartjs/Chart.js/releases) - [Commits](https://github.com/chartjs/Chart.js/compare/v4.4.0...v4.4.1) --- updated-dependencies: - dependency-name: chart.js dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index a9ef2b466abb..45c902f1a8ef 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -14,7 +14,7 @@ "assert": "^2.1.0", "bignumber.js": "^9.1.2", "bootstrap": "^4.6.0", - "chart.js": "^4.4.0", + "chart.js": "^4.4.1", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", "core-js": "^3.33.3", @@ -5558,9 +5558,9 @@ } }, "node_modules/chart.js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.0.tgz", - "integrity": "sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", + "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -21918,9 +21918,9 @@ "dev": true }, "chart.js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.0.tgz", - "integrity": "sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", + "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", "requires": { "@kurkle/color": "^0.3.0" } diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index d1833ae14e01..96c08a681427 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -26,7 +26,7 @@ "assert": "^2.1.0", "bignumber.js": "^9.1.2", "bootstrap": "^4.6.0", - "chart.js": "^4.4.0", + "chart.js": "^4.4.1", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", "core-js": "^3.33.3", From 2a7917ed9e6d95f6907bc40b12e89382803522d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:00:49 +0000 Subject: [PATCH 142/607] Bump @babel/preset-env in /apps/block_scout_web/assets Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.23.5 to 7.23.6. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.6/packages/babel-preset-env) --- updated-dependencies: - dependency-name: "@babel/preset-env" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 124 +++++++++--------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 64 insertions(+), 62 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index a9ef2b466abb..867394405d7a 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -72,7 +72,7 @@ }, "devDependencies": { "@babel/core": "^7.23.5", - "@babel/preset-env": "^7.23.5", + "@babel/preset-env": "^7.23.6", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^11.0.0", @@ -320,13 +320,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -1222,12 +1222,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.3.tgz", - "integrity": "sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", + "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1779,13 +1780,13 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.5.tgz", - "integrity": "sha512-0d/uxVD6tFGWXGDSfyMD1p2otoaKmu6+GD+NfAx0tMaH+dxORnp7T9TaVQ6mKyya7iBtCIVxHjWT7MuzzM9z+A==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.6.tgz", + "integrity": "sha512-2XPn/BqKkZCpzYhUUNZ1ssXw7DcXfKQEjv/uXZUXgaebCMYmkEsfZ2yY+vv+xtXv50WmL5SGhyB6/xsWxIvvOQ==", "dev": true, "dependencies": { "@babel/compat-data": "^7.23.5", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-option": "^7.23.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", @@ -1825,7 +1826,7 @@ "@babel/plugin-transform-dynamic-import": "^7.23.4", "@babel/plugin-transform-exponentiation-operator": "^7.23.3", "@babel/plugin-transform-export-namespace-from": "^7.23.4", - "@babel/plugin-transform-for-of": "^7.23.3", + "@babel/plugin-transform-for-of": "^7.23.6", "@babel/plugin-transform-function-name": "^7.23.3", "@babel/plugin-transform-json-strings": "^7.23.4", "@babel/plugin-transform-literals": "^7.23.3", @@ -5225,9 +5226,9 @@ ] }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", "funding": [ { "type": "opencollective", @@ -5243,9 +5244,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, "bin": { @@ -5496,9 +5497,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001549", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001549.tgz", - "integrity": "sha512-qRp48dPYSCYaP+KurZLhDYdVE+yEyht/3NlmcJgVQ2VMGt6JL36ndQ/7rgspdZsJuxDPFIo/OzBT2+GmIJ53BA==", + "version": "1.0.30001568", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001568.tgz", + "integrity": "sha512-vSUkH84HontZJ88MiNrOau1EBrCqEQYgkC5gIySiDlpsm8sGVrhU7Kx4V6h0tnqaHzIHZv08HlJIwPbL4XL9+A==", "funding": [ { "type": "opencollective", @@ -6919,9 +6920,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.555", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.555.tgz", - "integrity": "sha512-k1wGC7UXDTyCWcONkEMRG/w6Jvrxi+SVEU+IeqUKUKjv2lGJ1b+jf1mqrloyxVTG5WYYjNQ+F6+Cb1fGrLvNcA==" + "version": "1.4.609", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.609.tgz", + "integrity": "sha512-ihiCP7PJmjoGNuLpl7TjNA8pCQWu09vGyjlPYw1Rqww4gvNuCcmvl+44G+2QyJ6S2K4o+wbTS++Xz0YN8Q9ERw==" }, "node_modules/elliptic": { "version": "6.5.4", @@ -13093,9 +13094,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -18041,13 +18042,13 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", "requires": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -18655,12 +18656,13 @@ } }, "@babel/plugin-transform-for-of": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.3.tgz", - "integrity": "sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", + "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" } }, "@babel/plugin-transform-function-name": { @@ -19007,13 +19009,13 @@ } }, "@babel/preset-env": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.5.tgz", - "integrity": "sha512-0d/uxVD6tFGWXGDSfyMD1p2otoaKmu6+GD+NfAx0tMaH+dxORnp7T9TaVQ6mKyya7iBtCIVxHjWT7MuzzM9z+A==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.6.tgz", + "integrity": "sha512-2XPn/BqKkZCpzYhUUNZ1ssXw7DcXfKQEjv/uXZUXgaebCMYmkEsfZ2yY+vv+xtXv50WmL5SGhyB6/xsWxIvvOQ==", "dev": true, "requires": { "@babel/compat-data": "^7.23.5", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-option": "^7.23.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", @@ -19053,7 +19055,7 @@ "@babel/plugin-transform-dynamic-import": "^7.23.4", "@babel/plugin-transform-exponentiation-operator": "^7.23.3", "@babel/plugin-transform-export-namespace-from": "^7.23.4", - "@babel/plugin-transform-for-of": "^7.23.3", + "@babel/plugin-transform-for-of": "^7.23.6", "@babel/plugin-transform-function-name": "^7.23.3", "@babel/plugin-transform-json-strings": "^7.23.4", "@babel/plugin-transform-literals": "^7.23.3", @@ -21681,13 +21683,13 @@ } }, "browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", "requires": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" } }, @@ -21879,9 +21881,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001549", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001549.tgz", - "integrity": "sha512-qRp48dPYSCYaP+KurZLhDYdVE+yEyht/3NlmcJgVQ2VMGt6JL36ndQ/7rgspdZsJuxDPFIo/OzBT2+GmIJ53BA==" + "version": "1.0.30001568", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001568.tgz", + "integrity": "sha512-vSUkH84HontZJ88MiNrOau1EBrCqEQYgkC5gIySiDlpsm8sGVrhU7Kx4V6h0tnqaHzIHZv08HlJIwPbL4XL9+A==" }, "caseless": { "version": "0.12.0", @@ -22940,9 +22942,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { - "version": "1.4.555", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.555.tgz", - "integrity": "sha512-k1wGC7UXDTyCWcONkEMRG/w6Jvrxi+SVEU+IeqUKUKjv2lGJ1b+jf1mqrloyxVTG5WYYjNQ+F6+Cb1fGrLvNcA==" + "version": "1.4.609", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.609.tgz", + "integrity": "sha512-ihiCP7PJmjoGNuLpl7TjNA8pCQWu09vGyjlPYw1Rqww4gvNuCcmvl+44G+2QyJ6S2K4o+wbTS++Xz0YN8Q9ERw==" }, "elliptic": { "version": "6.5.4", @@ -27751,9 +27753,9 @@ "dev": true }, "node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "normalize-path": { "version": "3.0.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index d1833ae14e01..698ba1c22498 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -84,7 +84,7 @@ }, "devDependencies": { "@babel/core": "^7.23.5", - "@babel/preset-env": "^7.23.5", + "@babel/preset-env": "^7.23.6", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^11.0.0", From d188dc612af50c65061305c6624a473d6c1f29cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:22:38 +0000 Subject: [PATCH 143/607] Bump core-js from 3.33.3 to 3.34.0 in /apps/block_scout_web/assets Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.33.3 to 3.34.0. - [Release notes](https://github.com/zloirock/core-js/releases) - [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/zloirock/core-js/commits/v3.34.0/packages/core-js) --- updated-dependencies: - dependency-name: core-js dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 45c902f1a8ef..ad1ec14c2c66 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -17,7 +17,7 @@ "chart.js": "^4.4.1", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", - "core-js": "^3.33.3", + "core-js": "^3.34.0", "crypto-browserify": "^3.12.0", "dropzone": "^5.9.3", "eth-net-props": "^1.0.41", @@ -5937,9 +5937,9 @@ } }, "node_modules/core-js": { - "version": "3.33.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz", - "integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==", + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.34.0.tgz", + "integrity": "sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -22216,9 +22216,9 @@ } }, "core-js": { - "version": "3.33.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz", - "integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==" + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.34.0.tgz", + "integrity": "sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag==" }, "core-js-compat": { "version": "3.33.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 96c08a681427..522efb716ca1 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -29,7 +29,7 @@ "chart.js": "^4.4.1", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", - "core-js": "^3.33.3", + "core-js": "^3.34.0", "crypto-browserify": "^3.12.0", "dropzone": "^5.9.3", "eth-net-props": "^1.0.41", From aa3b3620728716394b2bfff9691b4546b5f21ebd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:24:40 +0000 Subject: [PATCH 144/607] Bump ecto_sql from 3.11.0 to 3.11.1 Bumps [ecto_sql](https://github.com/elixir-ecto/ecto_sql) from 3.11.0 to 3.11.1. - [Changelog](https://github.com/elixir-ecto/ecto_sql/blob/master/CHANGELOG.md) - [Commits](https://github.com/elixir-ecto/ecto_sql/compare/v3.11.0...v3.11.1) --- updated-dependencies: - dependency-name: ecto_sql dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index d1a958b57105..168c70a64ebb 100644 --- a/mix.lock +++ b/mix.lock @@ -37,8 +37,8 @@ "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, "digital_token": {:hex, :digital_token, "0.6.0", "13e6de581f0b1f6c686f7c7d12ab11a84a7b22fa79adeb4b50eec1a2d278d258", [:mix], [{:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "2455d626e7c61a128b02a4a8caddb092548c3eb613ac6f6a85e4cbb6caddc4d1"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "ecto": {:hex, :ecto, "3.11.0", "ff8614b4e70a774f9d39af809c426def80852048440e8785d93a6e91f48fec00", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7769dad267ef967310d6e988e92d772659b11b09a0c015f101ce0fff81ce1f81"}, - "ecto_sql": {:hex, :ecto_sql, "3.11.0", "c787b24b224942b69c9ff7ab9107f258ecdc68326be04815c6cce2941b6fad1c", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "77aa3677169f55c2714dda7352d563002d180eb33c0dc29cd36d39c0a1a971f5"}, + "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, + "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_abi": {:hex, :ex_abi, "0.6.4", "f722a38298f176dab511cf94627b2815282669255bc2eb834674f23ca71f5cfb", [:mix], [{:ex_keccak, "~> 0.7.3", [hex: :ex_keccak, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "07eaf39b70dd3beac1286c10368d27091a9a64844830eb26a38f1c8d8b19dfbb"}, From 601eb3ea901dc7bdb6d0c5e1c0e04b42f7e42fbd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Dec 2023 08:41:48 +0000 Subject: [PATCH 145/607] Bump @babel/core from 7.23.5 to 7.23.6 in /apps/block_scout_web/assets Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.23.5 to 7.23.6. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.6/packages/babel-core) --- updated-dependencies: - dependency-name: "@babel/core" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 126 +++++++++--------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index cb47e3141142..c9529d0036ef 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -71,7 +71,7 @@ "xss": "^1.0.14" }, "devDependencies": { - "@babel/core": "^7.23.5", + "@babel/core": "^7.23.6", "@babel/preset-env": "^7.23.6", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", @@ -249,20 +249,20 @@ } }, "node_modules/@babel/core": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.5.tgz", - "integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz", + "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.5", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.5", - "@babel/parser": "^7.23.5", + "@babel/helpers": "^7.23.6", + "@babel/parser": "^7.23.6", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.5", - "@babel/types": "^7.23.5", + "@babel/traverse": "^7.23.6", + "@babel/types": "^7.23.6", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -283,11 +283,11 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/@babel/generator": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz", - "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dependencies": { - "@babel/types": "^7.23.5", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -602,13 +602,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.5.tgz", - "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", + "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.5", - "@babel/types": "^7.23.5" + "@babel/traverse": "^7.23.6", + "@babel/types": "^7.23.6" }, "engines": { "node": ">=6.9.0" @@ -628,9 +628,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz", - "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1960,19 +1960,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz", - "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", + "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", "dependencies": { "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.5", + "@babel/generator": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.5", - "@babel/types": "^7.23.5", - "debug": "^4.1.0", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -1980,9 +1980,9 @@ } }, "node_modules/@babel/types": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz", - "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -17985,20 +17985,20 @@ "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==" }, "@babel/core": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.5.tgz", - "integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz", + "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.5", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.5", - "@babel/parser": "^7.23.5", + "@babel/helpers": "^7.23.6", + "@babel/parser": "^7.23.6", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.5", - "@babel/types": "^7.23.5", + "@babel/traverse": "^7.23.6", + "@babel/types": "^7.23.6", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -18014,11 +18014,11 @@ } }, "@babel/generator": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz", - "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "requires": { - "@babel/types": "^7.23.5", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -18248,13 +18248,13 @@ } }, "@babel/helpers": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.5.tgz", - "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", + "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", "requires": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.5", - "@babel/types": "^7.23.5" + "@babel/traverse": "^7.23.6", + "@babel/types": "^7.23.6" } }, "@babel/highlight": { @@ -18268,9 +18268,9 @@ } }, "@babel/parser": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz", - "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==" + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.23.3", @@ -19167,26 +19167,26 @@ } }, "@babel/traverse": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz", - "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", + "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", "requires": { "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.5", + "@babel/generator": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.5", - "@babel/types": "^7.23.5", - "debug": "^4.1.0", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz", - "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", "requires": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 45195f3c1199..08295a34142b 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -83,7 +83,7 @@ "xss": "^1.0.14" }, "devDependencies": { - "@babel/core": "^7.23.5", + "@babel/core": "^7.23.6", "@babel/preset-env": "^7.23.6", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", From 294caa9946789cd7ec328e70ac70808ae0f2659c Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 12 Dec 2023 19:04:12 +0300 Subject: [PATCH 146/607] Add DATABASE_QUEUE_TARGET env --- CHANGELOG.md | 2 ++ config/runtime/dev.exs | 11 ++++++++--- config/runtime/prod.exs | 10 +++++++--- docker-compose/envs/common-blockscout.env | 1 + 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4ab195fdb21..c4e44e287298 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Chore +- [#8991](https://github.com/blockscout/blockscout/pull/8991) - Manage DB queue target via runtime env var +
Dependencies version bumps
diff --git a/config/runtime/dev.exs b/config/runtime/dev.exs index c10a0b752a5c..3e4df28fb71e 100644 --- a/config/runtime/dev.exs +++ b/config/runtime/dev.exs @@ -42,12 +42,15 @@ pool_size = do: ConfigHelper.parse_integer_env_var("POOL_SIZE", 30), else: ConfigHelper.parse_integer_env_var("POOL_SIZE", 40) +queue_target = ConfigHelper.parse_integer_env_var("DATABASE_QUEUE_TARGET", 50) + # Configure your database config :explorer, Explorer.Repo, database: database, hostname: hostname, url: System.get_env("DATABASE_URL"), - pool_size: pool_size + pool_size: pool_size, + queue_target: queue_target database_api = if System.get_env("DATABASE_READ_ONLY_API_URL"), do: nil, else: database hostname_api = if System.get_env("DATABASE_READ_ONLY_API_URL"), do: nil, else: hostname @@ -57,7 +60,8 @@ config :explorer, Explorer.Repo.Replica1, database: database_api, hostname: hostname_api, url: ExplorerConfigHelper.get_api_db_url(), - pool_size: ConfigHelper.parse_integer_env_var("POOL_SIZE_API", 10) + pool_size: ConfigHelper.parse_integer_env_var("POOL_SIZE_API", 10), + queue_target: queue_target database_account = if System.get_env("ACCOUNT_DATABASE_URL"), do: nil, else: database hostname_account = if System.get_env("ACCOUNT_DATABASE_URL"), do: nil, else: hostname @@ -67,7 +71,8 @@ config :explorer, Explorer.Repo.Account, database: database_account, hostname: hostname_account, url: ExplorerConfigHelper.get_account_db_url(), - pool_size: ConfigHelper.parse_integer_env_var("ACCOUNT_POOL_SIZE", 10) + pool_size: ConfigHelper.parse_integer_env_var("ACCOUNT_POOL_SIZE", 10), + queue_target: queue_target # Configure PolygonEdge database config :explorer, Explorer.Repo.PolygonEdge, diff --git a/config/runtime/prod.exs b/config/runtime/prod.exs index a8692d8c540e..ce4602522a1d 100644 --- a/config/runtime/prod.exs +++ b/config/runtime/prod.exs @@ -28,24 +28,28 @@ config :block_scout_web, BlockScoutWeb.Endpoint, ################ pool_size = ConfigHelper.parse_integer_env_var("POOL_SIZE", 50) +queue_target = ConfigHelper.parse_integer_env_var("DATABASE_QUEUE_TARGET", 50) # Configures the database config :explorer, Explorer.Repo, url: System.get_env("DATABASE_URL"), pool_size: pool_size, - ssl: ExplorerConfigHelper.ssl_enabled?() + ssl: ExplorerConfigHelper.ssl_enabled?(), + queue_target: queue_target # Configures API the database config :explorer, Explorer.Repo.Replica1, url: ExplorerConfigHelper.get_api_db_url(), pool_size: ConfigHelper.parse_integer_env_var("POOL_SIZE_API", 50), - ssl: ExplorerConfigHelper.ssl_enabled?() + ssl: ExplorerConfigHelper.ssl_enabled?(), + queue_target: queue_target # Configures Account database config :explorer, Explorer.Repo.Account, url: ExplorerConfigHelper.get_account_db_url(), pool_size: ConfigHelper.parse_integer_env_var("ACCOUNT_POOL_SIZE", 50), - ssl: ExplorerConfigHelper.ssl_enabled?() + ssl: ExplorerConfigHelper.ssl_enabled?(), + queue_target: queue_target # Configures PolygonEdge database config :explorer, Explorer.Repo.PolygonEdge, diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 8ea2e9265dbe..f8a1507ac2d1 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -3,6 +3,7 @@ ETHEREUM_JSONRPC_VARIANT=geth ETHEREUM_JSONRPC_HTTP_URL=http://host.docker.internal:8545/ # ETHEREUM_JSONRPC_FALLBACK_HTTP_URL= DATABASE_URL=postgresql://blockscout:ceWb1MeLBEeOIfk65gU8EjF8@db:5432/blockscout +# DATABASE_QUEUE_TARGET ETHEREUM_JSONRPC_TRACE_URL=http://host.docker.internal:8545/ # ETHEREUM_JSONRPC_FALLBACK_TRACE_URL= # ETHEREUM_JSONRPC_HTTP_TIMEOUT= From c3cd8d6c6b6347bf16b1283a6bf32b767ba33317 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Wed, 6 Dec 2023 12:27:36 +0600 Subject: [PATCH 147/607] Remove daily balances updating from BlockReward fetcher --- CHANGELOG.md | 2 ++ .../lib/indexer/fetcher/block_reward.ex | 22 +------------------ 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4ab195fdb21..d51f123fc654 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Fixes +- [#8955](https://github.com/blockscout/blockscout/pull/8955) - Remove daily balances updating from BlockReward fetcher + ### Chore
diff --git a/apps/indexer/lib/indexer/fetcher/block_reward.ex b/apps/indexer/lib/indexer/fetcher/block_reward.ex index 2558b9510fee..78ba9ec9f3df 100644 --- a/apps/indexer/lib/indexer/fetcher/block_reward.ex +++ b/apps/indexer/lib/indexer/fetcher/block_reward.ex @@ -20,7 +20,7 @@ defmodule Indexer.Fetcher.BlockReward do alias Explorer.Chain.Cache.Accounts alias Indexer.{BufferedTask, Tracer} alias Indexer.Fetcher.BlockReward.Supervisor, as: BlockRewardSupervisor - alias Indexer.Fetcher.{CoinBalance, CoinBalanceDailyUpdater} + alias Indexer.Fetcher.CoinBalance alias Indexer.Transform.{AddressCoinBalances, AddressCoinBalancesDaily, Addresses} @behaviour BufferedTask @@ -274,26 +274,6 @@ defmodule Indexer.Fetcher.BlockReward do addresses_params = Addresses.extract_addresses(%{block_reward_contract_beneficiaries: block_rewards_params}) address_coin_balances_params_set = AddressCoinBalances.params_set(%{beneficiary_params: block_rewards_params}) - address_coin_balances_params_with_block_timestamp = - block_rewards_params - |> Enum.map(fn block_rewards_param -> - %{ - address_hash: block_rewards_param.address_hash, - block_number: block_rewards_param.block_number, - block_timestamp: block_rewards_param.block_timestamp - } - end) - |> Enum.into(MapSet.new()) - - address_coin_balances_params_with_block_timestamp_set = %{ - address_coin_balances_params_with_block_timestamp: address_coin_balances_params_with_block_timestamp - } - - address_coin_balances_daily_params_set = - AddressCoinBalancesDaily.params_set(address_coin_balances_params_with_block_timestamp_set) - - CoinBalanceDailyUpdater.add_daily_balances_params(address_coin_balances_daily_params_set) - Chain.import(%{ addresses: %{params: addresses_params}, address_coin_balances: %{params: address_coin_balances_params_set}, From 005b1b20ac6918c21b2516b0eb60313ded4c06e3 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Wed, 6 Dec 2023 13:55:55 +0300 Subject: [PATCH 148/607] Add /api/v2/transactions/{transaction_hash_param}/summary endpoint + Transaction Interpretation Service interface --- .github/workflows/config.yml | 26 ++-- CHANGELOG.md | 2 + .../lib/block_scout_web/api_router.ex | 1 + .../controllers/api/v2/fallback_controller.ex | 8 + .../api/v2/transaction_controller.ex | 92 ++++++----- .../transaction_interpretation.ex | 147 ++++++++++++++++++ .../views/api/v2/transaction_view.ex | 66 ++++---- apps/explorer/lib/explorer/helper.ex | 11 ++ .../rust_verifier_interface_behaviour.ex | 4 +- .../smart_contract/sig_provider_interface.ex | 4 +- .../lib/explorer/utility/microservice.ex | 15 ++ .../lib/explorer/utility/rust_service.ex | 15 -- .../lib/explorer/visualize/sol2uml.ex | 4 +- config/runtime.exs | 4 + 14 files changed, 296 insertions(+), 103 deletions(-) create mode 100644 apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex create mode 100644 apps/explorer/lib/explorer/utility/microservice.ex delete mode 100644 apps/explorer/lib/explorer/utility/rust_service.ex diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 247f01b0e87d..2d672f01197e 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -72,7 +72,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps- @@ -130,7 +130,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -154,7 +154,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -183,7 +183,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -227,7 +227,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -253,7 +253,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -282,7 +282,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -330,7 +330,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -376,7 +376,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -438,7 +438,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -498,7 +498,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -569,7 +569,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -637,7 +637,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" diff --git a/CHANGELOG.md b/CHANGELOG.md index a4ab195fdb21..845eff7b0d2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Features +- [#8957](https://github.com/blockscout/blockscout/pull/8957) - Add Tx Interpreter Service integration + ### Fixes ### Chore diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index dee973786cfe..7880bd15af05 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -216,6 +216,7 @@ defmodule BlockScoutWeb.ApiRouter do get("/:transaction_hash_param/logs", V2.TransactionController, :logs) get("/:transaction_hash_param/raw-trace", V2.TransactionController, :raw_trace) get("/:transaction_hash_param/state-changes", V2.TransactionController, :state_changes) + get("/:transaction_hash_param/summary", V2.TransactionController, :summary) end scope "/blocks" do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex index 691d8de75b91..c7f1fc3692ee 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex @@ -26,6 +26,7 @@ defmodule BlockScoutWeb.API.V2.FallbackController do @address_not_found "Address not found" @address_is_not_smart_contract "Address is not smart-contract" @empty_response "Empty response" + @tx_interpreter_service_disabled "Transaction Interpretation Service is not enabled" def call(conn, {:format, _params}) do Logger.error(fn -> @@ -256,4 +257,11 @@ defmodule BlockScoutWeb.API.V2.FallbackController do |> put_view(ApiView) |> render(:message, %{message: @empty_response}) end + + def call(conn, {:tx_interpreter_enabled, false}) do + conn + |> put_status(404) + |> put_view(ApiView) + |> render(:message, %{message: @tx_interpreter_service_disabled}) + end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex index 43b63b6f693d..f86c06c3cd77 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -23,8 +23,9 @@ defmodule BlockScoutWeb.API.V2.TransactionController do ] alias BlockScoutWeb.AccessHelper + alias BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation, as: TransactionInterpretationService alias BlockScoutWeb.Models.TransactionStateHelper - alias Explorer.Chain + alias Explorer.{Chain, Helper} alias Explorer.Chain.Zkevm.Reader alias Indexer.Fetcher.FirstTraceOnDemand @@ -36,7 +37,6 @@ defmodule BlockScoutWeb.API.V2.TransactionController do [created_contract_address: :token] => :optional, [from_address: :names] => :optional, [to_address: :names] => :optional, - # as far as I remember this needed for substituting implementation name in `to` address instead of is's real name (in transactions) [to_address: :smart_contract] => :optional } @@ -95,16 +95,11 @@ defmodule BlockScoutWeb.API.V2.TransactionController do necessity_by_association_with_actions end - with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, - {:not_found, {:ok, transaction}} <- - {:not_found, - Chain.hash_to_transaction( - transaction_hash, - necessity_by_association: necessity_by_association, - api?: true - )}, - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params), + with {:ok, transaction, _transaction_hash} <- + validate_transaction(transaction_hash_string, params, + necessity_by_association: necessity_by_association, + api?: true + ), preloaded <- Chain.preload_token_transfers(transaction, @token_transfers_in_tx_necessity_by_association, @api_true, false) do conn @@ -183,11 +178,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do """ @spec raw_trace(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} def raw_trace(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do - with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, - {:not_found, {:ok, transaction}} <- - {:not_found, Chain.hash_to_transaction(transaction_hash, @api_true)}, - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do + with {:ok, transaction, transaction_hash} <- validate_transaction(transaction_hash_string, params) do if is_nil(transaction.block_number) do conn |> put_status(200) @@ -216,11 +207,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do """ @spec token_transfers(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} def token_transfers(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do - with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, - {:not_found, {:ok, transaction}} <- - {:not_found, Chain.hash_to_transaction(transaction_hash, @api_true)}, - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do + with {:ok, _transaction, transaction_hash} <- validate_transaction(transaction_hash_string, params) do paging_options = paging_options(params) full_options = @@ -252,11 +239,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do """ @spec internal_transactions(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} def internal_transactions(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do - with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, - {:not_found, {:ok, transaction}} <- - {:not_found, Chain.hash_to_transaction(transaction_hash, @api_true)}, - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do + with {:ok, _transaction, transaction_hash} <- validate_transaction(transaction_hash_string, params) do full_options = @internal_transaction_necessity_by_association |> Keyword.merge(paging_options(params)) @@ -284,11 +267,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do """ @spec logs(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} def logs(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do - with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, - {:not_found, {:ok, transaction}} <- - {:not_found, Chain.hash_to_transaction(transaction_hash, @api_true)}, - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do + with {:ok, _transaction, transaction_hash} <- validate_transaction(transaction_hash_string, params) do full_options = [ necessity_by_association: %{ @@ -323,16 +302,12 @@ defmodule BlockScoutWeb.API.V2.TransactionController do """ @spec state_changes(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} def state_changes(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do - with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, - {:not_found, {:ok, transaction}} <- - {:not_found, - Chain.hash_to_transaction(transaction_hash, - necessity_by_association: - Map.merge(@transaction_necessity_by_association, %{[block: [miner: :names]] => :optional}), - api?: true - )}, - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do + with {:ok, transaction, _transaction_hash} <- + validate_transaction(transaction_hash_string, params, + necessity_by_association: + Map.merge(@transaction_necessity_by_association, %{[block: [miner: :names]] => :optional}), + api?: true + ) do state_changes_plus_next_page = transaction |> TransactionStateHelper.state_changes(params |> paging_options() |> Keyword.merge(api?: true)) @@ -376,4 +351,37 @@ defmodule BlockScoutWeb.API.V2.TransactionController do }) end end + + @doc """ + Function to handle GET requests to `/api/v2/transactions/:transaction_hash_param/summary` endpoint. + """ + @spec summary(any, map) :: + {:format, :error} + | {:not_found, {:error, :not_found}} + | {:restricted_access, true} + | {:tx_interpreter_enabled, boolean} + | Plug.Conn.t() + def summary(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do + with {:tx_interpreter_enabled, true} <- {:tx_interpreter_enabled, TransactionInterpretationService.enabled?()}, + {:ok, transaction, _transaction_hash} <- validate_transaction(transaction_hash_string, params) do + response = + case TransactionInterpretationService.interpret(transaction) do + {:ok, response} -> Helper.maybe_decode(response) + {:error, error} -> %{error: error} + end + + conn + |> json(response) + end + end + + defp validate_transaction(transaction_hash_string, params, options \\ @api_true) do + with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, + {:not_found, {:ok, transaction}} <- + {:not_found, Chain.hash_to_transaction(transaction_hash, options)}, + {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), + {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do + {:ok, transaction, transaction_hash} + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex new file mode 100644 index 000000000000..8ad149081d3c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex @@ -0,0 +1,147 @@ +defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do + @moduledoc """ + Module to interact with Transaction Interpretation Service + """ + + alias BlockScoutWeb.API.V2.{Helper, TransactionView} + alias Explorer.Chain + alias Explorer.Chain.Transaction + alias HTTPoison.Response + + import Explorer.Utility.Microservice, only: [base_url: 2] + + require Logger + + @post_timeout :timer.minutes(5) + @request_error_msg "Error while sending request to Transaction Interpretation Service" + @api_true api?: true + @items_limit 50 + + @spec interpret(any) :: {:error, :disabled | binary} | {:ok, any} + def interpret(transaction) do + if enabled?() do + url = interpret_url() + + body = prepare_request_body(transaction) + + http_post_request(url, body) + else + {:error, :disabled} + end + end + + defp http_post_request(url, body) do + headers = [{"Content-Type", "application/json"}] + + case HTTPoison.post(url, Jason.encode!(body), headers, recv_timeout: @post_timeout) do + {:ok, %Response{body: body, status_code: 200}} -> + {:ok, body} + + error -> + old_truncate = Application.get_env(:logger, :truncate) + Logger.configure(truncate: :infinity) + + Logger.error(fn -> + [ + "Error while sending request to microservice url: #{url}, body: #{inspect(body, limit: :infinity, printable_limit: :infinity)}: ", + inspect(error, limit: :infinity, printable_limit: :infinity) + ] + end) + + Logger.configure(truncate: old_truncate) + {:error, @request_error_msg} + end + end + + defp config do + Application.get_env(:block_scout_web, __MODULE__) + end + + def enabled?, do: config()[:enabled] + + defp interpret_url do + base_url(:block_scout_web, __MODULE__) <> "/transactions/summary" + end + + defp prepare_request_body(transaction) do + preloaded_transaction = + Chain.select_repo(@api_true).preload(transaction, [ + :transaction_actions, + to_address: [:names, :smart_contract], + from_address: [:names, :smart_contract], + created_contract_address: [:names, :token, :smart_contract] + ]) + + skip_sig_provider? = true + {decoded_input, _abi_acc, _methods_acc} = Transaction.decoded_input_data(transaction, skip_sig_provider?, @api_true) + + decoded_input_data = TransactionView.decoded_input(decoded_input) + + %{ + data: %{ + to: + Helper.address_with_info(nil, preloaded_transaction.to_address, preloaded_transaction.to_address_hash, true), + from: + Helper.address_with_info( + nil, + preloaded_transaction.from_address, + preloaded_transaction.from_address_hash, + true + ), + hash: transaction.hash, + type: transaction.type, + value: transaction.value, + method: TransactionView.method_name(transaction, decoded_input), + status: transaction.status, + actions: TransactionView.transaction_actions(transaction.transaction_actions), + tx_types: TransactionView.tx_types(transaction), + raw_input: transaction.input, + decoded_input: decoded_input_data, + token_transfers: prepare_token_transfers(preloaded_transaction, decoded_input) + }, + logs_data: %{items: prepare_logs(transaction)} + } + end + + defp prepare_token_transfers(transaction, decoded_input) do + full_options = + [ + necessity_by_association: %{ + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional + } + ] + |> Keyword.merge(@api_true) + + transaction.hash + |> Chain.transaction_to_token_transfers(full_options) + |> Chain.flat_1155_batch_token_transfers() + |> Enum.take(@items_limit) + |> Enum.map(&TransactionView.prepare_token_transfer(&1, nil, decoded_input)) + end + + defp prepare_logs(transaction) do + full_options = + [ + necessity_by_association: %{ + [address: :names] => :optional, + [address: :smart_contract] => :optional, + address: :optional + } + ] + |> Keyword.merge(@api_true) + + logs = + transaction.hash + |> Chain.transaction_to_logs(full_options) + |> Enum.take(@items_limit) + + decoded_logs = TransactionView.decode_logs(logs, true) + + logs + |> Enum.zip(decoded_logs) + |> Enum.map(fn {log, decoded_log} -> TransactionView.prepare_log(log, transaction.hash, decoded_log, true) end) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index e1ddea367b68..3314b06e58f0 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -184,7 +184,8 @@ defmodule BlockScoutWeb.API.V2.TransactionView do } end - defp decode_logs(logs, skip_sig_provider?) do + @spec decode_logs([Log.t()], boolean) :: [tuple] + def decode_logs(logs, skip_sig_provider?) do {result, _, _} = Enum.reduce(logs, {[], %{}, %{}}, fn log, {results, contracts_acc, events_acc} -> {result, contracts_acc, events_acc} = @@ -283,12 +284,12 @@ defmodule BlockScoutWeb.API.V2.TransactionView do } end - def prepare_log(log, transaction_or_hash, decoded_log) do + def prepare_log(log, transaction_or_hash, decoded_log, tags_for_address_needed? \\ false) do decoded = process_decoded_log(decoded_log) %{ "tx_hash" => get_tx_hash(transaction_or_hash), - "address" => Helper.address_with_info(nil, log.address, log.address_hash, false), + "address" => Helper.address_with_info(nil, log.address, log.address_hash, tags_for_address_needed?), "topics" => [ log.first_topic, log.second_topic, @@ -573,9 +574,9 @@ defmodule BlockScoutWeb.API.V2.TransactionView do def token_transfers_overflow(token_transfers, _), do: Enum.count(token_transfers) > Chain.get_token_transfers_per_transaction_preview_count() - defp transaction_actions(%NotLoaded{}), do: [] + def transaction_actions(%NotLoaded{}), do: [] - defp transaction_actions(actions) do + def transaction_actions(actions) do render("transaction_actions.json", %{actions: actions}) end @@ -617,7 +618,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end end - defp decoded_input(decoded_input) do + def decoded_input(decoded_input) do case decoded_input do {:ok, method_id, text, mapping} -> render(__MODULE__, "decoded_input.json", method_id: method_id, text: text, mapping: mapping, error?: false) @@ -695,17 +696,17 @@ defmodule BlockScoutWeb.API.V2.TransactionView do |> Timex.diff(right, :milliseconds) end - defp method_name(_, _, skip_sc_check? \\ false) + def method_name(_, _, skip_sc_check? \\ false) - defp method_name(_, {:ok, _method_id, text, _mapping}, _) do + def method_name(_, {:ok, _method_id, text, _mapping}, _) do Transaction.parse_method_name(text, false) end - defp method_name( - %Transaction{to_address: to_address, input: %{bytes: <>}}, - _, - skip_sc_check? - ) do + def method_name( + %Transaction{to_address: to_address, input: %{bytes: <>}}, + _, + skip_sc_check? + ) do if skip_sc_check? || Address.is_smart_contract(to_address) do "0x" <> Base.encode16(method_id, case: :lower) else @@ -713,13 +714,24 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end end - defp method_name(_, _, _) do + def method_name(_, _, _) do nil end - defp tx_types(tx, types \\ [], stage \\ :token_transfer) - - defp tx_types(%Transaction{token_transfers: token_transfers} = tx, types, :token_transfer) do + @spec tx_types( + Explorer.Chain.Transaction.t(), + list, + :coin_transfer + | :contract_call + | :contract_creation + | :rootstock_bridge + | :rootstock_remasc + | :token_creation + | :token_transfer + ) :: list + def tx_types(tx, types \\ [], stage \\ :token_transfer) + + def tx_types(%Transaction{token_transfers: token_transfers} = tx, types, :token_transfer) do types = if (!is_nil(token_transfers) && token_transfers != [] && !match?(%NotLoaded{}, token_transfers)) || tx.has_token_transfers do @@ -731,7 +743,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do tx_types(tx, types, :token_creation) end - defp tx_types(%Transaction{created_contract_address: created_contract_address} = tx, types, :token_creation) do + def tx_types(%Transaction{created_contract_address: created_contract_address} = tx, types, :token_creation) do types = if match?(%Address{}, created_contract_address) && match?(%Token{}, created_contract_address.token) do [:token_creation | types] @@ -742,11 +754,11 @@ defmodule BlockScoutWeb.API.V2.TransactionView do tx_types(tx, types, :contract_creation) end - defp tx_types( - %Transaction{to_address_hash: to_address_hash} = tx, - types, - :contract_creation - ) do + def tx_types( + %Transaction{to_address_hash: to_address_hash} = tx, + types, + :contract_creation + ) do types = if is_nil(to_address_hash) do [:contract_creation | types] @@ -757,7 +769,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do tx_types(tx, types, :contract_call) end - defp tx_types(%Transaction{to_address: to_address} = tx, types, :contract_call) do + def tx_types(%Transaction{to_address: to_address} = tx, types, :contract_call) do types = if Address.is_smart_contract(to_address) do [:contract_call | types] @@ -768,7 +780,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do tx_types(tx, types, :coin_transfer) end - defp tx_types(%Transaction{value: value} = tx, types, :coin_transfer) do + def tx_types(%Transaction{value: value} = tx, types, :coin_transfer) do types = if Decimal.compare(value.value, 0) == :gt do [:coin_transfer | types] @@ -779,7 +791,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do tx_types(tx, types, :rootstock_remasc) end - defp tx_types(tx, types, :rootstock_remasc) do + def tx_types(tx, types, :rootstock_remasc) do types = if Transaction.is_rootstock_remasc_transaction(tx) do [:rootstock_remasc | types] @@ -790,7 +802,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do tx_types(tx, types, :rootstock_bridge) end - defp tx_types(tx, types, :rootstock_bridge) do + def tx_types(tx, types, :rootstock_bridge) do if Transaction.is_rootstock_bridge_transaction(tx) do [:rootstock_bridge | types] else diff --git a/apps/explorer/lib/explorer/helper.ex b/apps/explorer/lib/explorer/helper.ex index 45d92a283068..20d65314b23e 100644 --- a/apps/explorer/lib/explorer/helper.ex +++ b/apps/explorer/lib/explorer/helper.ex @@ -81,4 +81,15 @@ defmodule Explorer.Helper do _ -> %{error: data} end end + + @doc """ + Tries to decode binary to json, return either decoded object, or initial binary + """ + @spec maybe_decode(binary) :: any + def maybe_decode(data) do + case safe_decode_json(data) do + %{error: _} -> data + decoded -> decoded + end + end end diff --git a/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex b/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex index 2466e6e1d572..2c5629d219c0 100644 --- a/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex +++ b/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex @@ -5,7 +5,7 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do defmacro __using__(_) do # credo:disable-for-next-line quote([]) do - alias Explorer.Utility.RustService + alias Explorer.Utility.Microservice alias HTTPoison.Response require Logger @@ -172,7 +172,7 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do def base_api_url, do: "#{base_url()}" <> "/api/v2" def base_url do - RustService.base_url(Explorer.SmartContract.RustVerifierInterfaceBehaviour) + Microservice.base_url(Explorer.SmartContract.RustVerifierInterfaceBehaviour) end def enabled?, do: Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)[:enabled] diff --git a/apps/explorer/lib/explorer/smart_contract/sig_provider_interface.ex b/apps/explorer/lib/explorer/smart_contract/sig_provider_interface.ex index 92bba0d0ec70..b97f9bcd1edf 100644 --- a/apps/explorer/lib/explorer/smart_contract/sig_provider_interface.ex +++ b/apps/explorer/lib/explorer/smart_contract/sig_provider_interface.ex @@ -3,7 +3,7 @@ defmodule Explorer.SmartContract.SigProviderInterface do Adapter for decoding events and function calls with https://github.com/blockscout/blockscout-rs/tree/main/sig-provider """ - alias Explorer.Utility.RustService + alias Explorer.Utility.Microservice alias HTTPoison.Response require Logger @@ -81,7 +81,7 @@ defmodule Explorer.SmartContract.SigProviderInterface do def base_api_url, do: "#{base_url()}" <> "/api/v1/abi" def base_url do - RustService.base_url(__MODULE__) + Microservice.base_url(__MODULE__) end def enabled?, do: Application.get_env(:explorer, __MODULE__)[:enabled] diff --git a/apps/explorer/lib/explorer/utility/microservice.ex b/apps/explorer/lib/explorer/utility/microservice.ex new file mode 100644 index 000000000000..6200f7f7acdf --- /dev/null +++ b/apps/explorer/lib/explorer/utility/microservice.ex @@ -0,0 +1,15 @@ +defmodule Explorer.Utility.Microservice do + @moduledoc """ + Module is responsible for common utils related to microservices. + """ + def base_url(application \\ :explorer, module) do + url = Application.get_env(application, module)[:service_url] + + if String.ends_with?(url, "/") do + url + |> String.slice(0..(String.length(url) - 2)) + else + url + end + end +end diff --git a/apps/explorer/lib/explorer/utility/rust_service.ex b/apps/explorer/lib/explorer/utility/rust_service.ex deleted file mode 100644 index 63f949961252..000000000000 --- a/apps/explorer/lib/explorer/utility/rust_service.ex +++ /dev/null @@ -1,15 +0,0 @@ -defmodule Explorer.Utility.RustService do - @moduledoc """ - Module is responsible for common utils related to rust microservices. - """ - def base_url(module) do - url = Application.get_env(:explorer, module)[:service_url] - - if String.ends_with?(url, "/") do - url - |> String.slice(0..(String.length(url) - 2)) - else - url - end - end -end diff --git a/apps/explorer/lib/explorer/visualize/sol2uml.ex b/apps/explorer/lib/explorer/visualize/sol2uml.ex index 10a6c2a9efda..459531a36299 100644 --- a/apps/explorer/lib/explorer/visualize/sol2uml.ex +++ b/apps/explorer/lib/explorer/visualize/sol2uml.ex @@ -2,7 +2,7 @@ defmodule Explorer.Visualize.Sol2uml do @moduledoc """ Adapter for sol2uml visualizer with https://github.com/blockscout/blockscout-rs/blob/main/visualizer """ - alias Explorer.Utility.RustService + alias Explorer.Utility.Microservice alias HTTPoison.Response require Logger @@ -61,7 +61,7 @@ defmodule Explorer.Visualize.Sol2uml do def base_api_url, do: "#{base_url()}" <> "/api/v1" def base_url do - RustService.base_url(__MODULE__) + Microservice.base_url(__MODULE__) end def enabled?, do: Application.get_env(:explorer, __MODULE__)[:enabled] diff --git a/config/runtime.exs b/config/runtime.exs index ec0cd0427b01..8fe99566ef2e 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -132,6 +132,10 @@ config :block_scout_web, BlockScoutWeb.Chain.Address.CoinBalance, config :block_scout_web, BlockScoutWeb.API.V2, enabled: ConfigHelper.parse_bool_env_var("API_V2_ENABLED", "true") +config :block_scout_web, BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation, + service_url: System.get_env("MICROSERVICE_TX_INTERPRETATION_URL"), + enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_TX_INTERPRETATION_ENABLED") + # Configures Ueberauth's Auth0 auth provider config :ueberauth, Ueberauth.Strategy.Auth0.OAuth, domain: System.get_env("ACCOUNT_AUTH0_DOMAIN"), From ccfadcee82ff691a6b32d7a2fbcc2377081db2dc Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Wed, 13 Dec 2023 13:51:00 +0300 Subject: [PATCH 149/607] Fix review comments; Fix txransaction actions preload --- .../transaction_interpretation.ex | 13 ++++++------ .../views/api/v2/transaction_view.ex | 20 ++++++++++--------- config/runtime.exs | 4 ++-- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex index 8ad149081d3c..30a7f95be06f 100644 --- a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex +++ b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex @@ -17,7 +17,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do @api_true api?: true @items_limit 50 - @spec interpret(any) :: {:error, :disabled | binary} | {:ok, any} + @spec interpret(Transaction.t()) :: {:error, :disabled | binary} | {:ok, any} def interpret(transaction) do if enabled?() do url = interpret_url() @@ -64,7 +64,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do end defp prepare_request_body(transaction) do - preloaded_transaction = + transaction = Chain.select_repo(@api_true).preload(transaction, [ :transaction_actions, to_address: [:names, :smart_contract], @@ -79,13 +79,12 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do %{ data: %{ - to: - Helper.address_with_info(nil, preloaded_transaction.to_address, preloaded_transaction.to_address_hash, true), + to: Helper.address_with_info(nil, transaction.to_address, transaction.to_address_hash, true), from: Helper.address_with_info( nil, - preloaded_transaction.from_address, - preloaded_transaction.from_address_hash, + transaction.from_address, + transaction.from_address_hash, true ), hash: transaction.hash, @@ -97,7 +96,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do tx_types: TransactionView.tx_types(transaction), raw_input: transaction.input, decoded_input: decoded_input_data, - token_transfers: prepare_token_transfers(preloaded_transaction, decoded_input) + token_transfers: prepare_token_transfers(transaction, decoded_input) }, logs_data: %{items: prepare_logs(transaction)} } diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index 3314b06e58f0..a94cecbcf5db 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -720,15 +720,17 @@ defmodule BlockScoutWeb.API.V2.TransactionView do @spec tx_types( Explorer.Chain.Transaction.t(), - list, - :coin_transfer - | :contract_call - | :contract_creation - | :rootstock_bridge - | :rootstock_remasc - | :token_creation - | :token_transfer - ) :: list + [tx_type], + tx_type + ) :: [tx_type] + when tx_type: + :coin_transfer + | :contract_call + | :contract_creation + | :rootstock_bridge + | :rootstock_remasc + | :token_creation + | :token_transfer def tx_types(tx, types \\ [], stage \\ :token_transfer) def tx_types(%Transaction{token_transfers: token_transfers} = tx, types, :token_transfer) do diff --git a/config/runtime.exs b/config/runtime.exs index 8fe99566ef2e..3b530be178d9 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -133,8 +133,8 @@ config :block_scout_web, BlockScoutWeb.Chain.Address.CoinBalance, config :block_scout_web, BlockScoutWeb.API.V2, enabled: ConfigHelper.parse_bool_env_var("API_V2_ENABLED", "true") config :block_scout_web, BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation, - service_url: System.get_env("MICROSERVICE_TX_INTERPRETATION_URL"), - enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_TX_INTERPRETATION_ENABLED") + service_url: System.get_env("MICROSERVICE_TRANSACTION_INTERPRETATION_URL"), + enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_TRANSACTION_INTERPRETATION_ENABLED") # Configures Ueberauth's Auth0 auth provider config :ueberauth, Ueberauth.Strategy.Auth0.OAuth, From ea6e678f6eeecf78225e6eaa7d57b5107fcd6e6c Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Thu, 14 Dec 2023 18:18:10 +0300 Subject: [PATCH 150/607] Add token preload to tx summary endpoint --- .../api/v2/transaction_controller.ex | 5 +-- .../transaction_interpretation.ex | 34 +++++++++++++++++-- apps/explorer/lib/explorer/chain.ex | 7 ++-- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex index f86c06c3cd77..368463461c79 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -25,7 +25,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation, as: TransactionInterpretationService alias BlockScoutWeb.Models.TransactionStateHelper - alias Explorer.{Chain, Helper} + alias Explorer.Chain alias Explorer.Chain.Zkevm.Reader alias Indexer.Fetcher.FirstTraceOnDemand @@ -366,7 +366,8 @@ defmodule BlockScoutWeb.API.V2.TransactionController do {:ok, transaction, _transaction_hash} <- validate_transaction(transaction_hash_string, params) do response = case TransactionInterpretationService.interpret(transaction) do - {:ok, response} -> Helper.maybe_decode(response) + {:ok, response} -> response + {:error, %Jason.DecodeError{}} -> %{error: "Error while tx interpreter response decoding"} {:error, error} -> %{error: error} end diff --git a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex index 30a7f95be06f..ea38800be069 100644 --- a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex +++ b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do Module to interact with Transaction Interpretation Service """ - alias BlockScoutWeb.API.V2.{Helper, TransactionView} + alias BlockScoutWeb.API.V2.{Helper, TokenView, TransactionView} alias Explorer.Chain alias Explorer.Chain.Transaction alias HTTPoison.Response @@ -17,7 +17,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do @api_true api?: true @items_limit 50 - @spec interpret(Transaction.t()) :: {:error, :disabled | binary} | {:ok, any} + @spec interpret(Transaction.t()) :: {:error, :disabled | binary | Jason.DecodeError.t()} | {:ok, any} def interpret(transaction) do if enabled?() do url = interpret_url() @@ -35,7 +35,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do case HTTPoison.post(url, Jason.encode!(body), headers, recv_timeout: @post_timeout) do {:ok, %Response{body: body, status_code: 200}} -> - {:ok, body} + body |> Jason.decode() |> preload_tokens() error -> old_truncate = Application.get_env(:logger, :truncate) @@ -143,4 +143,32 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do |> Enum.zip(decoded_logs) |> Enum.map(fn {log, decoded_log} -> TransactionView.prepare_log(log, transaction.hash, decoded_log, true) end) end + + defp preload_tokens({:ok, %{"success" => true, "data" => %{"summaries" => summaries}}}) do + summaries_updated = + Enum.map(summaries, fn %{"summary_template_variables" => summary_template_variables} = summary -> + summary_template_variables_preloaded = + Enum.reduce(summary_template_variables, %{}, fn {key, value}, acc -> + Map.put(acc, key, preload_token(value)) + end) + + Map.put(summary, "summary_template_variables", summary_template_variables_preloaded) + end) + + {:ok, %{"success" => true, "data" => %{"summaries" => summaries_updated}}} + end + + defp preload_tokens(error), do: error + + defp preload_token(%{"type" => "token", "value" => %{"address" => address_hash_string} = value}), + do: %{ + "type" => "token", + "value" => address_hash_string |> Chain.token_from_address_hash(@api_true) |> token_from_db() |> Map.merge(value) + } + + defp preload_token(other), do: other + + defp token_from_db({:error, _}), do: %{} + + defp token_from_db({:ok, token}), do: TokenView.render("token.json", %{token: token}) end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 6d3f48b91b41..a3d99d4e8479 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -3721,12 +3721,9 @@ defmodule Explorer.Chain do `:required`, and the `t:Token.t/0` has no associated record for that association, then the `t:Token.t/0` will not be included in the list. """ - @spec token_from_address_hash(Hash.Address.t(), [necessity_by_association_option | api?]) :: + @spec token_from_address_hash(Hash.Address.t() | String.t(), [necessity_by_association_option | api?]) :: {:ok, Token.t()} | {:error, :not_found} - def token_from_address_hash( - %Hash{byte_count: unquote(Hash.Address.byte_count())} = hash, - options \\ [] - ) do + def token_from_address_hash(hash, options \\ []) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) query = From 0673e7fd07ae9829bf4d07e0dcf7be85945ba49f Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 15 Dec 2023 11:19:41 +0300 Subject: [PATCH 151/607] Mention new Manual deployment guide in the README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 91130f13aaec..678d7f78d118 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,11 @@ Blockscout currently supports several hundred chains and rollups throughout the See the [project documentation](https://docs.blockscout.com/) for instructions: -- [Requirements](https://docs.blockscout.com/for-developers/information-and-settings/requirements) +- [Manual deployment](https://docs.blockscout.com/for-developers/deployment/manual-deployment-guide) +- [Docker-compose deployment](https://docs.blockscout.com/for-developers/deployment/docker-compose-deployment) +- [Kubernetes deployment](https://docs.blockscout.com/for-developers/deployment/kubernetes-deployment) +- [Manual deployment (backend + old UI)](https://docs.blockscout.com/for-developers/deployment/manual-old-ui) - [Ansible deployment](https://docs.blockscout.com/for-developers/ansible-deployment) -- [Manual deployment](https://docs.blockscout.com/for-developers/manual-deployment) - [ENV variables](https://docs.blockscout.com/for-developers/information-and-settings/env-variables) - [Configuration options](https://docs.blockscout.com/for-developers/configuration-options) From b9318e09d7ccf6b19f086f45e0d5361551ac2c07 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Sun, 17 Dec 2023 18:12:59 +0300 Subject: [PATCH 152/607] Speed up Indexer.Fetcher.TokenInstance.LegacySanitize --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/chain.ex | 50 ----------- .../lib/explorer/chain/token/instance.ex | 28 +++++++ .../explorer/chain/token/instance_test.exs | 83 +++++++++++++++++++ apps/explorer/test/explorer/chain_test.exs | 77 ----------------- .../fetcher/token_instance/legacy_sanitize.ex | 78 ++++++++--------- apps/indexer/lib/indexer/supervisor.ex | 2 +- config/runtime.exs | 2 +- 8 files changed, 149 insertions(+), 172 deletions(-) create mode 100644 apps/explorer/test/explorer/chain/token/instance_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ffaf8eef8ca..967b514acc70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Fixes +- [#9013](https://github.com/blockscout/blockscout/pull/9013) - Speed up `Indexer.Fetcher.TokenInstance.LegacySanitize` - [#8955](https://github.com/blockscout/blockscout/pull/8955) - Remove daily balances updating from BlockReward fetcher ### Chore diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 6d3f48b91b41..43a2ae836f28 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -3554,56 +3554,6 @@ defmodule Explorer.Chain do |> Repo.stream_reduce(initial, reducer) end - @doc """ - Finds all token instances (pairs of contract_address_hash and token_id) which was met in token transfers but has no corresponding entry in token_instances table - """ - @spec stream_not_inserted_token_instances( - initial :: accumulator, - reducer :: (entry :: map(), accumulator -> accumulator) - ) :: {:ok, accumulator} - when accumulator: term() - def stream_not_inserted_token_instances(initial, reducer) when is_function(reducer, 2) do - nft_tokens = - from( - token in Token, - where: token.type == ^"ERC-721" or token.type == ^"ERC-1155", - select: token.contract_address_hash - ) - - token_ids_query = - from( - token_transfer in TokenTransfer, - select: %{ - token_contract_address_hash: token_transfer.token_contract_address_hash, - token_id: fragment("unnest(?)", token_transfer.token_ids) - } - ) - - query = - from( - transfer in subquery(token_ids_query), - inner_join: token in subquery(nft_tokens), - on: token.contract_address_hash == transfer.token_contract_address_hash, - left_join: instance in Instance, - on: - transfer.token_contract_address_hash == instance.token_contract_address_hash and - transfer.token_id == instance.token_id, - where: is_nil(instance.token_id), - select: %{ - contract_address_hash: transfer.token_contract_address_hash, - token_id: transfer.token_id - } - ) - - distinct_query = - from( - q in subquery(query), - distinct: [q.contract_address_hash, q.token_id] - ) - - Repo.stream_reduce(distinct_query, initial, reducer) - end - @doc """ Finds all token instances where metadata never tried to fetch """ diff --git a/apps/explorer/lib/explorer/chain/token/instance.ex b/apps/explorer/lib/explorer/chain/token/instance.ex index 608aec33193e..e4680bb0250f 100644 --- a/apps/explorer/lib/explorer/chain/token/instance.ex +++ b/apps/explorer/lib/explorer/chain/token/instance.ex @@ -413,4 +413,32 @@ defmodule Explorer.Chain.Token.Instance do |> select_merge([ctb: ctb], %{current_token_balance: ctb}) |> Chain.select_repo(options).all() end + + @doc """ + Finds token instances (pairs of contract_address_hash and token_id) which was met in token transfers but has no corresponding entry in token_instances table + """ + @spec not_inserted_token_instances_query(integer()) :: Ecto.Query.t() + def not_inserted_token_instances_query(limit) do + token_transfers_query = + TokenTransfer + |> where([token_transfer], not is_nil(token_transfer.token_ids) and token_transfer.token_ids != ^[]) + |> select([token_transfer], %{ + token_contract_address_hash: token_transfer.token_contract_address_hash, + token_id: fragment("unnest(?)", token_transfer.token_ids) + }) + + token_transfers_query + |> subquery() + |> join(:left, [token_transfer], token_instance in __MODULE__, + on: + token_instance.token_contract_address_hash == token_transfer.token_contract_address_hash and + token_instance.token_id == token_transfer.token_id + ) + |> where([token_transfer, token_instance], is_nil(token_instance.token_id)) + |> select([token_transfer, token_instance], %{ + contract_address_hash: token_transfer.token_contract_address_hash, + token_id: token_transfer.token_id + }) + |> limit(^limit) + end end diff --git a/apps/explorer/test/explorer/chain/token/instance_test.exs b/apps/explorer/test/explorer/chain/token/instance_test.exs new file mode 100644 index 000000000000..96f78e79fcc6 --- /dev/null +++ b/apps/explorer/test/explorer/chain/token/instance_test.exs @@ -0,0 +1,83 @@ +defmodule Explorer.Chain.Token.InstanceTest do + use Explorer.DataCase + + alias Explorer.Repo + alias Explorer.Chain.Token.Instance + + describe "stream_not_inserted_token_instances/2" do + test "reduces with given reducer and accumulator for ERC-721 token" do + token_contract_address = insert(:contract_address) + token = insert(:token, contract_address: token_contract_address, type: "ERC-721") + + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + token_transfer = + insert( + :token_transfer, + block_number: 1000, + to_address: build(:address), + transaction: transaction, + token_contract_address: token_contract_address, + token: token, + token_ids: [11] + ) + + assert [result] = 5 |> Instance.not_inserted_token_instances_query() |> Repo.all() + assert result.token_id == List.first(token_transfer.token_ids) + assert result.contract_address_hash == token_transfer.token_contract_address_hash + end + + test "does not fetch token transfers without token_ids" do + token_contract_address = insert(:contract_address) + token = insert(:token, contract_address: token_contract_address, type: "ERC-721") + + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + insert( + :token_transfer, + block_number: 1000, + to_address: build(:address), + transaction: transaction, + token_contract_address: token_contract_address, + token: token, + token_ids: nil + ) + + assert [] = 5 |> Instance.not_inserted_token_instances_query() |> Repo.all() + end + + test "do not fetch records with token instances" do + token_contract_address = insert(:contract_address) + token = insert(:token, contract_address: token_contract_address, type: "ERC-721") + + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + token_transfer = + insert( + :token_transfer, + block_number: 1000, + to_address: build(:address), + transaction: transaction, + token_contract_address: token_contract_address, + token: token, + token_ids: [11] + ) + + insert(:token_instance, + token_id: List.first(token_transfer.token_ids), + token_contract_address_hash: token_transfer.token_contract_address_hash + ) + + assert [] = 5 |> Instance.not_inserted_token_instances_query() |> Repo.all() + end + end +end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 492861338086..eb4acd1ea340 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -4066,83 +4066,6 @@ defmodule Explorer.ChainTest do end end - describe "stream_not_inserted_token_instances/2" do - test "reduces with given reducer and accumulator for ERC-721 token" do - token_contract_address = insert(:contract_address) - token = insert(:token, contract_address: token_contract_address, type: "ERC-721") - - transaction = - :transaction - |> insert() - |> with_block(insert(:block, number: 1)) - - token_transfer = - insert( - :token_transfer, - block_number: 1000, - to_address: build(:address), - transaction: transaction, - token_contract_address: token_contract_address, - token: token, - token_ids: [11] - ) - - assert {:ok, [result]} = Chain.stream_not_inserted_token_instances([], &[&1 | &2]) - assert result.token_id == List.first(token_transfer.token_ids) - assert result.contract_address_hash == token_transfer.token_contract_address_hash - end - - test "does not fetch token transfers without token_ids" do - token_contract_address = insert(:contract_address) - token = insert(:token, contract_address: token_contract_address, type: "ERC-721") - - transaction = - :transaction - |> insert() - |> with_block(insert(:block, number: 1)) - - insert( - :token_transfer, - block_number: 1000, - to_address: build(:address), - transaction: transaction, - token_contract_address: token_contract_address, - token: token, - token_ids: nil - ) - - assert {:ok, []} = Chain.stream_not_inserted_token_instances([], &[&1 | &2]) - end - - test "do not fetch records with token instances" do - token_contract_address = insert(:contract_address) - token = insert(:token, contract_address: token_contract_address, type: "ERC-721") - - transaction = - :transaction - |> insert() - |> with_block(insert(:block, number: 1)) - - token_transfer = - insert( - :token_transfer, - block_number: 1000, - to_address: build(:address), - transaction: transaction, - token_contract_address: token_contract_address, - token: token, - token_ids: [11] - ) - - insert(:token_instance, - token_id: List.first(token_transfer.token_ids), - token_contract_address_hash: token_transfer.token_contract_address_hash - ) - - assert {:ok, []} = Chain.stream_not_inserted_token_instances([], &[&1 | &2]) - end - end - describe "transaction_has_token_transfers?/1" do test "returns true if transaction has token transfers" do transaction = insert(:transaction) diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/legacy_sanitize.ex b/apps/indexer/lib/indexer/fetcher/token_instance/legacy_sanitize.ex index 1fe11e1d90dc..391353ba3788 100644 --- a/apps/indexer/lib/indexer/fetcher/token_instance/legacy_sanitize.ex +++ b/apps/indexer/lib/indexer/fetcher/token_instance/legacy_sanitize.ex @@ -1,58 +1,50 @@ defmodule Indexer.Fetcher.TokenInstance.LegacySanitize do @moduledoc """ - This fetcher is stands for creating token instances which wasn't inserted yet and index meta for them. Legacy is because now we token instances inserted on block import and this fetcher is only for historical and unfetched for some reasons data + This fetcher is stands for creating token instances which wasn't inserted yet and index meta for them. + Legacy is because now we token instances inserted on block import and this fetcher is only for historical and unfetched for some reasons data """ - use Indexer.Fetcher, restart: :permanent - use Spandex.Decorators + use GenServer, restart: :transient - import Indexer.Fetcher.TokenInstance.Helper - - alias Explorer.Chain - alias Indexer.BufferedTask - - @behaviour BufferedTask + alias Explorer.Chain.Token.Instance + alias Explorer.Repo - @default_max_batch_size 10 - @default_max_concurrency 10 - @doc false - def child_spec([init_options, gen_server_options]) do - merged_init_opts = - defaults() - |> Keyword.merge(init_options) - |> Keyword.merge(state: []) + import Indexer.Fetcher.TokenInstance.Helper - Supervisor.child_spec({BufferedTask, [{__MODULE__, merged_init_opts}, gen_server_options]}, id: __MODULE__) + def start_link(_) do + concurrency = Application.get_env(:indexer, __MODULE__)[:concurrency] + batch_size = Application.get_env(:indexer, __MODULE__)[:batch_size] + GenServer.start_link(__MODULE__, %{concurrency: concurrency, batch_size: batch_size}, name: __MODULE__) end - @impl BufferedTask - def init(initial_acc, reducer, _) do - {:ok, acc} = - Chain.stream_not_inserted_token_instances(initial_acc, fn data, acc -> - reducer.(data, acc) - end) + @impl true + def init(opts) do + GenServer.cast(__MODULE__, :backfill) - acc + {:ok, opts} end - @impl BufferedTask - def run(token_instances, _) when is_list(token_instances) do - token_instances - |> Enum.filter(fn %{contract_address_hash: hash, token_id: token_id} -> - not Chain.token_instance_exists?(token_id, hash) - end) - |> batch_fetch_instances() - - :ok + @impl true + def handle_cast(:backfill, %{concurrency: concurrency, batch_size: batch_size} = state) do + instances_to_fetch = + (concurrency * batch_size) + |> Instance.not_inserted_token_instances_query() + |> Repo.all() + + if Enum.empty?(instances_to_fetch) do + {:stop, :normal, state} + else + instances_to_fetch + |> Enum.uniq() + |> Enum.chunk_every(batch_size) + |> Enum.map(&process_batch/1) + |> Task.await_many(:infinity) + + GenServer.cast(__MODULE__, :backfill) + + {:noreply, state} + end end - defp defaults do - [ - flush_interval: :infinity, - max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency, - max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size, - poll: false, - task_supervisor: __MODULE__.TaskSupervisor - ] - end + defp process_batch(batch), do: Task.async(fn -> batch_fetch_instances(batch) end) end diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index 007eab9e5db3..10a745512bd0 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -120,7 +120,7 @@ defmodule Indexer.Supervisor do {TokenInstanceRealtime.Supervisor, [[memory_monitor: memory_monitor]]}, {TokenInstanceRetry.Supervisor, [[memory_monitor: memory_monitor]]}, {TokenInstanceSanitize.Supervisor, [[memory_monitor: memory_monitor]]}, - {TokenInstanceLegacySanitize.Supervisor, [[memory_monitor: memory_monitor]]}, + {TokenInstanceLegacySanitize, [[memory_monitor: memory_monitor]]}, configure(TransactionAction.Supervisor, [[memory_monitor: memory_monitor]]), {ContractCode.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, diff --git a/config/runtime.exs b/config/runtime.exs index ec0cd0427b01..34de34ab9b1d 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -575,7 +575,7 @@ config :indexer, Indexer.Fetcher.TokenInstance.Sanitize, batch_size: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_SANITIZE_BATCH_SIZE", 10) config :indexer, Indexer.Fetcher.TokenInstance.LegacySanitize, - concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_CONCURRENCY", 10), + concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_CONCURRENCY", 2), batch_size: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_BATCH_SIZE", 10) config :indexer, Indexer.Fetcher.InternalTransaction, From 68d5156421972265bad34cce3d2a797519127bbf Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Sun, 17 Dec 2023 18:55:39 +0300 Subject: [PATCH 153/607] Decrease amount of NFT in address collection: 15 -> 9 --- CHANGELOG.md | 1 + .../controllers/api/v2/address_controller_test.exs | 10 ++++++---- apps/explorer/lib/explorer/chain/token/instance.ex | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ffaf8eef8ca..02587417eb29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Chore +- [#9014](https://github.com/blockscout/blockscout/pull/9014) - Decrease amount of NFT in address collection: 15 -> 9 - [#8991](https://github.com/blockscout/blockscout/pull/8991) - Manage DB queue target via runtime env var
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index 13ee1e884f1a..690c9e1bfbc2 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -26,6 +26,8 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do import Explorer.Chain, only: [hash_to_lower_case_string: 1] import Mox + @instances_amount_in_collection 9 + setup :set_mox_global setup :verify_on_exit! @@ -2742,10 +2744,10 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do token_name = token.name amount = to_string(ctb.distinct_token_instances_count || ctb.value) - assert Enum.count(json["token_instances"]) == 15 + assert Enum.count(json["token_instances"]) == @instances_amount_in_collection token_instances - |> Enum.take(15) + |> Enum.take(@instances_amount_in_collection) |> Enum.with_index() |> Enum.each(fn {instance, index} -> compare_token_instance_in_collection(instance, Enum.at(json["token_instances"], index)) @@ -2763,10 +2765,10 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do token_name = token.name amount = to_string(amount) - assert Enum.count(json["token_instances"]) == 15 + assert Enum.count(json["token_instances"]) == @instances_amount_in_collection token_instances - |> Enum.take(15) + |> Enum.take(@instances_amount_in_collection) |> Enum.with_index() |> Enum.each(fn {instance, index} -> compare_token_instance_in_collection(instance, Enum.at(json["token_instances"], index)) diff --git a/apps/explorer/lib/explorer/chain/token/instance.ex b/apps/explorer/lib/explorer/chain/token/instance.ex index 608aec33193e..34bccbecfc29 100644 --- a/apps/explorer/lib/explorer/chain/token/instance.ex +++ b/apps/explorer/lib/explorer/chain/token/instance.ex @@ -224,7 +224,7 @@ defmodule Explorer.Chain.Token.Instance do %{"token_contract_address_hash" => token_contract_address_hash, "token_id" => token_id, "token_type" => "ERC-721"} end - @preloaded_nfts_limit 15 + @preloaded_nfts_limit 9 @spec nft_collections(binary() | Hash.Address.t(), keyword) :: list def nft_collections(address_hash, options \\ []) From e2890b7e5f606c09a14eca02dc97a27979be25bd Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Sun, 17 Dec 2023 23:44:58 +0300 Subject: [PATCH 154/607] Add @spec and @doc; Add just_request_body=true mode to get request body for testing purposes --- .../api/v2/transaction_controller.ex | 10 +++++++++- .../transaction_interpretation.ex | 4 ++++ .../views/api/v2/transaction_view.ex | 17 +++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex index 368463461c79..b9729bad84fe 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -352,10 +352,18 @@ defmodule BlockScoutWeb.API.V2.TransactionController do end end + def summary(conn, %{"transaction_hash_param" => transaction_hash_string, "just_request_body" => "true"} = params) do + with {:tx_interpreter_enabled, true} <- {:tx_interpreter_enabled, TransactionInterpretationService.enabled?()}, + {:ok, transaction, _transaction_hash} <- validate_transaction(transaction_hash_string, params) do + conn + |> json(TransactionInterpretationService.get_request_body(transaction)) + end + end + @doc """ Function to handle GET requests to `/api/v2/transactions/:transaction_hash_param/summary` endpoint. """ - @spec summary(any, map) :: + @spec summary(Plug.Conn.t(), map()) :: {:format, :error} | {:not_found, {:error, :not_found}} | {:restricted_access, true} diff --git a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex index ea38800be069..7e6907668137 100644 --- a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex +++ b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex @@ -30,6 +30,10 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do end end + def get_request_body(transaction) do + prepare_request_body(transaction) + end + defp http_post_request(url, body) do headers = [{"Content-Type", "application/json"}] diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index a94cecbcf5db..0a2f519ea623 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -184,6 +184,9 @@ defmodule BlockScoutWeb.API.V2.TransactionView do } end + @doc """ + Decodes list of logs + """ @spec decode_logs([Log.t()], boolean) :: [tuple] def decode_logs(logs, skip_sig_provider?) do {result, _, _} = @@ -576,6 +579,9 @@ defmodule BlockScoutWeb.API.V2.TransactionView do def transaction_actions(%NotLoaded{}), do: [] + @doc """ + Renders transaction actions + """ def transaction_actions(actions) do render("transaction_actions.json", %{actions: actions}) end @@ -618,6 +624,10 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end end + @doc """ + Prepares decoded tx info + """ + @spec decoded_input(any()) :: map() | nil def decoded_input(decoded_input) do case decoded_input do {:ok, method_id, text, mapping} -> @@ -696,6 +706,10 @@ defmodule BlockScoutWeb.API.V2.TransactionView do |> Timex.diff(right, :milliseconds) end + @doc """ + Return method name used in tx + """ + @spec method_name(Transaction.t(), any(), boolean()) :: binary() | nil def method_name(_, _, skip_sc_check? \\ false) def method_name(_, {:ok, _method_id, text, _mapping}, _) do @@ -718,6 +732,9 @@ defmodule BlockScoutWeb.API.V2.TransactionView do nil end + @doc """ + Returns array of token types for tx. + """ @spec tx_types( Explorer.Chain.Transaction.t(), [tx_type], From 9449990e36f21930c49d1e3d187a1ea064a40071 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Sun, 17 Dec 2023 23:51:04 +0300 Subject: [PATCH 155/607] Do not trim debug data from response --- .../microservice_interfaces/transaction_interpretation.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex index 7e6907668137..60d2a3a368a4 100644 --- a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex +++ b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex @@ -148,7 +148,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do |> Enum.map(fn {log, decoded_log} -> TransactionView.prepare_log(log, transaction.hash, decoded_log, true) end) end - defp preload_tokens({:ok, %{"success" => true, "data" => %{"summaries" => summaries}}}) do + defp preload_tokens({:ok, %{"success" => true, "data" => %{"summaries" => summaries} = data}}) do summaries_updated = Enum.map(summaries, fn %{"summary_template_variables" => summary_template_variables} = summary -> summary_template_variables_preloaded = @@ -159,7 +159,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do Map.put(summary, "summary_template_variables", summary_template_variables_preloaded) end) - {:ok, %{"success" => true, "data" => %{"summaries" => summaries_updated}}} + {:ok, %{"success" => true, "data" => Map.put(data, "summaries", summaries_updated)}} end defp preload_tokens(error), do: error From 21e2f46aa605aef2304a63292b82da5a9dbc6b6b Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 18 Dec 2023 13:57:53 +0300 Subject: [PATCH 156/607] Rename burnt_fee_counter to total_gas_used --- .dialyzer-ignore | 2 - CHANGELOG.md | 1 + .../templates/block/_tile.html.eex | 4 +- .../templates/block/overview.html.eex | 8 +- .../templates/transaction/overview.html.eex | 14 ++-- .../lib/block_scout_web/views/address_view.ex | 2 + .../views/api/rpc/address_view.ex | 4 +- .../views/api/v2/block_view.ex | 16 ++-- .../views/api/v2/transaction_view.ex | 6 +- .../lib/block_scout_web/views/wei_helper.ex | 6 +- apps/block_scout_web/priv/gettext/default.pot | 30 +++---- .../priv/gettext/en/LC_MESSAGES/default.po | 30 +++---- apps/explorer/lib/explorer/chain.ex | 63 +------------- apps/explorer/lib/explorer/chain/block.ex | 84 ++++++++++++++++++- apps/explorer/lib/explorer/chain/wei.ex | 9 ++ .../counters/block_burned_fee_counter.ex | 2 +- .../test/explorer/chain/block_test.exs | 44 +++++++++- apps/explorer/test/explorer/chain_test.exs | 42 ---------- apps/indexer/lib/indexer/block/fetcher.ex | 6 +- 19 files changed, 207 insertions(+), 166 deletions(-) diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 67aeedb7fc98..a4baec92256e 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -24,5 +24,3 @@ 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:1452 -lib/explorer/chain/transaction.ex:1453 diff --git a/CHANGELOG.md b/CHANGELOG.md index 859972c71d9f..0bd0c080ebfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Fixes - [#8955](https://github.com/blockscout/blockscout/pull/8955) - Remove daily balances updating from BlockReward fetcher +- [#8846](https://github.com/blockscout/blockscout/pull/8846) - Handle nil gas_price at address view ### Chore diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex index d7b9c2613400..cff7f06adb9c 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex @@ -1,4 +1,4 @@ -<% burned_fee = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurnedFeeCounter.fetch(@block.hash)), else: nil %> +<% burnt_fees = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurnedFeeCounter.fetch(@block.hash)), else: nil %> <% priority_fee = if !is_nil(@block.base_fee_per_gas), do: BlockPriorityFeeCounter.fetch(@block.hash), else: nil %>
@@ -61,7 +61,7 @@ <%= format_wei_value(%Wei{value: priority_fee}, :ether) %> <%= gettext "Priority Fees" %> - <%= format_wei_value(burned_fee, :ether) %> <%= gettext "Burnt Fees" %> + <%= format_wei_value(burnt_fees, :ether) %> <%= gettext "Burnt Fees" %> <% end %> <%= formatted_gas(@block.gas_limit) %> <%= gettext "Gas Limit" %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex index 332ae2b1392a..96fdfbec92c4 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex @@ -1,4 +1,4 @@ -<% burned_fee = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurnedFeeCounter.fetch(@block.hash)), else: nil %> +<% burnt_fees = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurnedFeeCounter.fetch(@block.hash)), else: nil %> <% priority_fee = if !is_nil(@block.base_fee_per_gas), do: BlockPriorityFeeCounter.fetch(@block.hash), else: nil %>
<%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %> @@ -215,7 +215,7 @@ text: Explorer.coin_name() <> " " <> gettext("burned from transactions included in the block (Base fee (per unit of gas) * Gas Used).") %> <%= gettext("Burnt Fees") %> -
<%= format_wei_value(burned_fee, :ether) %>
+
<%= format_wei_value(burnt_fees, :ether) %>
@@ -226,7 +226,7 @@
<%= format_wei_value(%Wei{value: priority_fee}, :ether) %>
- <% end %> + <% end %> <%= if show_reward?(@block.rewards) do %>
<%= for block_reward <- @block.rewards do %> @@ -268,4 +268,4 @@
-<%= render BlockScoutWeb.Advertisement.BannersAdView, "_banner_728.html", conn: @conn %> +<%= render BlockScoutWeb.Advertisement.BannersAdView, "_banner_728.html", conn: @conn %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex index 08a92a4fb370..4e61f77e6990 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex @@ -7,7 +7,7 @@ <% base_fee_per_gas = if block, do: block.base_fee_per_gas, else: nil %> <% max_priority_fee_per_gas = @transaction.max_priority_fee_per_gas %> <% max_fee_per_gas = @transaction.max_fee_per_gas %> -<% burned_fee = +<% burnt_fees = if !is_nil(max_fee_per_gas) and !is_nil(@transaction.gas_used) and !is_nil(base_fee_per_gas) do if Decimal.compare(max_fee_per_gas.value, 0) == :eq do %Wei{value: Decimal.new(0)} @@ -17,7 +17,7 @@ else nil end %> -<% %Wei{value: burned_fee_decimal} = if is_nil(burned_fee), do: %Wei{value: Decimal.new(0)}, else: burned_fee %> +<% %Wei{value: burnt_fee_decimal} = if is_nil(burnt_fees), do: %Wei{value: Decimal.new(0)}, else: burnt_fees %> <% priority_fee_per_gas = if is_nil(max_priority_fee_per_gas) or is_nil(base_fee_per_gas), do: nil, else: Enum.min_by([max_priority_fee_per_gas, Wei.sub(max_fee_per_gas, base_fee_per_gas)], fn x -> Wei.to(x, :wei) end) %> <% priority_fee = if is_nil(priority_fee_per_gas), do: nil, else: Wei.mult(priority_fee_per_gas, @transaction.gas_used) %> <% decoded_input_data = decoded_input_data(@transaction) %> @@ -122,7 +122,7 @@ <% true -> %> <%= render BlockScoutWeb.FormView, "_tag.html", text: formatted_status, additional_classes: ["success", "large"] %> <% end %> - + <%= if confirmations > 0 do %> <%= gettext "Confirmed by " %><%= confirmations %><%= " " <> confirmations_ds_name(confirmations) %> <% end %> @@ -429,17 +429,17 @@
<%= format_wei_value(priority_fee, :ether) %>
- <% end %> - <%= if !is_nil(burned_fee) do %> + <% end %> + <%= if !is_nil(burnt_fees) do %>
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", text: gettext("Amount of") <> " " <> Explorer.coin_name() <> " " <> gettext("burned for this transaction. Equals Block Base Fee per Gas * Gas Used.") %> <%= gettext "Transaction Burnt Fee" %>
-
<%= format_wei_value(burned_fee, :ether) %> +
<%= format_wei_value(burnt_fees, :ether) %> <%= unless empty_exchange_rate?(@exchange_rate) do %> - ( data-usd-exchange-rate=<%= @exchange_rate.usd_value %>>) + ( data-usd-exchange-rate=<%= @exchange_rate.usd_value %>>) <% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex index ed3f54040657..eb56bccdeefd 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex @@ -334,6 +334,8 @@ defmodule BlockScoutWeb.AddressView do end end + defp matching_address_check(nil, nil, _contract?, _truncate), do: nil + defp matching_address_check(%Address{hash: hash} = current_address, %Address{hash: hash}, contract?, truncate) do [ view_module: __MODULE__, diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex index 2d9deed6efb4..263462e9cc54 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex @@ -113,7 +113,7 @@ defmodule BlockScoutWeb.API.RPC.AddressView do "to" => "#{transaction.to_address_hash}", "value" => "#{transaction.value.value}", "gas" => "#{transaction.gas}", - "gasPrice" => "#{transaction.gas_price.value}", + "gasPrice" => "#{transaction.gas_price && transaction.gas_price.value}", "isError" => if(transaction.status == :ok, do: "0", else: "1"), "txreceipt_status" => if(transaction.status == :ok, do: "1", else: "0"), "input" => "#{transaction.input}", @@ -160,7 +160,7 @@ defmodule BlockScoutWeb.API.RPC.AddressView do "tokenDecimal" => to_string(token_transfer.token_decimals), "transactionIndex" => to_string(token_transfer.transaction_index), "gas" => to_string(token_transfer.transaction_gas), - "gasPrice" => to_string(token_transfer.transaction_gas_price.value), + "gasPrice" => to_string(token_transfer.transaction_gas_price && token_transfer.transaction_gas_price.value), "gasUsed" => to_string(token_transfer.transaction_gas_used), "cumulativeGasUsed" => to_string(token_transfer.transaction_cumulative_gas_used), "input" => to_string(token_transfer.transaction_input), diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex index bc6b62d7c712..9c4b40a3516f 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex @@ -3,7 +3,6 @@ defmodule BlockScoutWeb.API.V2.BlockView do alias BlockScoutWeb.BlockView alias BlockScoutWeb.API.V2.{ApiView, Helper} - alias Explorer.Chain alias Explorer.Chain.Block alias Explorer.Counters.BlockPriorityFeeCounter @@ -29,10 +28,10 @@ defmodule BlockScoutWeb.API.V2.BlockView do end def prepare_block(block, _conn, single_block? \\ false) do - burned_fee = Chain.burned_fees(block.transactions, block.base_fee_per_gas) + burnt_fees = Block.burnt_fees(block.transactions, block.base_fee_per_gas) priority_fee = block.base_fee_per_gas && BlockPriorityFeeCounter.fetch(block.hash) - tx_fees = Chain.txn_fees(block.transactions) + transaction_fees = Block.transaction_fees(block.transactions) %{ "height" => block.number, @@ -48,7 +47,7 @@ defmodule BlockScoutWeb.API.V2.BlockView do "gas_limit" => block.gas_limit, "nonce" => block.nonce, "base_fee_per_gas" => block.base_fee_per_gas, - "burnt_fees" => burned_fee, + "burnt_fees" => burnt_fees, "priority_fee" => priority_fee, # "extra_data" => "TODO", "uncles_hashes" => prepare_uncles(block.uncle_relations), @@ -56,9 +55,9 @@ defmodule BlockScoutWeb.API.V2.BlockView do "rewards" => prepare_rewards(block.rewards, block, single_block?), "gas_target_percentage" => gas_target(block), "gas_used_percentage" => gas_used_percentage(block), - "burnt_fees_percentage" => burnt_fees_percentage(burned_fee, tx_fees), + "burnt_fees_percentage" => burnt_fees_percentage(burnt_fees, transaction_fees), "type" => block |> BlockView.block_type() |> String.downcase(), - "tx_fees" => tx_fees, + "tx_fees" => transaction_fees, "withdrawals_count" => count_withdrawals(block) } |> chain_type_fields(block, single_block?) @@ -105,8 +104,9 @@ defmodule BlockScoutWeb.API.V2.BlockView do def burnt_fees_percentage(_, %Decimal{coef: 0}), do: nil - def burnt_fees_percentage(burnt_fees, tx_fees) when not is_nil(tx_fees) and not is_nil(burnt_fees) do - burnt_fees.value |> Decimal.div(tx_fees) |> Decimal.mult(100) |> Decimal.to_float() + def burnt_fees_percentage(burnt_fees, transaction_fees) + when not is_nil(transaction_fees) and not is_nil(burnt_fees) do + burnt_fees.value |> Decimal.div(transaction_fees) |> Decimal.mult(100) |> Decimal.to_float() end def burnt_fees_percentage(_, _), do: nil diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index 0a2f519ea623..84d6f65bd5a4 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -359,7 +359,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do priority_fee_per_gas = priority_fee_per_gas(max_priority_fee_per_gas, base_fee_per_gas, max_fee_per_gas) - burned_fee = burned_fee(transaction, max_fee_per_gas, base_fee_per_gas) + burnt_fees = burnt_fees(transaction, max_fee_per_gas, base_fee_per_gas) status = transaction |> Chain.transaction_to_status() |> format_status() @@ -409,7 +409,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do "max_priority_fee_per_gas" => transaction.max_priority_fee_per_gas, "base_fee_per_gas" => base_fee_per_gas, "priority_fee" => priority_fee_per_gas && Wei.mult(priority_fee_per_gas, transaction.gas_used), - "tx_burnt_fee" => burned_fee, + "tx_burnt_fee" => burnt_fees, "nonce" => transaction.nonce, "position" => transaction.index, "revert_reason" => revert_reason, @@ -595,7 +595,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end) end - defp burned_fee(transaction, max_fee_per_gas, base_fee_per_gas) do + defp burnt_fees(transaction, max_fee_per_gas, base_fee_per_gas) do if !is_nil(max_fee_per_gas) and !is_nil(transaction.gas_used) and !is_nil(base_fee_per_gas) do if Decimal.compare(max_fee_per_gas.value, 0) == :eq do %Wei{value: Decimal.new(0)} diff --git a/apps/block_scout_web/lib/block_scout_web/views/wei_helper.ex b/apps/block_scout_web/lib/block_scout_web/views/wei_helper.ex index 3b507d46557c..ba84665daae7 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/wei_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/wei_helper.ex @@ -53,7 +53,11 @@ defmodule BlockScoutWeb.WeiHelper do "10" """ @spec format_wei_value(Wei.t(), Wei.unit(), format_options()) :: String.t() - def format_wei_value(%Wei{} = wei, unit, options \\ []) when unit in @valid_units do + def format_wei_value(_wei, _unit, _options \\ []) + + def format_wei_value(nil, _unit, _options), do: nil + + def format_wei_value(%Wei{} = wei, unit, options) when unit in @valid_units do converted_value = wei |> Wei.to(unit) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index c67d50ebf77a..b4cecbc73d2a 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -556,7 +556,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:56 #: lib/block_scout_web/templates/address/overview.html.eex:275 #: lib/block_scout_web/templates/address_validation/index.html.eex:11 -#: lib/block_scout_web/views/address_view.ex:386 +#: lib/block_scout_web/views/address_view.ex:388 #, elixir-autogen, elixir-format msgid "Blocks Validated" msgstr "" @@ -656,13 +656,13 @@ msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 -#: lib/block_scout_web/views/address_view.ex:379 +#: lib/block_scout_web/views/address_view.ex:381 #, elixir-autogen, elixir-format msgid "Code" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:42 -#: lib/block_scout_web/views/address_view.ex:385 +#: lib/block_scout_web/views/address_view.ex:387 #, elixir-autogen, elixir-format msgid "Coin Balance History" msgstr "" @@ -1084,7 +1084,7 @@ msgstr "" msgid "Decoded" msgstr "" -#: lib/block_scout_web/views/address_view.ex:380 +#: lib/block_scout_web/views/address_view.ex:382 #, elixir-autogen, elixir-format msgid "Decompiled Code" msgstr "" @@ -1485,7 +1485,7 @@ msgstr "" #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:38 #: lib/block_scout_web/views/block_view.ex:22 -#: lib/block_scout_web/views/wei_helper.ex:77 +#: lib/block_scout_web/views/wei_helper.ex:81 #, elixir-autogen, elixir-format msgid "Gwei" msgstr "" @@ -1601,7 +1601,7 @@ msgstr "" #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 #: 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/address_view.ex:378 #: lib/block_scout_web/views/transaction_view.ex:533 #, elixir-autogen, elixir-format msgid "Internal Transactions" @@ -1718,7 +1718,7 @@ msgstr "" #: lib/block_scout_web/templates/address_logs/index.html.eex:10 #: 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/address_view.ex:389 #: lib/block_scout_web/views/transaction_view.ex:534 #, elixir-autogen, elixir-format msgid "Logs" @@ -2208,7 +2208,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:89 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 -#: lib/block_scout_web/views/address_view.ex:381 +#: lib/block_scout_web/views/address_view.ex:383 #: lib/block_scout_web/views/tokens/overview_view.ex:42 #, elixir-autogen, elixir-format msgid "Read Contract" @@ -2216,7 +2216,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:96 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 -#: lib/block_scout_web/views/address_view.ex:382 +#: lib/block_scout_web/views/address_view.ex:384 #, elixir-autogen, elixir-format msgid "Read Proxy" msgstr "" @@ -2903,7 +2903,7 @@ msgstr "" #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 -#: lib/block_scout_web/views/address_view.ex:378 +#: lib/block_scout_web/views/address_view.ex:380 #: 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 @@ -2927,7 +2927,7 @@ msgstr "" #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 #: lib/block_scout_web/templates/layout/_topnav.html.eex:84 #: lib/block_scout_web/templates/tokens/index.html.eex:10 -#: lib/block_scout_web/views/address_view.ex:375 +#: lib/block_scout_web/views/address_view.ex:377 #, elixir-autogen, elixir-format msgid "Tokens" msgstr "" @@ -3100,7 +3100,7 @@ msgstr "" #: lib/block_scout_web/templates/block/overview.html.eex:80 #: lib/block_scout_web/templates/chain/show.html.eex:214 #: lib/block_scout_web/templates/layout/_topnav.html.eex:49 -#: lib/block_scout_web/views/address_view.ex:377 +#: lib/block_scout_web/views/address_view.ex:379 #, elixir-autogen, elixir-format msgid "Transactions" msgstr "" @@ -3444,7 +3444,7 @@ msgstr "" msgid "We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the" msgstr "" -#: lib/block_scout_web/views/wei_helper.ex:76 +#: lib/block_scout_web/views/wei_helper.ex:80 #, elixir-autogen, elixir-format msgid "Wei" msgstr "" @@ -3465,14 +3465,14 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:103 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 -#: lib/block_scout_web/views/address_view.ex:383 +#: lib/block_scout_web/views/address_view.ex:385 #, elixir-autogen, elixir-format msgid "Write Contract" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:110 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 -#: lib/block_scout_web/views/address_view.ex:384 +#: lib/block_scout_web/views/address_view.ex:386 #, elixir-autogen, elixir-format msgid "Write Proxy" msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 0797f9e126fd..ba76c15c789a 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -556,7 +556,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:56 #: lib/block_scout_web/templates/address/overview.html.eex:275 #: lib/block_scout_web/templates/address_validation/index.html.eex:11 -#: lib/block_scout_web/views/address_view.ex:386 +#: lib/block_scout_web/views/address_view.ex:388 #, elixir-autogen, elixir-format msgid "Blocks Validated" msgstr "" @@ -656,13 +656,13 @@ msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 -#: lib/block_scout_web/views/address_view.ex:379 +#: lib/block_scout_web/views/address_view.ex:381 #, elixir-autogen, elixir-format msgid "Code" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:42 -#: lib/block_scout_web/views/address_view.ex:385 +#: lib/block_scout_web/views/address_view.ex:387 #, elixir-autogen, elixir-format msgid "Coin Balance History" msgstr "" @@ -1084,7 +1084,7 @@ msgstr "" msgid "Decoded" msgstr "" -#: lib/block_scout_web/views/address_view.ex:380 +#: lib/block_scout_web/views/address_view.ex:382 #, elixir-autogen, elixir-format msgid "Decompiled Code" msgstr "" @@ -1485,7 +1485,7 @@ msgstr "" #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:38 #: lib/block_scout_web/views/block_view.ex:22 -#: lib/block_scout_web/views/wei_helper.ex:77 +#: lib/block_scout_web/views/wei_helper.ex:81 #, elixir-autogen, elixir-format msgid "Gwei" msgstr "" @@ -1601,7 +1601,7 @@ msgstr "" #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 #: 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/address_view.ex:378 #: lib/block_scout_web/views/transaction_view.ex:533 #, elixir-autogen, elixir-format msgid "Internal Transactions" @@ -1718,7 +1718,7 @@ msgstr "" #: lib/block_scout_web/templates/address_logs/index.html.eex:10 #: 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/address_view.ex:389 #: lib/block_scout_web/views/transaction_view.ex:534 #, elixir-autogen, elixir-format msgid "Logs" @@ -2208,7 +2208,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:89 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 -#: lib/block_scout_web/views/address_view.ex:381 +#: lib/block_scout_web/views/address_view.ex:383 #: lib/block_scout_web/views/tokens/overview_view.ex:42 #, elixir-autogen, elixir-format msgid "Read Contract" @@ -2216,7 +2216,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:96 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 -#: lib/block_scout_web/views/address_view.ex:382 +#: lib/block_scout_web/views/address_view.ex:384 #, elixir-autogen, elixir-format msgid "Read Proxy" msgstr "" @@ -2903,7 +2903,7 @@ msgstr "" #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 -#: lib/block_scout_web/views/address_view.ex:378 +#: lib/block_scout_web/views/address_view.ex:380 #: 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 @@ -2927,7 +2927,7 @@ msgstr "" #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 #: lib/block_scout_web/templates/layout/_topnav.html.eex:84 #: lib/block_scout_web/templates/tokens/index.html.eex:10 -#: lib/block_scout_web/views/address_view.ex:375 +#: lib/block_scout_web/views/address_view.ex:377 #, elixir-autogen, elixir-format msgid "Tokens" msgstr "" @@ -3100,7 +3100,7 @@ msgstr "" #: lib/block_scout_web/templates/block/overview.html.eex:80 #: lib/block_scout_web/templates/chain/show.html.eex:214 #: lib/block_scout_web/templates/layout/_topnav.html.eex:49 -#: lib/block_scout_web/views/address_view.ex:377 +#: lib/block_scout_web/views/address_view.ex:379 #, elixir-autogen, elixir-format msgid "Transactions" msgstr "" @@ -3444,7 +3444,7 @@ msgstr "" msgid "We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the" msgstr "" -#: lib/block_scout_web/views/wei_helper.ex:76 +#: lib/block_scout_web/views/wei_helper.ex:80 #, elixir-autogen, elixir-format msgid "Wei" msgstr "" @@ -3465,14 +3465,14 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:103 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 -#: lib/block_scout_web/views/address_view.ex:383 +#: lib/block_scout_web/views/address_view.ex:385 #, elixir-autogen, elixir-format msgid "Write Contract" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:110 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 -#: lib/block_scout_web/views/address_view.ex:384 +#: lib/block_scout_web/views/address_view.ex:386 #, elixir-autogen, elixir-format msgid "Write Proxy" msgstr "" diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index a3d99d4e8479..edb4fe4e8d2c 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -512,65 +512,6 @@ defmodule Explorer.Chain do end end - def txn_fees(transactions) do - Enum.reduce(transactions, Decimal.new(0), fn %{gas_used: gas_used, gas_price: gas_price}, acc -> - gas_used - |> Decimal.new() - |> Decimal.mult(gas_price_to_decimal(gas_price)) - |> Decimal.add(acc) - end) - end - - defp gas_price_to_decimal(%Wei{} = wei), do: wei.value - defp gas_price_to_decimal(gas_price), do: Decimal.new(gas_price) - - def burned_fees(transactions, base_fee_per_gas) do - burned_fee_counter = - transactions - |> Enum.reduce(Decimal.new(0), fn %{gas_used: gas_used}, acc -> - gas_used - |> Decimal.new() - |> Decimal.add(acc) - end) - - base_fee_per_gas && Wei.mult(base_fee_per_gas_to_wei(base_fee_per_gas), burned_fee_counter) - end - - defp base_fee_per_gas_to_wei(%Wei{} = wei), do: wei - defp base_fee_per_gas_to_wei(base_fee_per_gas), do: %Wei{value: Decimal.new(base_fee_per_gas)} - - @uncle_reward_coef 1 / 32 - def block_reward_by_parts(block, transactions) do - %{hash: block_hash, number: block_number} = block - base_fee_per_gas = Map.get(block, :base_fee_per_gas) - - txn_fees = txn_fees(transactions) - - static_reward = - Repo.one( - from( - er in EmissionReward, - where: fragment("int8range(?, ?) <@ ?", ^block_number, ^(block_number + 1), er.block_range), - select: er.reward - ) - ) || %Wei{value: Decimal.new(0)} - - has_uncles? = is_list(block.uncles) and not Enum.empty?(block.uncles) - - burned_fees = burned_fees(transactions, base_fee_per_gas) - uncle_reward = (has_uncles? && Wei.mult(static_reward, Decimal.from_float(@uncle_reward_coef))) || nil - - %{ - block_number: block_number, - block_hash: block_hash, - miner_hash: block.miner_hash, - static_reward: static_reward, - txn_fees: %Wei{value: txn_fees}, - burned_fees: burned_fees || %Wei{value: Decimal.new(0)}, - uncle_reward: uncle_reward || %Wei{value: Decimal.new(0)} - } - end - @doc """ The `t:Explorer.Chain.Wei.t/0` paid to the miners of the `t:Explorer.Chain.Block.t/0`s with `hash` `Explorer.Chain.Hash.Full.t/0` by the signers of the transactions in those blocks to cover the gas fee @@ -937,6 +878,8 @@ defmodule Explorer.Chain do """ @spec fee(Transaction.t(), :ether | :gwei | :wei) :: {:maximum, Decimal.t()} | {:actual, Decimal.t()} + def fee(%Transaction{gas: _gas, gas_price: nil, gas_used: nil}, _unit), do: {:maximum, nil} + def fee(%Transaction{gas: gas, gas_price: gas_price, gas_used: nil}, unit) do fee = gas_price @@ -946,6 +889,8 @@ defmodule Explorer.Chain do {:maximum, fee} end + def fee(%Transaction{gas_price: nil, gas_used: _gas_used}, _unit), do: {:actual, nil} + def fee(%Transaction{gas_price: gas_price, gas_used: gas_used}, unit) do fee = gas_price diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index b41941fdc91f..9cee9e21dfe7 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -8,7 +8,8 @@ defmodule Explorer.Chain.Block do use Explorer.Schema alias Explorer.Chain.{Address, Block, Gas, Hash, PendingBlockOperation, Transaction, Wei, Withdrawal} - alias Explorer.Chain.Block.{Reward, SecondDegreeRelation} + alias Explorer.Chain.Block.{EmissionReward, Reward, SecondDegreeRelation} + alias Explorer.Repo @optional_attrs ~w(size refetch_needed total_difficulty difficulty base_fee_per_gas)a |> (&(case Application.compile_env(:explorer, :chain_type) do @@ -219,4 +220,85 @@ defmodule Explorer.Chain.Block do order_by: [desc: block.number] ) end + + @doc """ + Calculates transaction fees (gas price * gas used) for the list of transactions (from a single block) + """ + @spec transaction_fees(list()) :: Decimal.t() + def transaction_fees(transactions) do + Enum.reduce(transactions, Decimal.new(0), fn %{gas_used: gas_used, gas_price: gas_price}, acc -> + if gas_price do + gas_used + |> Decimal.new() + |> Decimal.mult(gas_price_to_decimal(gas_price)) + |> Decimal.add(acc) + else + acc + end + end) + end + + defp gas_price_to_decimal(nil), do: nil + defp gas_price_to_decimal(%Wei{} = wei), do: wei.value + defp gas_price_to_decimal(gas_price), do: Decimal.new(gas_price) + + @doc """ + Calculates burnt fees for the list of transactions (from a single block) + """ + @spec burnt_fees(list(), Wei.t()) :: Wei.t() | nil + def burnt_fees(transactions, base_fee_per_gas) do + total_gas_used = + transactions + |> Enum.reduce(Decimal.new(0), fn %{gas_used: gas_used}, acc -> + gas_used + |> Decimal.new() + |> Decimal.add(acc) + end) + + base_fee_per_gas && Wei.mult(base_fee_per_gas_to_wei(base_fee_per_gas), total_gas_used) + end + + defp base_fee_per_gas_to_wei(%Wei{} = wei), do: wei + defp base_fee_per_gas_to_wei(base_fee_per_gas), do: %Wei{value: Decimal.new(base_fee_per_gas)} + + @uncle_reward_coef 1 / 32 + @spec block_reward_by_parts(Block.t(), list()) :: %{ + block_number: block_number(), + block_hash: Hash.Full.t(), + miner_hash: Hash.Address.t(), + static_reward: any(), + transaction_fees: any(), + burnt_fees: Wei.t() | nil, + uncle_reward: Wei.t() | nil | false + } + def block_reward_by_parts(block, transactions) do + %{hash: block_hash, number: block_number} = block + base_fee_per_gas = Map.get(block, :base_fee_per_gas) + + transaction_fees = transaction_fees(transactions) + + static_reward = + Repo.one( + from( + er in EmissionReward, + where: fragment("int8range(?, ?) <@ ?", ^block_number, ^(block_number + 1), er.block_range), + select: er.reward + ) + ) || %Wei{value: Decimal.new(0)} + + has_uncles? = is_list(block.uncles) and not Enum.empty?(block.uncles) + + burnt_fees = burnt_fees(transactions, base_fee_per_gas) + uncle_reward = (has_uncles? && Wei.mult(static_reward, Decimal.from_float(@uncle_reward_coef))) || nil + + %{ + block_number: block_number, + block_hash: block_hash, + miner_hash: block.miner_hash, + static_reward: static_reward, + transaction_fees: %Wei{value: transaction_fees}, + burnt_fees: burnt_fees || %Wei{value: Decimal.new(0)}, + uncle_reward: uncle_reward || %Wei{value: Decimal.new(0)} + } + end end diff --git a/apps/explorer/lib/explorer/chain/wei.ex b/apps/explorer/lib/explorer/chain/wei.ex index 533174ba4524..76167ec33687 100644 --- a/apps/explorer/lib/explorer/chain/wei.ex +++ b/apps/explorer/lib/explorer/chain/wei.ex @@ -139,6 +139,11 @@ defmodule Explorer.Chain.Wei do %Explorer.Chain.Wei{value: Decimal.new(1_123)} """ @spec sum(Wei.t(), Wei.t()) :: Wei.t() + def sum(%Wei{value: wei_1}, %Wei{value: nil}) do + wei_1 + |> from(:wei) + end + def sum(%Wei{value: wei_1}, %Wei{value: wei_2}) do wei_1 |> Decimal.add(wei_2) @@ -207,6 +212,10 @@ defmodule Explorer.Chain.Wei do """ @spec from(ether(), :ether) :: t() + def from(nil, :ether), do: nil + def from(nil, :wei), do: nil + def from(nil, :gwei), do: nil + def from(%Decimal{} = ether, :ether) do %__MODULE__{value: Decimal.mult(ether, @wei_per_ether)} end diff --git a/apps/explorer/lib/explorer/counters/block_burned_fee_counter.ex b/apps/explorer/lib/explorer/counters/block_burned_fee_counter.ex index 85e9359f4294..f5e3ee00cddc 100644 --- a/apps/explorer/lib/explorer/counters/block_burned_fee_counter.ex +++ b/apps/explorer/lib/explorer/counters/block_burned_fee_counter.ex @@ -7,7 +7,7 @@ defmodule Explorer.Counters.BlockBurnedFeeCounter do alias Explorer.Chain alias Explorer.Counters.Helper - @cache_name :block_burned_fee_counter + @cache_name :block_burnt_fee_counter config = Application.compile_env(:explorer, Explorer.Counters.BlockBurnedFeeCounter) @enable_consolidation Keyword.get(config, :enable_consolidation) diff --git a/apps/explorer/test/explorer/chain/block_test.exs b/apps/explorer/test/explorer/chain/block_test.exs index 35f14c54795c..703cd8609a7b 100644 --- a/apps/explorer/test/explorer/chain/block_test.exs +++ b/apps/explorer/test/explorer/chain/block_test.exs @@ -2,7 +2,7 @@ defmodule Explorer.Chain.BlockTest do use Explorer.DataCase alias Ecto.Changeset - alias Explorer.Chain.Block + alias Explorer.Chain.{Block, Wei} describe "changeset/2" do test "with valid attributes" do @@ -58,4 +58,46 @@ defmodule Explorer.Chain.BlockTest do assert Enum.member?(results, unrewarded_block.hash) end end + + describe "block_reward_by_parts/1" do + setup do + {:ok, emission_reward: insert(:emission_reward)} + end + + test "without uncles", %{emission_reward: %{reward: reward, block_range: range}} do + block = build(:block, number: range.from, base_fee_per_gas: 5, uncles: []) + + tx1 = build(:transaction, gas_price: 1, gas_used: 1, block_number: block.number, block_hash: block.hash) + tx2 = build(:transaction, gas_price: 1, gas_used: 2, block_number: block.number, block_hash: block.hash) + + tx3 = + build(:transaction, + gas_price: 1, + gas_used: 3, + block_number: block.number, + block_hash: block.hash, + max_priority_fee_per_gas: 1 + ) + + expected_transaction_fees = %Wei{value: Decimal.new(6)} + expected_burnt_fees = %Wei{value: Decimal.new(30)} + expected_uncle_reward = %Wei{value: Decimal.new(0)} + + assert %{ + static_reward: ^reward, + transaction_fees: ^expected_transaction_fees, + burnt_fees: ^expected_burnt_fees, + uncle_reward: ^expected_uncle_reward + } = Block.block_reward_by_parts(block, [tx1, tx2, tx3]) + end + + test "with uncles", %{emission_reward: %{reward: reward, block_range: range}} do + block = + build(:block, number: range.from, uncles: ["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273311"]) + + expected_uncle_reward = Wei.mult(reward, Decimal.from_float(1 / 32)) + + assert %{uncle_reward: ^expected_uncle_reward} = Block.block_reward_by_parts(block, []) + end + end end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 492861338086..c3edcf826bf2 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -3133,48 +3133,6 @@ defmodule Explorer.ChainTest do end end - describe "block_reward_by_parts/1" do - setup do - {:ok, emission_reward: insert(:emission_reward)} - end - - test "without uncles", %{emission_reward: %{reward: reward, block_range: range}} do - block = build(:block, number: range.from, base_fee_per_gas: 5, uncles: []) - - tx1 = build(:transaction, gas_price: 1, gas_used: 1, block_number: block.number, block_hash: block.hash) - tx2 = build(:transaction, gas_price: 1, gas_used: 2, block_number: block.number, block_hash: block.hash) - - tx3 = - build(:transaction, - gas_price: 1, - gas_used: 3, - block_number: block.number, - block_hash: block.hash, - max_priority_fee_per_gas: 1 - ) - - expected_txn_fees = %Wei{value: Decimal.new(6)} - expected_burned_fees = %Wei{value: Decimal.new(30)} - expected_uncle_reward = %Wei{value: Decimal.new(0)} - - assert %{ - static_reward: ^reward, - txn_fees: ^expected_txn_fees, - burned_fees: ^expected_burned_fees, - uncle_reward: ^expected_uncle_reward - } = Chain.block_reward_by_parts(block, [tx1, tx2, tx3]) - end - - test "with uncles", %{emission_reward: %{reward: reward, block_range: range}} do - block = - build(:block, number: range.from, uncles: ["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273311"]) - - expected_uncle_reward = Wei.mult(reward, Decimal.from_float(1 / 32)) - - assert %{uncle_reward: ^expected_uncle_reward} = Chain.block_reward_by_parts(block, []) - end - end - describe "gas_payment_by_block_hash/1" do setup do number = 1 diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 713880e401d2..5342b077fbc3 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -407,15 +407,15 @@ defmodule Indexer.Block.Fetcher do def fetch_beneficiaries_manual(block, transactions) do block - |> Chain.block_reward_by_parts(transactions) + |> Block.block_reward_by_parts(transactions) |> reward_parts_to_beneficiaries() end defp reward_parts_to_beneficiaries(reward_parts) do reward = reward_parts.static_reward - |> Wei.sum(reward_parts.txn_fees) - |> Wei.sub(reward_parts.burned_fees) + |> Wei.sum(reward_parts.transaction_fees) + |> Wei.sub(reward_parts.burnt_fees) |> Wei.sum(reward_parts.uncle_reward) MapSet.new([ From 8f10d61d511a166fe273e5441839d79a6f7f9b19 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 27 Nov 2023 12:14:49 +0300 Subject: [PATCH 157/607] Process multiple reviewers comments --- .../templates/block/_tile.html.eex | 2 +- .../templates/block/overview.html.eex | 4 +- .../templates/transaction/overview.html.eex | 2 +- .../lib/block_scout_web/views/address_view.ex | 2 - .../lib/block_scout_web/views/block_view.ex | 2 +- .../lib/block_scout_web/views/wei_helper.ex | 2 +- apps/block_scout_web/priv/gettext/default.pot | 46 +++++++++---------- .../priv/gettext/en/LC_MESSAGES/default.po | 46 +++++++++---------- .../lib/ethereum_jsonrpc/block.ex | 2 +- apps/explorer/config/config.exs | 2 +- apps/explorer/lib/explorer/application.ex | 2 +- apps/explorer/lib/explorer/chain.ex | 4 +- apps/explorer/lib/explorer/chain/block.ex | 12 +++-- apps/explorer/lib/explorer/chain/supply.ex | 2 +- apps/explorer/lib/explorer/chain/wei.ex | 17 +++++-- ..._counter.ex => block_burnt_fee_counter.ex} | 6 +-- .../counters/block_priority_fee_counter.ex | 2 +- 17 files changed, 82 insertions(+), 73 deletions(-) rename apps/explorer/lib/explorer/counters/{block_burned_fee_counter.ex => block_burnt_fee_counter.ex} (91%) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex index cff7f06adb9c..452c73a3feb0 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex @@ -1,4 +1,4 @@ -<% burnt_fees = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurnedFeeCounter.fetch(@block.hash)), else: nil %> +<% burnt_fees = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurntFeeCounter.fetch(@block.hash)), else: nil %> <% priority_fee = if !is_nil(@block.base_fee_per_gas), do: BlockPriorityFeeCounter.fetch(@block.hash), else: nil %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex index 96fdfbec92c4..feee728643a1 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex @@ -1,4 +1,4 @@ -<% burnt_fees = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurnedFeeCounter.fetch(@block.hash)), else: nil %> +<% burnt_fees = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurntFeeCounter.fetch(@block.hash)), else: nil %> <% priority_fee = if !is_nil(@block.base_fee_per_gas), do: BlockPriorityFeeCounter.fetch(@block.hash), else: nil %>
<%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %> @@ -212,7 +212,7 @@
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", - text: Explorer.coin_name() <> " " <> gettext("burned from transactions included in the block (Base fee (per unit of gas) * Gas Used).") %> + text: Explorer.coin_name() <> " " <> gettext("burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used).") %> <%= gettext("Burnt Fees") %>
<%= format_wei_value(burnt_fees, :ether) %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex index 4e61f77e6990..ebd4a61cadc7 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex @@ -434,7 +434,7 @@
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", - text: gettext("Amount of") <> " " <> Explorer.coin_name() <> " " <> gettext("burned for this transaction. Equals Block Base Fee per Gas * Gas Used.") %> + text: gettext("Amount of") <> " " <> Explorer.coin_name() <> " " <> gettext("burnt for this transaction. Equals Block Base Fee per Gas * Gas Used.") %> <%= gettext "Transaction Burnt Fee" %>
<%= format_wei_value(burnt_fees, :ether) %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex index eb56bccdeefd..ed3f54040657 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex @@ -334,8 +334,6 @@ defmodule BlockScoutWeb.AddressView do end end - defp matching_address_check(nil, nil, _contract?, _truncate), do: nil - defp matching_address_check(%Address{hash: hash} = current_address, %Address{hash: hash}, contract?, truncate) do [ view_module: __MODULE__, diff --git a/apps/block_scout_web/lib/block_scout_web/views/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/block_view.ex index e6355faf1945..2f215b478ced 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/block_view.ex @@ -7,7 +7,7 @@ defmodule BlockScoutWeb.BlockView do alias Explorer.Chain alias Explorer.Chain.{Block, Wei} alias Explorer.Chain.Block.Reward - alias Explorer.Counters.{BlockBurnedFeeCounter, BlockPriorityFeeCounter} + alias Explorer.Counters.{BlockBurntFeeCounter, BlockPriorityFeeCounter} @dialyzer :no_match diff --git a/apps/block_scout_web/lib/block_scout_web/views/wei_helper.ex b/apps/block_scout_web/lib/block_scout_web/views/wei_helper.ex index ba84665daae7..72c1c1ee1f3a 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/wei_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/wei_helper.ex @@ -52,7 +52,7 @@ defmodule BlockScoutWeb.WeiHelper do ...> ) "10" """ - @spec format_wei_value(Wei.t(), Wei.unit(), format_options()) :: String.t() + @spec format_wei_value(Wei.t() | nil, Wei.unit(), format_options()) :: String.t() | nil def format_wei_value(_wei, _unit, _options \\ []) def format_wei_value(nil, _unit, _options), do: nil diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index b4cecbc73d2a..38cc6f9a2ee5 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -556,7 +556,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:56 #: lib/block_scout_web/templates/address/overview.html.eex:275 #: lib/block_scout_web/templates/address_validation/index.html.eex:11 -#: lib/block_scout_web/views/address_view.ex:388 +#: lib/block_scout_web/views/address_view.ex:386 #, elixir-autogen, elixir-format msgid "Blocks Validated" msgstr "" @@ -656,13 +656,13 @@ msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 -#: lib/block_scout_web/views/address_view.ex:381 +#: lib/block_scout_web/views/address_view.ex:379 #, elixir-autogen, elixir-format msgid "Code" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:42 -#: lib/block_scout_web/views/address_view.ex:387 +#: lib/block_scout_web/views/address_view.ex:385 #, elixir-autogen, elixir-format msgid "Coin Balance History" msgstr "" @@ -1084,7 +1084,7 @@ msgstr "" msgid "Decoded" msgstr "" -#: lib/block_scout_web/views/address_view.ex:382 +#: lib/block_scout_web/views/address_view.ex:380 #, elixir-autogen, elixir-format msgid "Decompiled Code" msgstr "" @@ -1601,7 +1601,7 @@ msgstr "" #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 #: 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:378 +#: lib/block_scout_web/views/address_view.ex:376 #: lib/block_scout_web/views/transaction_view.ex:533 #, elixir-autogen, elixir-format msgid "Internal Transactions" @@ -1718,7 +1718,7 @@ msgstr "" #: lib/block_scout_web/templates/address_logs/index.html.eex:10 #: 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:389 +#: lib/block_scout_web/views/address_view.ex:387 #: lib/block_scout_web/views/transaction_view.ex:534 #, elixir-autogen, elixir-format msgid "Logs" @@ -2208,7 +2208,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:89 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 -#: lib/block_scout_web/views/address_view.ex:383 +#: lib/block_scout_web/views/address_view.ex:381 #: lib/block_scout_web/views/tokens/overview_view.ex:42 #, elixir-autogen, elixir-format msgid "Read Contract" @@ -2216,7 +2216,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:96 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 -#: lib/block_scout_web/views/address_view.ex:384 +#: lib/block_scout_web/views/address_view.ex:382 #, elixir-autogen, elixir-format msgid "Read Proxy" msgstr "" @@ -2903,7 +2903,7 @@ msgstr "" #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 -#: lib/block_scout_web/views/address_view.ex:380 +#: 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 @@ -2927,7 +2927,7 @@ msgstr "" #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 #: lib/block_scout_web/templates/layout/_topnav.html.eex:84 #: lib/block_scout_web/templates/tokens/index.html.eex:10 -#: lib/block_scout_web/views/address_view.ex:377 +#: lib/block_scout_web/views/address_view.ex:375 #, elixir-autogen, elixir-format msgid "Tokens" msgstr "" @@ -3100,7 +3100,7 @@ msgstr "" #: lib/block_scout_web/templates/block/overview.html.eex:80 #: lib/block_scout_web/templates/chain/show.html.eex:214 #: lib/block_scout_web/templates/layout/_topnav.html.eex:49 -#: lib/block_scout_web/views/address_view.ex:379 +#: lib/block_scout_web/views/address_view.ex:377 #, elixir-autogen, elixir-format msgid "Transactions" msgstr "" @@ -3465,14 +3465,14 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:103 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 -#: lib/block_scout_web/views/address_view.ex:385 +#: lib/block_scout_web/views/address_view.ex:383 #, elixir-autogen, elixir-format msgid "Write Contract" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:110 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 -#: lib/block_scout_web/views/address_view.ex:386 +#: lib/block_scout_web/views/address_view.ex:384 #, elixir-autogen, elixir-format msgid "Write Proxy" msgstr "" @@ -3541,16 +3541,6 @@ msgstr "" msgid "balance of the address" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:437 -#, elixir-autogen, elixir-format -msgid "burned for this transaction. Equals Block Base Fee per Gas * Gas Used." -msgstr "" - -#: lib/block_scout_web/templates/block/overview.html.eex:215 -#, elixir-autogen, elixir-format -msgid "burned from transactions included in the block (Base fee (per unit of gas) * Gas Used)." -msgstr "" - #: lib/block_scout_web/templates/address_contract/index.html.eex:28 #, elixir-autogen, elixir-format msgid "button" @@ -3682,3 +3672,13 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Yul" msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:437 +#, elixir-autogen, elixir-format +msgid "burnt for this transaction. Equals Block Base Fee per Gas * Gas Used." +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:215 +#, elixir-autogen, elixir-format +msgid "burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used)." +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index ba76c15c789a..dbd9a598a406 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -556,7 +556,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:56 #: lib/block_scout_web/templates/address/overview.html.eex:275 #: lib/block_scout_web/templates/address_validation/index.html.eex:11 -#: lib/block_scout_web/views/address_view.ex:388 +#: lib/block_scout_web/views/address_view.ex:386 #, elixir-autogen, elixir-format msgid "Blocks Validated" msgstr "" @@ -656,13 +656,13 @@ msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 -#: lib/block_scout_web/views/address_view.ex:381 +#: lib/block_scout_web/views/address_view.ex:379 #, elixir-autogen, elixir-format msgid "Code" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:42 -#: lib/block_scout_web/views/address_view.ex:387 +#: lib/block_scout_web/views/address_view.ex:385 #, elixir-autogen, elixir-format msgid "Coin Balance History" msgstr "" @@ -1084,7 +1084,7 @@ msgstr "" msgid "Decoded" msgstr "" -#: lib/block_scout_web/views/address_view.ex:382 +#: lib/block_scout_web/views/address_view.ex:380 #, elixir-autogen, elixir-format msgid "Decompiled Code" msgstr "" @@ -1601,7 +1601,7 @@ msgstr "" #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 #: 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:378 +#: lib/block_scout_web/views/address_view.ex:376 #: lib/block_scout_web/views/transaction_view.ex:533 #, elixir-autogen, elixir-format msgid "Internal Transactions" @@ -1718,7 +1718,7 @@ msgstr "" #: lib/block_scout_web/templates/address_logs/index.html.eex:10 #: 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:389 +#: lib/block_scout_web/views/address_view.ex:387 #: lib/block_scout_web/views/transaction_view.ex:534 #, elixir-autogen, elixir-format msgid "Logs" @@ -2208,7 +2208,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:89 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 -#: lib/block_scout_web/views/address_view.ex:383 +#: lib/block_scout_web/views/address_view.ex:381 #: lib/block_scout_web/views/tokens/overview_view.ex:42 #, elixir-autogen, elixir-format msgid "Read Contract" @@ -2216,7 +2216,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:96 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 -#: lib/block_scout_web/views/address_view.ex:384 +#: lib/block_scout_web/views/address_view.ex:382 #, elixir-autogen, elixir-format msgid "Read Proxy" msgstr "" @@ -2903,7 +2903,7 @@ msgstr "" #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 -#: lib/block_scout_web/views/address_view.ex:380 +#: 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 @@ -2927,7 +2927,7 @@ msgstr "" #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 #: lib/block_scout_web/templates/layout/_topnav.html.eex:84 #: lib/block_scout_web/templates/tokens/index.html.eex:10 -#: lib/block_scout_web/views/address_view.ex:377 +#: lib/block_scout_web/views/address_view.ex:375 #, elixir-autogen, elixir-format msgid "Tokens" msgstr "" @@ -3100,7 +3100,7 @@ msgstr "" #: lib/block_scout_web/templates/block/overview.html.eex:80 #: lib/block_scout_web/templates/chain/show.html.eex:214 #: lib/block_scout_web/templates/layout/_topnav.html.eex:49 -#: lib/block_scout_web/views/address_view.ex:379 +#: lib/block_scout_web/views/address_view.ex:377 #, elixir-autogen, elixir-format msgid "Transactions" msgstr "" @@ -3465,14 +3465,14 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:103 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 -#: lib/block_scout_web/views/address_view.ex:385 +#: lib/block_scout_web/views/address_view.ex:383 #, elixir-autogen, elixir-format msgid "Write Contract" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:110 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 -#: lib/block_scout_web/views/address_view.ex:386 +#: lib/block_scout_web/views/address_view.ex:384 #, elixir-autogen, elixir-format msgid "Write Proxy" msgstr "" @@ -3541,16 +3541,6 @@ msgstr "" msgid "balance of the address" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:437 -#, elixir-autogen, elixir-format -msgid "burned for this transaction. Equals Block Base Fee per Gas * Gas Used." -msgstr "" - -#: lib/block_scout_web/templates/block/overview.html.eex:215 -#, elixir-autogen, elixir-format -msgid "burned from transactions included in the block (Base fee (per unit of gas) * Gas Used)." -msgstr "" - #: lib/block_scout_web/templates/address_contract/index.html.eex:28 #, elixir-autogen, elixir-format msgid "button" @@ -3682,3 +3672,13 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Yul" msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:437 +#, elixir-autogen, elixir-format, fuzzy +msgid "burnt for this transaction. Equals Block Base Fee per Gas * Gas Used." +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:215 +#, elixir-autogen, elixir-format, fuzzy +msgid "burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used)." +msgstr "" diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex index 88f0213f98aa..1431d39bde2b 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex @@ -82,7 +82,7 @@ defmodule EthereumJSONRPC.Block do * `uncles`: `t:list/0` of [uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block) `t:EthereumJSONRPC.hash/0`. - * `"baseFeePerGas"` - `t:EthereumJSONRPC.quantity/0` of wei to denote amount of fee burned per unit gas used. Introduced in [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) + * `"baseFeePerGas"` - `t:EthereumJSONRPC.quantity/0` of wei to denote amount of fee burnt per unit gas used. Introduced in [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) * `"withdrawalsRoot"` - `t:EthereumJSONRPC.hash/0` of the root of the withdrawals. #{if Application.compile_env(:explorer, :chain_type) == "rsk" do """ diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index e9d1eb627e9c..500417a2fd7a 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -101,7 +101,7 @@ config :explorer, Explorer.Counters.AddressTokenTransfersCounter, enabled: true, enable_consolidation: true -config :explorer, Explorer.Counters.BlockBurnedFeeCounter, +config :explorer, Explorer.Counters.BlockBurntFeeCounter, enabled: true, enable_consolidation: true diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index f59e56903bc5..666407fbc025 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -113,7 +113,7 @@ defmodule Explorer.Application do configure(Explorer.Counters.AddressTokenUsdSum), configure(Explorer.Counters.TokenHoldersCounter), configure(Explorer.Counters.TokenTransfersCounter), - configure(Explorer.Counters.BlockBurnedFeeCounter), + configure(Explorer.Counters.BlockBurntFeeCounter), configure(Explorer.Counters.BlockPriorityFeeCounter), configure(Explorer.Counters.AverageBlockTime), configure(Explorer.Counters.Bridge), diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index edb4fe4e8d2c..bb6c3d211f98 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -877,7 +877,7 @@ defmodule Explorer.Chain do {:actual, Decimal.new(4)} """ - @spec fee(Transaction.t(), :ether | :gwei | :wei) :: {:maximum, Decimal.t()} | {:actual, Decimal.t()} + @spec fee(Transaction.t(), :ether | :gwei | :wei) :: {:maximum, Decimal.t()} | {:actual, Decimal.t() | nil} def fee(%Transaction{gas: _gas, gas_price: nil, gas_used: nil}, _unit), do: {:maximum, nil} def fee(%Transaction{gas: gas, gas_price: gas_price, gas_used: nil}, unit) do @@ -3452,7 +3452,7 @@ defmodule Explorer.Chain do end @doc """ - The current total number of coins minted minus verifiably burned coins. + The current total number of coins minted minus verifiably burnt coins. """ @spec total_supply :: non_neg_integer() | nil def total_supply do diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index 9cee9e21dfe7..fae356f3bcfa 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -224,7 +224,7 @@ defmodule Explorer.Chain.Block do @doc """ Calculates transaction fees (gas price * gas used) for the list of transactions (from a single block) """ - @spec transaction_fees(list()) :: Decimal.t() + @spec transaction_fees([Transaction.t()]) :: Decimal.t() def transaction_fees(transactions) do Enum.reduce(transactions, Decimal.new(0), fn %{gas_used: gas_used, gas_price: gas_price}, acc -> if gas_price do @@ -245,7 +245,7 @@ defmodule Explorer.Chain.Block do @doc """ Calculates burnt fees for the list of transactions (from a single block) """ - @spec burnt_fees(list(), Wei.t()) :: Wei.t() | nil + @spec burnt_fees(list(), Wei.t() | nil) :: Wei.t() | nil def burnt_fees(transactions, base_fee_per_gas) do total_gas_used = transactions @@ -255,14 +255,18 @@ defmodule Explorer.Chain.Block do |> Decimal.add(acc) end) - base_fee_per_gas && Wei.mult(base_fee_per_gas_to_wei(base_fee_per_gas), total_gas_used) + if is_nil(base_fee_per_gas) do + nil + else + Wei.mult(base_fee_per_gas_to_wei(base_fee_per_gas), total_gas_used) + end end defp base_fee_per_gas_to_wei(%Wei{} = wei), do: wei defp base_fee_per_gas_to_wei(base_fee_per_gas), do: %Wei{value: Decimal.new(base_fee_per_gas)} @uncle_reward_coef 1 / 32 - @spec block_reward_by_parts(Block.t(), list()) :: %{ + @spec block_reward_by_parts(Block.t(), [Transaction.t()]) :: %{ block_number: block_number(), block_hash: Hash.Full.t(), miner_hash: Hash.Address.t(), diff --git a/apps/explorer/lib/explorer/chain/supply.ex b/apps/explorer/lib/explorer/chain/supply.ex index b442e4012597..0287a0dde80b 100644 --- a/apps/explorer/lib/explorer/chain/supply.ex +++ b/apps/explorer/lib/explorer/chain/supply.ex @@ -7,7 +7,7 @@ defmodule Explorer.Chain.Supply do """ @doc """ - The current total number of coins minted minus verifiably burned coins. + The current total number of coins minted minus verifiably burnt coins. """ @callback total :: non_neg_integer() | %Decimal{sign: 1} diff --git a/apps/explorer/lib/explorer/chain/wei.ex b/apps/explorer/lib/explorer/chain/wei.ex index 76167ec33687..1ae38c7ed9c8 100644 --- a/apps/explorer/lib/explorer/chain/wei.ex +++ b/apps/explorer/lib/explorer/chain/wei.ex @@ -138,12 +138,17 @@ defmodule Explorer.Chain.Wei do iex> Explorer.Chain.Wei.sum(first, second) %Explorer.Chain.Wei{value: Decimal.new(1_123)} """ - @spec sum(Wei.t(), Wei.t()) :: Wei.t() + @spec sum(Wei.t() | nil, Wei.t() | nil) :: Wei.t() | nil def sum(%Wei{value: wei_1}, %Wei{value: nil}) do wei_1 |> from(:wei) end + def sum(%Wei{value: nil}, %Wei{value: wei_2}) do + wei_2 + |> from(:wei) + end + def sum(%Wei{value: wei_1}, %Wei{value: wei_2}) do wei_1 |> Decimal.add(wei_2) @@ -211,21 +216,23 @@ defmodule Explorer.Chain.Wei do """ - @spec from(ether(), :ether) :: t() + @spec from(ether() | nil, :ether) :: t() | nil def from(nil, :ether), do: nil - def from(nil, :wei), do: nil - def from(nil, :gwei), do: nil def from(%Decimal{} = ether, :ether) do %__MODULE__{value: Decimal.mult(ether, @wei_per_ether)} end - @spec from(gwei(), :gwei) :: t() + @spec from(gwei(), :gwei) :: t() | nil + def from(nil, :gwei), do: nil + def from(%Decimal{} = gwei, :gwei) do %__MODULE__{value: Decimal.mult(gwei, @wei_per_gwei)} end @spec from(wei(), :wei) :: t() + def from(nil, :wei), do: nil + def from(%Decimal{} = wei, :wei) do %__MODULE__{value: wei} end diff --git a/apps/explorer/lib/explorer/counters/block_burned_fee_counter.ex b/apps/explorer/lib/explorer/counters/block_burnt_fee_counter.ex similarity index 91% rename from apps/explorer/lib/explorer/counters/block_burned_fee_counter.ex rename to apps/explorer/lib/explorer/counters/block_burnt_fee_counter.ex index f5e3ee00cddc..9c87e313ffe7 100644 --- a/apps/explorer/lib/explorer/counters/block_burned_fee_counter.ex +++ b/apps/explorer/lib/explorer/counters/block_burnt_fee_counter.ex @@ -1,6 +1,6 @@ -defmodule Explorer.Counters.BlockBurnedFeeCounter do +defmodule Explorer.Counters.BlockBurntFeeCounter do @moduledoc """ - Caches Block Burned Fee counter. + Caches Block Burnt Fee counter. """ use GenServer @@ -9,7 +9,7 @@ defmodule Explorer.Counters.BlockBurnedFeeCounter do @cache_name :block_burnt_fee_counter - config = Application.compile_env(:explorer, Explorer.Counters.BlockBurnedFeeCounter) + config = Application.compile_env(:explorer, __MODULE__) @enable_consolidation Keyword.get(config, :enable_consolidation) @spec start_link(term()) :: GenServer.on_start() diff --git a/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex b/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex index f51deb4e2e7a..152679fe830d 100644 --- a/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex +++ b/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex @@ -1,6 +1,6 @@ defmodule Explorer.Counters.BlockPriorityFeeCounter do @moduledoc """ - Caches Block Burned Fee counter. + Caches Block Priority Fee counter. """ use GenServer From f2f0febdaedc9670c7485416f5610f76ca81c7b5 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 18 Dec 2023 14:12:26 +0300 Subject: [PATCH 158/607] Process review comment --- apps/explorer/lib/explorer/chain/wei.ex | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/wei.ex b/apps/explorer/lib/explorer/chain/wei.ex index 1ae38c7ed9c8..c542c9081a13 100644 --- a/apps/explorer/lib/explorer/chain/wei.ex +++ b/apps/explorer/lib/explorer/chain/wei.ex @@ -155,6 +155,7 @@ defmodule Explorer.Chain.Wei do |> from(:wei) end + @spec sub(Wei.t(), Wei.t()) :: Wei.t() | nil @doc """ Subtracts two Wei values. @@ -165,6 +166,8 @@ defmodule Explorer.Chain.Wei do iex> Explorer.Chain.Wei.sub(first, second) %Explorer.Chain.Wei{value: Decimal.new(123)} """ + def sub(_, nil), do: nil + def sub(%Wei{value: wei_1}, %Wei{value: wei_2}) do wei_1 |> Decimal.sub(wei_2) @@ -263,17 +266,22 @@ defmodule Explorer.Chain.Wei do """ - @spec to(t(), :ether) :: ether() + @spec to(t(), :ether) :: ether() | nil + def to(nil, :ether), do: nil + def to(%__MODULE__{value: wei}, :ether) do Decimal.div(wei, @wei_per_ether) end - @spec to(t(), :gwei) :: gwei() + @spec to(t(), :gwei) :: gwei() | nil + def to(nil, :gwei), do: nil + def to(%__MODULE__{value: wei}, :gwei) do Decimal.div(wei, @wei_per_gwei) end - @spec to(t(), :wei) :: wei() + @spec to(t(), :wei) :: wei() | nil + def to(nil, :wei), do: nil def to(%__MODULE__{value: wei}, :wei), do: wei end From d7e8171fb8ac08d8c119010738c11885958323dc Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Wed, 13 Dec 2023 13:34:52 +0600 Subject: [PATCH 159/607] Refactor transactions event preloads --- CHANGELOG.md | 1 + apps/block_scout_web/lib/block_scout_web/notifier.ex | 8 +++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fb1fb811eec..043f2425ec84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ### Chore - [#9014](https://github.com/blockscout/blockscout/pull/9014) - Decrease amount of NFT in address collection: 15 -> 9 +- [#8994](https://github.com/blockscout/blockscout/pull/8994) - Refactor transactions event preloads - [#8991](https://github.com/blockscout/blockscout/pull/8991) - Manage DB queue target via runtime env var
diff --git a/apps/block_scout_web/lib/block_scout_web/notifier.ex b/apps/block_scout_web/lib/block_scout_web/notifier.ex index e340c4351df3..1de89cee8b78 100644 --- a/apps/block_scout_web/lib/block_scout_web/notifier.ex +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -191,13 +191,11 @@ defmodule BlockScoutWeb.Notifier do end def handle_event({:chain_event, :transactions, :realtime, transactions}) do - preloads = [:block, created_contract_address: :names, from_address: :names, to_address: :names] + base_preloads = [:block, created_contract_address: :names, from_address: :names, to_address: :names] + preloads = if API_V2.enabled?(), do: [:token_transfers | base_preloads], else: base_preloads transactions - |> Enum.map( - &(&1 - |> Repo.preload(if API_V2.enabled?(), do: [:token_transfers | preloads], else: preloads)) - ) + |> Repo.preload(preloads) |> broadcast_transactions_websocket_v2() |> Enum.map(fn tx -> # Disable parsing of token transfers from websocket for transaction tab because we display token transfers at a separate tab From 35641bef4fe2e77cf82a94c482f1d28612db8036 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 18:48:31 +0000 Subject: [PATCH 160/607] Bump @amplitude/analytics-browser in /apps/block_scout_web/assets Bumps [@amplitude/analytics-browser](https://github.com/amplitude/Amplitude-TypeScript) from 2.3.6 to 2.3.7. - [Release notes](https://github.com/amplitude/Amplitude-TypeScript/releases) - [Commits](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@2.3.6...@amplitude/analytics-browser@2.3.7) --- updated-dependencies: - dependency-name: "@amplitude/analytics-browser" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 46 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index b40fbe7c8901..a24155751c76 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -7,7 +7,7 @@ "name": "blockscout", "license": "GPL-3.0", "dependencies": { - "@amplitude/analytics-browser": "^2.3.6", + "@amplitude/analytics-browser": "^2.3.7", "@fortawesome/fontawesome-free": "^6.5.1", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", @@ -116,15 +116,15 @@ } }, "node_modules/@amplitude/analytics-browser": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.6.tgz", - "integrity": "sha512-P8N19qeoSDPO0crdQwa2yi+T0/UT4v9FqZrtXF3JW4kgxu++dTW/QkQ6bsv29EPg2N54xyr9IOt98q9EkZbA7A==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.7.tgz", + "integrity": "sha512-dk9Q8/pSxM8zyzT5jmWNuDZPicKXK3JQu/1s6ztWD8lWN/vxSIYWaFAdaz60/AM7jkOwxdP8iANMxMyxPv06Rw==", "dependencies": { "@amplitude/analytics-client-common": "^2.0.9", "@amplitude/analytics-core": "^2.1.2", "@amplitude/analytics-types": "^2.3.1", - "@amplitude/plugin-page-view-tracking-browser": "^2.0.16", - "@amplitude/plugin-web-attribution-browser": "^2.0.16", + "@amplitude/plugin-page-view-tracking-browser": "^2.0.17", + "@amplitude/plugin-web-attribution-browser": "^2.0.17", "tslib": "^2.4.1" } }, @@ -174,9 +174,9 @@ "integrity": "sha512-yojBG20qvph0rpCJKb4i/FJa+otqLINEwv//hfzvjnCOcPPyS0YscI8oiRBM0rG7kZIDgaL9a6jPwkqK4ACmcw==" }, "node_modules/@amplitude/plugin-page-view-tracking-browser": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.16.tgz", - "integrity": "sha512-o9YlpXm0BcnUEK47K5xAqEa/q7DOsKGb6F35gF5iGUFXTtBP+QVLTtNujfnfiO4V9g5JrkOhB/Wm2Sr34VNFhQ==", + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.17.tgz", + "integrity": "sha512-AcpNehWJRPNFiyLDbji4ccZ0jD8640EL3XbauFE8IVSsp+a0a8YbGda/5uQrjmepDGC7nS8BIxxBDiIjbDD90g==", "dependencies": { "@amplitude/analytics-client-common": "^2.0.9", "@amplitude/analytics-types": "^2.3.1", @@ -189,9 +189,9 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@amplitude/plugin-web-attribution-browser": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.16.tgz", - "integrity": "sha512-IEYDrtZGJvBRhie7u9xIWNZhd0Z7heRx6A/+dNdERrW/akC1iAVmUyBZPMOR5ywVK7nwsymlVpFbPccuMoYTmg==", + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.17.tgz", + "integrity": "sha512-sdO3MY3QkHOumMfm6shDOFAdV7Kf5VDsuYnyCn4g3F68e3PiKhrSgpjiqLuB0I6PVNBtkFLjTPxafNdHYsGCZQ==", "dependencies": { "@amplitude/analytics-client-common": "^2.0.9", "@amplitude/analytics-core": "^2.1.2", @@ -17852,15 +17852,15 @@ "dev": true }, "@amplitude/analytics-browser": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.6.tgz", - "integrity": "sha512-P8N19qeoSDPO0crdQwa2yi+T0/UT4v9FqZrtXF3JW4kgxu++dTW/QkQ6bsv29EPg2N54xyr9IOt98q9EkZbA7A==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.7.tgz", + "integrity": "sha512-dk9Q8/pSxM8zyzT5jmWNuDZPicKXK3JQu/1s6ztWD8lWN/vxSIYWaFAdaz60/AM7jkOwxdP8iANMxMyxPv06Rw==", "requires": { "@amplitude/analytics-client-common": "^2.0.9", "@amplitude/analytics-core": "^2.1.2", "@amplitude/analytics-types": "^2.3.1", - "@amplitude/plugin-page-view-tracking-browser": "^2.0.16", - "@amplitude/plugin-web-attribution-browser": "^2.0.16", + "@amplitude/plugin-page-view-tracking-browser": "^2.0.17", + "@amplitude/plugin-web-attribution-browser": "^2.0.17", "tslib": "^2.4.1" }, "dependencies": { @@ -17916,9 +17916,9 @@ "integrity": "sha512-yojBG20qvph0rpCJKb4i/FJa+otqLINEwv//hfzvjnCOcPPyS0YscI8oiRBM0rG7kZIDgaL9a6jPwkqK4ACmcw==" }, "@amplitude/plugin-page-view-tracking-browser": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.16.tgz", - "integrity": "sha512-o9YlpXm0BcnUEK47K5xAqEa/q7DOsKGb6F35gF5iGUFXTtBP+QVLTtNujfnfiO4V9g5JrkOhB/Wm2Sr34VNFhQ==", + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.17.tgz", + "integrity": "sha512-AcpNehWJRPNFiyLDbji4ccZ0jD8640EL3XbauFE8IVSsp+a0a8YbGda/5uQrjmepDGC7nS8BIxxBDiIjbDD90g==", "requires": { "@amplitude/analytics-client-common": "^2.0.9", "@amplitude/analytics-types": "^2.3.1", @@ -17933,9 +17933,9 @@ } }, "@amplitude/plugin-web-attribution-browser": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.16.tgz", - "integrity": "sha512-IEYDrtZGJvBRhie7u9xIWNZhd0Z7heRx6A/+dNdERrW/akC1iAVmUyBZPMOR5ywVK7nwsymlVpFbPccuMoYTmg==", + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.17.tgz", + "integrity": "sha512-sdO3MY3QkHOumMfm6shDOFAdV7Kf5VDsuYnyCn4g3F68e3PiKhrSgpjiqLuB0I6PVNBtkFLjTPxafNdHYsGCZQ==", "requires": { "@amplitude/analytics-client-common": "^2.0.9", "@amplitude/analytics-core": "^2.1.2", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 03bbfa3dd87d..af04f9d79097 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^6.5.1", - "@amplitude/analytics-browser": "^2.3.6", + "@amplitude/analytics-browser": "^2.3.7", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", "assert": "^2.1.0", From 5d4b3e7cdba609d90b9fd0723272354138920e15 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 18:48:52 +0000 Subject: [PATCH 161/607] Bump eslint-plugin-import in /apps/block_scout_web/assets Bumps [eslint-plugin-import](https://github.com/import-js/eslint-plugin-import) from 2.29.0 to 2.29.1. - [Release notes](https://github.com/import-js/eslint-plugin-import/releases) - [Changelog](https://github.com/import-js/eslint-plugin-import/blob/main/CHANGELOG.md) - [Commits](https://github.com/import-js/eslint-plugin-import/compare/v2.29.0...v2.29.1) --- updated-dependencies: - dependency-name: eslint-plugin-import dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 30 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index b40fbe7c8901..17aba7d77bd4 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -80,7 +80,7 @@ "css-minimizer-webpack-plugin": "^5.0.1", "eslint": "^8.55.0", "eslint-config-standard": "^17.1.0", - "eslint-plugin-import": "^2.29.0", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^6.1.1", "file-loader": "^6.2.0", @@ -7457,9 +7457,9 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "dependencies": { "array-includes": "^3.1.7", @@ -7478,7 +7478,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -16344,9 +16344,9 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", @@ -23496,9 +23496,9 @@ } }, "eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "requires": { "array-includes": "^3.1.7", @@ -23517,7 +23517,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "dependencies": { "debug": { @@ -30070,9 +30070,9 @@ } }, "tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "requires": { "@types/json5": "^0.0.29", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 03bbfa3dd87d..6daea8525ea7 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -92,7 +92,7 @@ "css-minimizer-webpack-plugin": "^5.0.1", "eslint": "^8.55.0", "eslint-config-standard": "^17.1.0", - "eslint-plugin-import": "^2.29.0", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^6.1.1", "file-loader": "^6.2.0", From c278bf73a1672ccd9ad7ea8d2d68382a9dbde461 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 08:56:18 +0000 Subject: [PATCH 162/607] Bump eslint from 8.55.0 to 8.56.0 in /apps/block_scout_web/assets Bumps [eslint](https://github.com/eslint/eslint) from 8.55.0 to 8.56.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v8.55.0...v8.56.0) --- updated-dependencies: - dependency-name: eslint dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 30 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 17aba7d77bd4..2d35472fd42c 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -78,7 +78,7 @@ "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.8.1", "css-minimizer-webpack-plugin": "^5.0.1", - "eslint": "^8.55.0", + "eslint": "^8.56.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-node": "^11.1.0", @@ -2123,9 +2123,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", - "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -7307,15 +7307,15 @@ } }, "node_modules/eslint": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", - "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -19293,9 +19293,9 @@ } }, "@eslint/js": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", - "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true }, "@ethereumjs/common": { @@ -23253,15 +23253,15 @@ } }, "eslint": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", - "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 6daea8525ea7..9fa3abc74d56 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -90,7 +90,7 @@ "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.8.1", "css-minimizer-webpack-plugin": "^5.0.1", - "eslint": "^8.55.0", + "eslint": "^8.56.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-node": "^11.1.0", From d45ab762583b364db4744223aca630a2c51a4c4c Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Sun, 10 Dec 2023 14:53:33 +0300 Subject: [PATCH 163/607] BENS integration --- CHANGELOG.md | 1 + .../controllers/api/v2/address_controller.ex | 22 +- .../controllers/api/v2/block_controller.ex | 7 +- .../api/v2/main_page_controller.ex | 10 +- .../controllers/api/v2/search_controller.ex | 8 +- .../controllers/api/v2/token_controller.ex | 24 +- .../api/v2/transaction_controller.ex | 19 +- .../api/v2/withdrawal_controller.ex | 3 +- .../block_scout_web/views/api/v2/helper.ex | 9 +- .../views/api/v2/search_view.ex | 3 +- .../account/api/v1/user_controller_test.exs | 6 +- .../api/v2/address_controller_test.exs | 3 +- apps/explorer/lib/explorer/chain/address.ex | 7 +- apps/explorer/lib/explorer/chain/search.ex | 191 ++++++---- .../explorer/microservice_interfaces/bens.ex | 340 ++++++++++++++++++ .../lib/explorer/utility/microservice.ex | 9 + config/runtime.exs | 4 + 17 files changed, 572 insertions(+), 94 deletions(-) create mode 100644 apps/explorer/lib/explorer/microservice_interfaces/bens.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index 043f2425ec84..719f013806c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#8972](https://github.com/blockscout/blockscout/pull/8972) - BENS integration - [#8957](https://github.com/blockscout/blockscout/pull/8957) - Add Tx Interpreter Service integration ### Fixes diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex index 0c77e9ace6b0..e5e99277fa6d 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -20,6 +20,8 @@ defmodule BlockScoutWeb.API.V2.AddressController do nft_token_types_options: 1 ] + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] + alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.API.V2.{BlockView, TransactionView, WithdrawalView} alias Explorer.{Chain, Market} @@ -142,7 +144,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:transactions, %{transactions: transactions, next_page_params: next_page_params}) + |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params}) end end @@ -185,7 +187,10 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params}) + |> render(:token_transfers, %{ + token_transfers: token_transfers |> maybe_preload_ens(), + next_page_params: next_page_params + }) end end @@ -214,7 +219,10 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params}) + |> render(:token_transfers, %{ + token_transfers: token_transfers |> maybe_preload_ens(), + next_page_params: next_page_params + }) end end @@ -245,7 +253,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do |> put_status(200) |> put_view(TransactionView) |> render(:internal_transactions, %{ - internal_transactions: internal_transactions, + internal_transactions: internal_transactions |> maybe_preload_ens(), next_page_params: next_page_params }) end @@ -268,7 +276,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:logs, %{logs: logs, next_page_params: next_page_params}) + |> render(:logs, %{logs: logs |> maybe_preload_ens(), next_page_params: next_page_params}) end end @@ -285,7 +293,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:logs, %{logs: logs, next_page_params: next_page_params}) + |> render(:logs, %{logs: logs |> maybe_preload_ens(), next_page_params: next_page_params}) end end @@ -387,7 +395,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> put_view(WithdrawalView) - |> render(:withdrawals, %{withdrawals: withdrawals, next_page_params: next_page_params}) + |> render(:withdrawals, %{withdrawals: withdrawals |> maybe_preload_ens(), next_page_params: next_page_params}) end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex index a680d7616d40..49e21dcce202 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex @@ -11,6 +11,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do ] import BlockScoutWeb.PagingHelper, only: [delete_parameters_from_next_page_params: 1, select_block_type: 1] + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] alias BlockScoutWeb.API.V2.{TransactionView, WithdrawalView} alias Explorer.Chain @@ -81,7 +82,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do conn |> put_status(200) - |> render(:blocks, %{blocks: blocks, next_page_params: next_page_params}) + |> render(:blocks, %{blocks: blocks |> maybe_preload_ens(), next_page_params: next_page_params}) end def transactions(conn, %{"block_hash_or_number" => block_hash_or_number} = params) do @@ -103,7 +104,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:transactions, %{transactions: transactions, next_page_params: next_page_params}) + |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params}) end end @@ -122,7 +123,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do conn |> put_status(200) |> put_view(WithdrawalView) - |> render(:withdrawals, %{withdrawals: withdrawals, next_page_params: next_page_params}) + |> render(:withdrawals, %{withdrawals: withdrawals |> maybe_preload_ens(), next_page_params: next_page_params}) end end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex index ceec1582e098..0e06f6e058ca 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex @@ -6,6 +6,7 @@ defmodule BlockScoutWeb.API.V2.MainPageController do alias Explorer.{Chain, Repo} import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] @transactions_options [ necessity_by_association: %{ @@ -32,7 +33,7 @@ defmodule BlockScoutWeb.API.V2.MainPageController do conn |> put_status(200) |> put_view(BlockView) - |> render(:blocks, %{blocks: blocks}) + |> render(:blocks, %{blocks: blocks |> maybe_preload_ens()}) end def transactions(conn, _params) do @@ -41,7 +42,7 @@ defmodule BlockScoutWeb.API.V2.MainPageController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:transactions, %{transactions: recent_transactions}) + |> render(:transactions, %{transactions: recent_transactions |> maybe_preload_ens()}) end def watchlist_transactions(conn, _params) do @@ -51,7 +52,10 @@ defmodule BlockScoutWeb.API.V2.MainPageController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:transactions_watchlist, %{transactions: transactions, watchlist_names: watchlist_names}) + |> render(:transactions_watchlist, %{ + transactions: transactions |> maybe_preload_ens(), + watchlist_names: watchlist_names + }) end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex index abe0aca31486..f94a752f369a 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex @@ -2,6 +2,7 @@ defmodule BlockScoutWeb.API.V2.SearchController do use Phoenix.Controller import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1, from_param: 1] + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens_info_to_search_result: 1] alias Explorer.Chain.Search alias Explorer.PagingOptions @@ -22,7 +23,10 @@ defmodule BlockScoutWeb.API.V2.SearchController do conn |> put_status(200) - |> render(:search_results, %{search_results: search_results, next_page_params: next_page_params}) + |> render(:search_results, %{ + search_results: search_results |> maybe_preload_ens_info_to_search_result(), + next_page_params: next_page_params + }) end def check_redirect(conn, %{"q" => query}) do @@ -41,6 +45,6 @@ defmodule BlockScoutWeb.API.V2.SearchController do conn |> put_status(200) - |> render(:search_results, %{search_results: search_results}) + |> render(:search_results, %{search_results: search_results |> maybe_preload_ens_info_to_search_result()}) end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex index e98f648cb984..c9640e73722a 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex @@ -20,6 +20,8 @@ defmodule BlockScoutWeb.API.V2.TokenController do import BlockScoutWeb.PagingHelper, only: [delete_parameters_from_next_page_params: 1, token_transfers_types_options: 1, tokens_sorting: 1] + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] + action_fallback(BlockScoutWeb.API.V2.FallbackController) @api_true [api?: true] @@ -67,7 +69,10 @@ defmodule BlockScoutWeb.API.V2.TokenController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params}) + |> render(:token_transfers, %{ + token_transfers: token_transfers |> maybe_preload_ens(), + next_page_params: next_page_params + }) end end @@ -84,7 +89,11 @@ defmodule BlockScoutWeb.API.V2.TokenController do conn |> put_status(200) - |> render(:token_balances, %{token_balances: token_balances, next_page_params: next_page_params, token: token}) + |> render(:token_balances, %{ + token_balances: token_balances |> maybe_preload_ens(), + next_page_params: next_page_params, + token: token + }) end end @@ -190,7 +199,10 @@ defmodule BlockScoutWeb.API.V2.TokenController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params}) + |> render(:token_transfers, %{ + token_transfers: token_transfers |> maybe_preload_ens(), + next_page_params: next_page_params + }) end end @@ -217,7 +229,11 @@ defmodule BlockScoutWeb.API.V2.TokenController do conn |> put_status(200) - |> render(:token_balances, %{token_balances: token_holders, next_page_params: next_page_params, token: token}) + |> render(:token_balances, %{ + token_balances: token_holders |> maybe_preload_ens(), + next_page_params: next_page_params, + token: token + }) end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex index b9729bad84fe..a9f1eb50490b 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -22,6 +22,8 @@ defmodule BlockScoutWeb.API.V2.TransactionController do type_filter_options: 1 ] + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] + alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation, as: TransactionInterpretationService alias BlockScoutWeb.Models.TransactionStateHelper @@ -132,7 +134,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) - |> render(:transactions, %{transactions: transactions, next_page_params: next_page_params}) + |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params}) end @doc """ @@ -149,7 +151,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) - |> render(:transactions, %{transactions: transactions, items: true}) + |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), items: true}) end def execution_node(conn, %{"execution_node_hash_param" => execution_node_hash_string} = params) do @@ -169,7 +171,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) - |> render(:transactions, %{transactions: transactions, next_page_params: next_page_params}) + |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params}) end end @@ -230,7 +232,10 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) - |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params}) + |> render(:token_transfers, %{ + token_transfers: token_transfers |> maybe_preload_ens(), + next_page_params: next_page_params + }) end end @@ -256,7 +261,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) |> render(:internal_transactions, %{ - internal_transactions: internal_transactions, + internal_transactions: internal_transactions |> maybe_preload_ens(), next_page_params: next_page_params }) end @@ -291,7 +296,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do |> put_status(200) |> render(:logs, %{ tx_hash: transaction_hash, - logs: logs, + logs: logs |> maybe_preload_ens(), next_page_params: next_page_params }) end @@ -345,7 +350,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) |> render(:transactions_watchlist, %{ - transactions: transactions, + transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params, watchlist_names: watchlist_names }) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/withdrawal_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/withdrawal_controller.ex index fc26823e5211..4282d16d4fbb 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/withdrawal_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/withdrawal_controller.ex @@ -5,6 +5,7 @@ defmodule BlockScoutWeb.API.V2.WithdrawalController do only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] import BlockScoutWeb.PagingHelper, only: [delete_parameters_from_next_page_params: 1] + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] alias Explorer.Chain @@ -20,7 +21,7 @@ defmodule BlockScoutWeb.API.V2.WithdrawalController do conn |> put_status(200) - |> render(:withdrawals, %{withdrawals: withdrawals, next_page_params: next_page_params}) + |> render(:withdrawals, %{withdrawals: withdrawals |> maybe_preload_ens(), next_page_params: next_page_params}) end def withdrawals_counters(conn, _params) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex index 9c490d9131fc..96a69840ee24 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex @@ -54,10 +54,17 @@ defmodule BlockScoutWeb.API.V2.Helper do "is_contract" => Address.is_smart_contract(address), "name" => address_name(address), "implementation_name" => implementation_name(address), - "is_verified" => is_verified(address) + "is_verified" => is_verified(address), + "ens_domain_name" => address.ens_domain_name } end + defp address_with_info(%{ens_domain_name: name}, address_hash) do + nil + |> address_with_info(address_hash) + |> Map.put("ens_domain_name", name) + end + defp address_with_info(%NotLoaded{}, address_hash) do address_with_info(nil, address_hash) end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex index 0376e04f423e..b56c67352056 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex @@ -47,7 +47,8 @@ defmodule BlockScoutWeb.API.V2.SearchView do "name" => search_result.name, "address" => search_result.address_hash, "url" => address_path(Endpoint, :show, search_result.address_hash), - "is_smart_contract_verified" => search_result.verified + "is_smart_contract_verified" => search_result.verified, + "ens_info" => search_result[:ens_info] } end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs index f32b5727e727..cb8359f003f5 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs @@ -156,7 +156,8 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do "name" => nil, "private_tags" => [], "public_tags" => [], - "watchlist_names" => [] + "watchlist_names" => [], + "ens_domain_name" => nil } }} end) @@ -207,7 +208,8 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do "name" => nil, "private_tags" => [], "public_tags" => [], - "watchlist_names" => [] + "watchlist_names" => [], + "ens_domain_name" => nil } }} end) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index 690c9e1bfbc2..6ed830528b12 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -78,7 +78,8 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do "has_tokens" => false, "has_token_transfers" => false, "watchlist_address_id" => nil, - "has_beacon_chain_withdrawals" => false + "has_beacon_chain_withdrawals" => false, + "ens_domain_name" => nil } request = get(conn, "/api/v2/addresses/#{Address.checksum(address.hash)}") diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index 96dc8c782c27..352d1fcc05e6 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -47,7 +47,8 @@ defmodule Explorer.Chain.Address do contract has been verified * `names` - names known for the address * `inserted_at` - when this address was inserted - * `updated_at` when this address was last updated + * `updated_at` - when this address was last updated + * `ens_domain_name` - virtual field for ENS domain name passing `fetched_coin_balance` and `fetched_coin_balance_block_number` may be updated when a new coin_balance row is fetched. They may also be updated when the balance is fetched via the on demand fetcher. @@ -64,7 +65,8 @@ defmodule Explorer.Chain.Address do nonce: non_neg_integer() | nil, transactions_count: non_neg_integer() | nil, token_transfers_count: non_neg_integer() | nil, - gas_used: non_neg_integer() | nil + gas_used: non_neg_integer() | nil, + ens_domain_name: String.t() } @derive {Poison.Encoder, @@ -104,6 +106,7 @@ defmodule Explorer.Chain.Address do field(:transactions_count, :integer) field(:token_transfers_count, :integer) field(:gas_used, :integer) + field(:ens_domain_name, :string, virtual: true) has_one(:smart_contract, SmartContract) has_one(:token, Token, foreign_key: :contract_address_hash) diff --git a/apps/explorer/lib/explorer/chain/search.ex b/apps/explorer/lib/explorer/chain/search.ex index 64e8c1640645..d74f697a9b34 100644 --- a/apps/explorer/lib/explorer/chain/search.ex +++ b/apps/explorer/lib/explorer/chain/search.ex @@ -14,7 +14,7 @@ defmodule Explorer.Chain.Search do ] import Explorer.Chain, only: [select_repo: 1] - + import Explorer.MicroserviceInterfaces.BENS, only: [ens_domain_name_lookup: 1] alias Explorer.{Chain, PagingOptions} alias Explorer.Tags.{AddressTag, AddressToTag} @@ -33,74 +33,87 @@ defmodule Explorer.Chain.Search do def joint_search(paging_options, offset, raw_string, options \\ []) do string = String.trim(raw_string) - case prepare_search_term(string) do - {:some, term} -> - tokens_query = search_token_query(string, term) - contracts_query = search_contract_query(term) - labels_query = search_label_query(term) - tx_query = search_tx_query(string) - address_query = search_address_query(string) - block_query = search_block_query(string) - - basic_query = - from( - tokens in subquery(tokens_query), - union: ^contracts_query, - union: ^labels_query - ) - - query = - cond do - address_query -> - basic_query - |> union(^address_query) + ens_task = maybe_run_ens_task(paging_options, raw_string, options) + + result = + case prepare_search_term(string) do + {:some, term} -> + tokens_query = search_token_query(string, term) + contracts_query = search_contract_query(term) + labels_query = search_label_query(term) + tx_query = search_tx_query(string) + address_query = search_address_query(string) + block_query = search_block_query(string) + + basic_query = + from( + tokens in subquery(tokens_query), + union: ^contracts_query, + union: ^labels_query + ) - tx_query -> - basic_query - |> union(^tx_query) - |> union(^block_query) + query = + cond do + address_query -> + basic_query + |> union(^address_query) + + tx_query -> + basic_query + |> union(^tx_query) + |> union(^block_query) + + block_query -> + basic_query + |> union(^block_query) + + true -> + basic_query + end + + ordered_query = + from(items in subquery(query), + order_by: [ + desc: items.priority, + desc_nulls_last: items.circulating_market_cap, + desc_nulls_last: items.exchange_rate, + desc_nulls_last: items.is_verified_via_admin_panel, + desc_nulls_last: items.holder_count, + asc: items.name, + desc: items.inserted_at + ], + limit: ^paging_options.page_size, + offset: ^offset + ) - block_query -> - basic_query - |> union(^block_query) + paginated_ordered_query = + ordered_query + |> page_search_results(paging_options) - true -> - basic_query - end + search_results = select_repo(options).all(paginated_ordered_query) - ordered_query = - from(items in subquery(query), - order_by: [ - desc: items.priority, - desc_nulls_last: items.circulating_market_cap, - desc_nulls_last: items.exchange_rate, - desc_nulls_last: items.is_verified_via_admin_panel, - desc_nulls_last: items.holder_count, - asc: items.name, - desc: items.inserted_at - ], - limit: ^paging_options.page_size, - offset: ^offset - ) + search_results + |> Enum.map(fn result -> + result + |> compose_result_checksummed_address_hash() + |> format_timestamp() + end) - paginated_ordered_query = - ordered_query - |> page_search_results(paging_options) + _ -> + [] + end - search_results = select_repo(options).all(paginated_ordered_query) + ens_result = (ens_task && await_ens_task(ens_task)) || [] - search_results - |> Enum.map(fn result -> - result - |> compose_result_checksummed_address_hash() - |> format_timestamp() - end) + result ++ ens_result + end - _ -> - [] - end + defp maybe_run_ens_task(%PagingOptions{key: nil}, query_string, options) do + Task.async(fn -> search_ens_name(query_string, options) end) end + defp maybe_run_ens_task(_, _query_string, _options), do: nil + @doc """ Search function. Differences from joint_search/4: 1. Returns all the found categories (amount of results up to `paging_options.page_size`). @@ -111,6 +124,7 @@ defmodule Explorer.Chain.Search do @spec balanced_unpaginated_search(PagingOptions.t(), binary(), [Chain.api?()] | []) :: list def balanced_unpaginated_search(paging_options, raw_search_query, options \\ []) do search_query = String.trim(raw_search_query) + ens_task = Task.async(fn -> search_ens_name(raw_search_query, options) end) case prepare_search_term(search_query) do {:some, term} -> @@ -167,8 +181,10 @@ defmodule Explorer.Chain.Search do [] end + ens_result = await_ens_task(ens_task) + non_empty_lists = - [tokens_result, contracts_result, labels_result, tx_result, address_result, blocks_result] + [tokens_result, contracts_result, labels_result, tx_result, address_result, blocks_result, ens_result] |> Enum.filter(fn list -> Enum.count(list) > 0 end) |> Enum.sort_by(fn list -> Enum.count(list) end, :asc) @@ -190,6 +206,16 @@ defmodule Explorer.Chain.Search do end end + defp await_ens_task(ens_task) do + case Task.yield(ens_task, 5000) || Task.shutdown(ens_task) do + {:ok, result} -> + result + + _ -> + [] + end + end + def prepare_search_term(string) do case Regex.scan(~r/[a-zA-Z0-9]+/, string) do [_ | _] = words -> @@ -525,4 +551,49 @@ defmodule Explorer.Chain.Search do result end end + + defp search_ens_name(search_query, options) do + trimmed_query = String.trim(search_query) + + with true <- Regex.match?(~r/\w+\.\w+/, trimmed_query), + result when is_map(result) <- ens_domain_name_lookup(search_query) do + [ + result[:address_hash] + |> search_address_query() + |> select_repo(options).all() + |> merge_address_search_result_with_ens_info(result) + ] + else + _ -> + [] + end + end + + defp merge_address_search_result_with_ens_info([], ens_info) do + %{ + address_hash: ens_info[:address_hash], + block_hash: nil, + tx_hash: nil, + type: "address", + name: nil, + symbol: nil, + holder_count: nil, + inserted_at: nil, + block_number: 0, + icon_url: nil, + token_type: nil, + timestamp: nil, + verified: false, + exchange_rate: nil, + total_supply: nil, + circulating_market_cap: nil, + priority: 0, + is_verified_via_admin_panel: nil, + ens_info: ens_info + } + end + + defp merge_address_search_result_with_ens_info([address], ens_info) do + Map.put(address |> compose_result_checksummed_address_hash(), :ens_info, ens_info) + end end diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex new file mode 100644 index 000000000000..03587ce310b9 --- /dev/null +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -0,0 +1,340 @@ +defmodule Explorer.MicroserviceInterfaces.BENS do + @moduledoc """ + Interface to interact with Blockscout ENS microservice + """ + + alias Ecto.Association.NotLoaded + alias Explorer.Chain + + alias Explorer.Chain.{ + Address, + Address.CurrentTokenBalance, + Block, + InternalTransaction, + Log, + TokenTransfer, + Transaction, + Withdrawal + } + + alias Explorer.Utility.Microservice + alias HTTPoison.Response + require Logger + + @post_timeout :timer.seconds(5) + @request_error_msg "Error while sending request to BENS microservice" + + @typep supported_types :: + Address.t() + | Block.t() + | CurrentTokenBalance.t() + | InternalTransaction.t() + | Log.t() + | TokenTransfer.t() + | Transaction.t() + | Withdrawal.t() + + @doc """ + Batch request for ENS names via {{baseUrl}}/api/v1/:chainId/addresses:batch-resolve-names + """ + @spec ens_names_batch_request([String.t()]) :: {:error, :disabled | String.t() | Jason.DecodeError.t()} | {:ok, any} + def ens_names_batch_request(addresses) do + with :ok <- Microservice.check_enabled(__MODULE__) do + body = %{ + addresses: Enum.map(addresses, &to_string/1) + } + + http_post_request(batch_resolve_name_url(), body) + end + end + + @doc """ + Request for ENS name via {{baseUrl}}/api/v1/:chainId/addresses:lookup + """ + @spec address_lookup(binary()) :: {:error, :disabled | String.t() | Jason.DecodeError.t()} | {:ok, any} + def address_lookup(address) do + with :ok <- Microservice.check_enabled(__MODULE__) do + body = %{ + "address" => to_string(address), + "resolvedTo" => true, + "ownedBy" => false, + "onlyActive" => true, + "order" => "DESC" + } + + http_post_request(address_lookup_url(), body) + end + end + + @doc """ + Lookup for ENS domain name via {{baseUrl}}/api/v1/:chainId/domains:lookup + """ + @spec ens_domain_lookup(binary()) :: {:error, :disabled | String.t() | Jason.DecodeError.t()} | {:ok, any} + def ens_domain_lookup(domain) do + with :ok <- Microservice.check_enabled(__MODULE__) do + body = %{ + "name" => domain, + "onlyActive" => true, + "sort" => "registration_date", + "order" => "DESC" + } + + http_post_request(domain_lookup_url(), body) + end + end + + defp http_post_request(url, body) do + headers = [{"Content-Type", "application/json"}] + + case HTTPoison.post(url, Jason.encode!(body), headers, recv_timeout: @post_timeout) do + {:ok, %Response{body: body, status_code: 200}} -> + Jason.decode(body) + + {_, error} -> + old_truncate = Application.get_env(:logger, :truncate) + Logger.configure(truncate: :infinity) + + Logger.error(fn -> + [ + "Error while sending request to BENS microservice url: #{url}, body: #{inspect(body, limit: :infinity, printable_limit: :infinity)}: ", + inspect(error, limit: :infinity, printable_limit: :infinity) + ] + end) + + Logger.configure(truncate: old_truncate) + {:error, @request_error_msg} + end + end + + @spec enabled?() :: boolean + def enabled?, do: Application.get_env(:explorer, __MODULE__)[:enabled] + + defp batch_resolve_name_url do + "#{addresses_url()}:batch-resolve-names" + end + + defp address_lookup_url do + "#{addresses_url()}:lookup" + end + + defp domain_lookup_url do + "#{domains_url()}:lookup" + end + + defp addresses_url do + "#{base_url()}/addresses" + end + + defp domains_url do + "#{base_url()}/domains" + end + + defp base_url do + chain_id = Application.get_env(:block_scout_web, :chain_id) + "#{Microservice.base_url(__MODULE__)}/api/v1/#{chain_id}" + end + + @doc """ + Preload ENS info to list of entities if enabled?() + """ + @spec maybe_preload_ens([supported_types]) :: [supported_types] + def maybe_preload_ens(entity_list, function \\ &preload_ens_to_list/1) do + if enabled?() do + function.(entity_list) + else + entity_list + end + end + + def maybe_preload_ens_info_to_search_result(list) do + maybe_preload_ens(list, &preload_ens_info_to_search_result/1) + end + + @doc """ + Preload ENS names to list of entities + """ + @spec preload_ens_to_list([supported_types]) :: [supported_types] + def preload_ens_to_list(items) do + address_hash_strings = + Enum.reduce(items, [], fn item, acc -> + acc ++ item_to_address_hash_strings(item) + end) + + case ens_names_batch_request(address_hash_strings) do + {:ok, result} -> + put_ens_names(result["names"], items) + + _ -> + items + end + end + + @doc """ + Preload ENS info to search result, using address_lookup/1 + """ + @spec preload_ens_info_to_search_result(list) :: list + def preload_ens_info_to_search_result(list) do + Enum.map(list, fn + %{type: "address"} = search_result -> + ens_info = search_result[:address_hash] |> address_lookup() |> parse_lookup_response() + Map.put(search_result, :ens_info, ens_info) + + search_result -> + search_result + end) + end + + @spec ens_domain_name_lookup(binary()) :: nil | %{address_hash: binary(), expiry_date: any(), name: any()} + def ens_domain_name_lookup(domain) do + domain |> ens_domain_lookup() |> parse_lookup_response() + end + + defp parse_lookup_response( + {:ok, + %{ + "items" => + [ + %{"name" => name, "expiryDate" => expiry_date, "resolvedAddress" => %{"hash" => address_hash_string}} + | _other + ] = items + }} + ) do + {:ok, hash} = Chain.string_to_address_hash(address_hash_string) + + %{ + name: name, + expiry_date: expiry_date, + names_count: Enum.count(items), + address_hash: Address.checksum(hash) + } + end + + defp parse_lookup_response(_), do: nil + + defp item_to_address_hash_strings(%Transaction{ + to_address_hash: nil, + created_contract_address_hash: created_contract_address_hash, + from_address_hash: from_address_hash + }) do + [to_string(created_contract_address_hash), to_string(from_address_hash)] + end + + defp item_to_address_hash_strings(%Transaction{ + to_address_hash: to_address_hash, + created_contract_address_hash: nil, + from_address_hash: from_address_hash + }) do + [to_string(to_address_hash), to_string(from_address_hash)] + end + + defp item_to_address_hash_strings(%TokenTransfer{ + to_address_hash: to_address_hash, + from_address_hash: from_address_hash + }) do + [to_string(to_address_hash), to_string(from_address_hash)] + end + + defp item_to_address_hash_strings(%InternalTransaction{ + to_address_hash: to_address_hash, + from_address_hash: from_address_hash + }) do + [to_string(to_address_hash), to_string(from_address_hash)] + end + + defp item_to_address_hash_strings(%Log{address_hash: address_hash}) do + [to_string(address_hash)] + end + + defp item_to_address_hash_strings(%Withdrawal{address_hash: address_hash}) do + [to_string(address_hash)] + end + + defp item_to_address_hash_strings(%Block{miner_hash: miner_hash}) do + [to_string(miner_hash)] + end + + defp item_to_address_hash_strings(%CurrentTokenBalance{address_hash: address_hash}) do + [to_string(address_hash)] + end + + defp put_ens_names(names, items) do + Enum.map(items, &put_ens_name_to_item(&1, names)) + end + + defp put_ens_name_to_item( + %Transaction{ + to_address_hash: to_address_hash, + created_contract_address_hash: created_contract_address_hash, + from_address_hash: from_address_hash + } = tx, + names + ) do + %Transaction{ + tx + | to_address: alter_address(tx.to_address, to_address_hash, names), + created_contract_address: alter_address(tx.created_contract_address, created_contract_address_hash, names), + from_address: alter_address(tx.from_address, from_address_hash, names) + } + end + + defp put_ens_name_to_item( + %TokenTransfer{ + to_address_hash: to_address_hash, + from_address_hash: from_address_hash + } = tt, + names + ) do + %TokenTransfer{ + tt + | to_address: alter_address(tt.to_address, to_address_hash, names), + from_address: alter_address(tt.from_address, from_address_hash, names) + } + end + + defp put_ens_name_to_item( + %InternalTransaction{ + to_address_hash: to_address_hash, + created_contract_address_hash: created_contract_address_hash, + from_address_hash: from_address_hash + } = tx, + names + ) do + %InternalTransaction{ + tx + | to_address: alter_address(tx.to_address, to_address_hash, names), + created_contract_address: alter_address(tx.created_contract_address, created_contract_address_hash, names), + from_address: alter_address(tx.from_address, from_address_hash, names) + } + end + + defp put_ens_name_to_item(%Log{address_hash: address_hash} = log, names) do + %Log{log | address: alter_address(log.address, address_hash, names)} + end + + defp put_ens_name_to_item(%Withdrawal{address_hash: address_hash} = withdrawal, names) do + %Withdrawal{withdrawal | address: alter_address(withdrawal.address, address_hash, names)} + end + + defp put_ens_name_to_item(%Block{miner_hash: miner_hash} = block, names) do + %Block{block | miner: alter_address(block.miner, miner_hash, names)} + end + + defp put_ens_name_to_item(%CurrentTokenBalance{address_hash: address_hash} = current_token_balance, names) do + %CurrentTokenBalance{ + current_token_balance + | address: alter_address(current_token_balance.address, address_hash, names) + } + end + + defp alter_address(_, nil, _names) do + nil + end + + defp alter_address(%NotLoaded{}, address_hash, names) do + %{ens_domain_name: names[to_string(address_hash)]} + end + + defp alter_address(%Address{} = address, address_hash, names) do + %Address{address | ens_domain_name: names[to_string(address_hash)]} + end +end diff --git a/apps/explorer/lib/explorer/utility/microservice.ex b/apps/explorer/lib/explorer/utility/microservice.ex index 6200f7f7acdf..3a8620255ec1 100644 --- a/apps/explorer/lib/explorer/utility/microservice.ex +++ b/apps/explorer/lib/explorer/utility/microservice.ex @@ -12,4 +12,13 @@ defmodule Explorer.Utility.Microservice do url end end + + @spec check_enabled(atom) :: :ok | {:error, :disabled} + def check_enabled(module) do + if Application.get_env(:explorer, module)[:enabled] do + :ok + else + {:error, :disabled} + end + end end diff --git a/config/runtime.exs b/config/runtime.exs index 4eb51cf2e4c8..e9d915a65a7e 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -451,6 +451,10 @@ config :explorer, Explorer.Chain.Transaction, config :explorer, Explorer.Chain.Cache.AddressesTabsCounters, ttl: ConfigHelper.parse_time_env_var("ADDRESSES_TABS_COUNTERS_TTL", "10m") +config :explorer, Explorer.MicroserviceInterfaces.BENS, + service_url: System.get_env("MICROSERVICE_BENS_URL"), + enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_BENS_ENABLED") + ############### ### Indexer ### ############### From d7be418ec39ed8021db3a1b7c08c1c878265301f Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Thu, 14 Dec 2023 20:47:50 +0300 Subject: [PATCH 164/607] Fix after review --- apps/explorer/lib/explorer/chain/address.ex | 5 +++++ apps/explorer/lib/explorer/microservice_interfaces/bens.ex | 6 +++++- apps/explorer/lib/explorer/utility/microservice.ex | 3 +++ docker-compose/envs/common-blockscout.env | 2 ++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index 352d1fcc05e6..fdb664504d5a 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -161,6 +161,11 @@ defmodule Explorer.Chain.Address do checksum(hash, iodata?) end + def checksum(hash_string, iodata?) when is_binary(hash_string) do + {:ok, hash} = Chain.string_to_address_hash(hash_string) + checksum(hash, iodata?) + end + def checksum(hash, iodata?) do checksum_formatted = case Application.get_env(:explorer, :checksum_function) || :eth do diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex index 03587ce310b9..e7b3ee6310c7 100644 --- a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -175,6 +175,9 @@ defmodule Explorer.MicroserviceInterfaces.BENS do @spec preload_ens_info_to_search_result(list) :: list def preload_ens_info_to_search_result(list) do Enum.map(list, fn + %{type: "address", ens_info: ens_info} = search_result when not is_nil(ens_info) -> + search_result + %{type: "address"} = search_result -> ens_info = search_result[:address_hash] |> address_lookup() |> parse_lookup_response() Map.put(search_result, :ens_info, ens_info) @@ -184,7 +187,8 @@ defmodule Explorer.MicroserviceInterfaces.BENS do end) end - @spec ens_domain_name_lookup(binary()) :: nil | %{address_hash: binary(), expiry_date: any(), name: any()} + @spec ens_domain_name_lookup(binary()) :: + nil | %{address_hash: binary(), expiry_date: any(), name: any(), names_count: integer()} def ens_domain_name_lookup(domain) do domain |> ens_domain_lookup() |> parse_lookup_response() end diff --git a/apps/explorer/lib/explorer/utility/microservice.ex b/apps/explorer/lib/explorer/utility/microservice.ex index 3a8620255ec1..fe1af3df46b4 100644 --- a/apps/explorer/lib/explorer/utility/microservice.ex +++ b/apps/explorer/lib/explorer/utility/microservice.ex @@ -13,6 +13,9 @@ defmodule Explorer.Utility.Microservice do end end + @doc """ + Returns :ok if Application.get_env(:explorer, module)[:enabled] is true (module is enabled) + """ @spec check_enabled(atom) :: :ok | {:error, :disabled} def check_enabled(module) do if Application.get_env(:explorer, module)[:enabled] do diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index f8a1507ac2d1..9e7e050cb573 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -258,3 +258,5 @@ API_V2_ENABLED=true # ADDRESSES_TABS_COUNTERS_TTL=10m # ACCOUNT_PRIVATE_TAGS_LIMIT=2000 # ACCOUNT_WATCHLIST_ADDRESSES_LIMIT=15 +# MICROSERVICE_BENS_URL= +# MICROSERVICE_BENS_ENABLED= \ No newline at end of file From 7370587cb2400b3a26ebc3078496a15acc6f6b39 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Fri, 15 Dec 2023 11:58:09 +0300 Subject: [PATCH 165/607] Add ens info to transaction details --- .../controllers/api/v2/search_controller.ex | 6 ++-- .../api/v2/transaction_controller.ex | 4 +-- .../explorer/microservice_interfaces/bens.ex | 30 +++++++++++++------ 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex index f94a752f369a..0a3c0f17aed4 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex @@ -2,7 +2,7 @@ defmodule BlockScoutWeb.API.V2.SearchController do use Phoenix.Controller import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1, from_param: 1] - import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens_info_to_search_result: 1] + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens_info_to_search_results: 1] alias Explorer.Chain.Search alias Explorer.PagingOptions @@ -24,7 +24,7 @@ defmodule BlockScoutWeb.API.V2.SearchController do conn |> put_status(200) |> render(:search_results, %{ - search_results: search_results |> maybe_preload_ens_info_to_search_result(), + search_results: search_results |> maybe_preload_ens_info_to_search_results(), next_page_params: next_page_params }) end @@ -45,6 +45,6 @@ defmodule BlockScoutWeb.API.V2.SearchController do conn |> put_status(200) - |> render(:search_results, %{search_results: search_results |> maybe_preload_ens_info_to_search_result()}) + |> render(:search_results, %{search_results: search_results |> maybe_preload_ens_info_to_search_results()}) end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex index a9f1eb50490b..993d2ac68a36 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -22,7 +22,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do type_filter_options: 1 ] - import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1, maybe_preload_ens_to_transaction: 1] alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation, as: TransactionInterpretationService @@ -106,7 +106,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do Chain.preload_token_transfers(transaction, @token_transfers_in_tx_necessity_by_association, @api_true, false) do conn |> put_status(200) - |> render(:transaction, %{transaction: preloaded}) + |> render(:transaction, %{transaction: preloaded |> maybe_preload_ens_to_transaction()}) end end diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex index e7b3ee6310c7..162c9e313efb 100644 --- a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -59,7 +59,7 @@ defmodule Explorer.MicroserviceInterfaces.BENS do "resolvedTo" => true, "ownedBy" => false, "onlyActive" => true, - "order" => "DESC" + "order" => "ASC" } http_post_request(address_lookup_url(), body) @@ -137,17 +137,29 @@ defmodule Explorer.MicroserviceInterfaces.BENS do @doc """ Preload ENS info to list of entities if enabled?() """ - @spec maybe_preload_ens([supported_types]) :: [supported_types] - def maybe_preload_ens(entity_list, function \\ &preload_ens_to_list/1) do + @spec maybe_preload_ens([supported_types] | supported_types) :: [supported_types] | supported_types + def maybe_preload_ens(argument, function \\ &preload_ens_to_list/1) do if enabled?() do - function.(entity_list) + function.(argument) else - entity_list + argument end end - def maybe_preload_ens_info_to_search_result(list) do - maybe_preload_ens(list, &preload_ens_info_to_search_result/1) + @spec maybe_preload_ens_info_to_search_results(list()) :: list() + def maybe_preload_ens_info_to_search_results(list) do + maybe_preload_ens(list, &preload_ens_info_to_search_results/1) + end + + @spec maybe_preload_ens_to_transaction(Transaction.t()) :: Transaction.t() + def maybe_preload_ens_to_transaction(transaction) do + maybe_preload_ens(transaction, &preload_ens_to_transaction/1) + end + + @spec preload_ens_to_transaction(Transaction.t()) :: Transaction.t() + def preload_ens_to_transaction(transaction) do + [transaction_with_ens] = preload_ens_to_list([transaction]) + transaction_with_ens end @doc """ @@ -172,8 +184,8 @@ defmodule Explorer.MicroserviceInterfaces.BENS do @doc """ Preload ENS info to search result, using address_lookup/1 """ - @spec preload_ens_info_to_search_result(list) :: list - def preload_ens_info_to_search_result(list) do + @spec preload_ens_info_to_search_results(list) :: list + def preload_ens_info_to_search_results(list) do Enum.map(list, fn %{type: "address", ens_info: ens_info} = search_result when not is_nil(ens_info) -> search_result From 905d56207fc3458637c9f90c943946ed08608e47 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Fri, 15 Dec 2023 13:16:20 +0300 Subject: [PATCH 166/607] Add ens info to addresses --- .../controllers/api/v2/address_controller.ex | 6 ++--- .../explorer/microservice_interfaces/bens.ex | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex index e5e99277fa6d..3a78dcdb676c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -20,7 +20,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do nft_token_types_options: 1 ] - import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1, maybe_preload_ens_to_address: 1] alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.API.V2.{BlockView, TransactionView, WithdrawalView} @@ -85,7 +85,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) - |> render(:address, %{address: fully_preloaded_address}) + |> render(:address, %{address: fully_preloaded_address |> maybe_preload_ens_to_address()}) end end @@ -415,7 +415,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> render(:addresses, %{ - addresses: addresses, + addresses: addresses |> maybe_preload_ens(), next_page_params: next_page_params, exchange_rate: exchange_rate, total_supply: total_supply diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex index 162c9e313efb..02447a050630 100644 --- a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -162,6 +162,17 @@ defmodule Explorer.MicroserviceInterfaces.BENS do transaction_with_ens end + @spec maybe_preload_ens_to_address(Address.t()) :: Address.t() + def maybe_preload_ens_to_address(address) do + maybe_preload_ens(address, &preload_ens_to_address/1) + end + + @spec preload_ens_to_address(Address.t()) :: Address.t() + def preload_ens_to_address(address) do + [address_with_ens] = preload_ens_to_list([address]) + address_with_ens + end + @doc """ Preload ENS names to list of entities """ @@ -273,6 +284,14 @@ defmodule Explorer.MicroserviceInterfaces.BENS do [to_string(address_hash)] end + defp item_to_address_hash_strings({%Address{} = address, _}) do + item_to_address_hash_strings(address) + end + + defp item_to_address_hash_strings(%Address{hash: hash}) do + [to_string(hash)] + end + defp put_ens_names(names, items) do Enum.map(items, &put_ens_name_to_item(&1, names)) end @@ -342,6 +361,14 @@ defmodule Explorer.MicroserviceInterfaces.BENS do } end + defp put_ens_name_to_item({%Address{} = address, count}, names) do + {put_ens_name_to_item(address, names), count} + end + + defp put_ens_name_to_item(%Address{} = address, names) do + alter_address(address, address.hash, names) + end + defp alter_address(_, nil, _names) do nil end From 377166b2d1e073f9a4a1a5bf28385a9e9ebd7acc Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Tue, 19 Dec 2023 22:24:38 +0300 Subject: [PATCH 167/607] Fix after review --- apps/explorer/lib/explorer/chain/address.ex | 2 +- .../explorer/lib/explorer/microservice_interfaces/bens.ex | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index fdb664504d5a..41bac5f23cf4 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -66,7 +66,7 @@ defmodule Explorer.Chain.Address do transactions_count: non_neg_integer() | nil, token_transfers_count: non_neg_integer() | nil, gas_used: non_neg_integer() | nil, - ens_domain_name: String.t() + ens_domain_name: String.t() | nil } @derive {Poison.Encoder, diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex index 02447a050630..186f599b127c 100644 --- a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -37,7 +37,7 @@ defmodule Explorer.MicroserviceInterfaces.BENS do @doc """ Batch request for ENS names via {{baseUrl}}/api/v1/:chainId/addresses:batch-resolve-names """ - @spec ens_names_batch_request([String.t()]) :: {:error, :disabled | String.t() | Jason.DecodeError.t()} | {:ok, any} + @spec ens_names_batch_request([binary()]) :: {:error, :disabled | binary() | Jason.DecodeError.t()} | {:ok, any} def ens_names_batch_request(addresses) do with :ok <- Microservice.check_enabled(__MODULE__) do body = %{ @@ -51,7 +51,7 @@ defmodule Explorer.MicroserviceInterfaces.BENS do @doc """ Request for ENS name via {{baseUrl}}/api/v1/:chainId/addresses:lookup """ - @spec address_lookup(binary()) :: {:error, :disabled | String.t() | Jason.DecodeError.t()} | {:ok, any} + @spec address_lookup(binary()) :: {:error, :disabled | binary() | Jason.DecodeError.t()} | {:ok, any} def address_lookup(address) do with :ok <- Microservice.check_enabled(__MODULE__) do body = %{ @@ -69,7 +69,7 @@ defmodule Explorer.MicroserviceInterfaces.BENS do @doc """ Lookup for ENS domain name via {{baseUrl}}/api/v1/:chainId/domains:lookup """ - @spec ens_domain_lookup(binary()) :: {:error, :disabled | String.t() | Jason.DecodeError.t()} | {:ok, any} + @spec ens_domain_lookup(binary()) :: {:error, :disabled | binary() | Jason.DecodeError.t()} | {:ok, any} def ens_domain_lookup(domain) do with :ok <- Microservice.check_enabled(__MODULE__) do body = %{ @@ -180,7 +180,7 @@ defmodule Explorer.MicroserviceInterfaces.BENS do def preload_ens_to_list(items) do address_hash_strings = Enum.reduce(items, [], fn item, acc -> - acc ++ item_to_address_hash_strings(item) + item_to_address_hash_strings(item) ++ acc end) case ens_names_batch_request(address_hash_strings) do From 72f14715b24c19d71148b62212e8c0bd2eb68985 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Tue, 19 Dec 2023 22:48:45 +0300 Subject: [PATCH 168/607] Reset GA cache --- .github/workflows/config.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 2d672f01197e..5efcd092a62e 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -72,7 +72,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps- @@ -130,7 +130,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -154,7 +154,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -183,7 +183,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -227,7 +227,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -253,7 +253,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -282,7 +282,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -330,7 +330,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -376,7 +376,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -438,7 +438,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -498,7 +498,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -569,7 +569,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -637,7 +637,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_29-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" From 931c2ee2b73aa4b904a2a46cebbd022c9608f1e0 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Thu, 21 Dec 2023 11:43:52 +0300 Subject: [PATCH 169/607] Fix tx input decoding in tx summary microservice request --- CHANGELOG.md | 1 + .../transaction_interpretation.ex | 2 +- .../views/api/v2/transaction_view.ex | 22 +++++++++++-------- .../lib/explorer/chain/transaction.ex | 10 +++++++++ 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 719f013806c6..e20c9e169c05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Fixes +- [#9039](https://github.com/blockscout/blockscout/pull/9039) - Fix tx input decoding in tx summary microservice request - [#9013](https://github.com/blockscout/blockscout/pull/9013) - Speed up `Indexer.Fetcher.TokenInstance.LegacySanitize` - [#8955](https://github.com/blockscout/blockscout/pull/8955) - Remove daily balances updating from BlockReward fetcher - [#8846](https://github.com/blockscout/blockscout/pull/8846) - Handle nil gas_price at address view diff --git a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex index 60d2a3a368a4..587c78cfada9 100644 --- a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex +++ b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex @@ -79,7 +79,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do skip_sig_provider? = true {decoded_input, _abi_acc, _methods_acc} = Transaction.decoded_input_data(transaction, skip_sig_provider?, @api_true) - decoded_input_data = TransactionView.decoded_input(decoded_input) + decoded_input_data = decoded_input |> TransactionView.format_decoded_input() |> TransactionView.decoded_input() %{ data: %{ diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index 84d6f65bd5a4..28bc4042c46b 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -208,12 +208,15 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end def decode_transactions(transactions, skip_sig_provider?) do - Enum.reduce(transactions, {[], %{}, %{}}, fn transaction, {results, abi_acc, methods_acc} -> - {result, abi_acc, methods_acc} = - Transaction.decoded_input_data(transaction, skip_sig_provider?, @api_true, abi_acc, methods_acc) + {results, abi_acc, methods_acc} = + Enum.reduce(transactions, {[], %{}, %{}}, fn transaction, {results, abi_acc, methods_acc} -> + {result, abi_acc, methods_acc} = + Transaction.decoded_input_data(transaction, skip_sig_provider?, @api_true, abi_acc, methods_acc) - {Enum.reverse([format_decoded_input(result) | Enum.reverse(results)]), abi_acc, methods_acc} - end) + {[format_decoded_input(result) | results], abi_acc, methods_acc} + end) + + {Enum.reverse(results), abi_acc, methods_acc} end def prepare_token_transfer(token_transfer, _conn, decoded_input) do @@ -653,10 +656,11 @@ defmodule BlockScoutWeb.API.V2.TransactionView do defp format_status({:error, reason}), do: reason defp format_status(status), do: status - defp format_decoded_input({:error, _, []}), do: nil - defp format_decoded_input({:error, _, candidates}), do: Enum.at(candidates, 0) - defp format_decoded_input({:ok, _identifier, _text, _mapping} = decoded), do: decoded - defp format_decoded_input(_), do: nil + @spec format_decoded_input(any()) :: nil | map() | tuple() + def format_decoded_input({:error, _, []}), do: nil + def format_decoded_input({:error, _, candidates}), do: Enum.at(candidates, 0) + def format_decoded_input({:ok, _identifier, _text, _mapping} = decoded), do: decoded + def format_decoded_input(_), do: nil defp format_decoded_log_input({:error, :could_not_decode}), do: nil defp format_decoded_log_input({:ok, _method_id, _text, _mapping} = decoded), do: decoded diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 1f7e67790bdc..8971b738781d 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -594,6 +594,16 @@ defmodule Explorer.Chain.Transaction do end # Because there is no contract association, we know the contract was not verified + @spec decoded_input_data( + %{:__struct__ => Ecto.Association.NotLoaded | Explorer.Chain.Transaction, optional(any()) => any()}, + any(), + any(), + any(), + any() + ) :: + {{:error | :ok | binary(), any()} + | {:error, :contract_not_verified | :contract_verified, list()} + | {:ok, binary(), binary(), list()}, any(), any()} def decoded_input_data(tx, skip_sig_provider? \\ false, options, full_abi_acc \\ %{}, methods_acc \\ %{}) def decoded_input_data(%__MODULE__{to_address: nil}, _, _, full_abi_acc, methods_acc), From 61eeae6e2ccdbf9abe08cc7960abcb9b141f4adf Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Thu, 7 Dec 2023 17:34:33 +0600 Subject: [PATCH 170/607] TRACE_BLOCK_RANGES env var --- CHANGELOG.md | 1 + apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex | 32 +---- .../ethereum_jsonrpc/utility/ranges_helper.ex | 117 ++++++++++++++++++ apps/explorer/lib/explorer/chain.ex | 3 +- .../explorer/chain/import/runner/blocks.ex | 16 ++- .../import/runner/internal_transactions.ex | 33 +++-- .../runner/internal_transactions_test.exs | 23 ++++ .../lib/indexer/block/catchup/helper.ex | 39 ------ .../block/catchup/missing_ranges_collector.ex | 9 +- .../lib/indexer/fetcher/coin_balance.ex | 9 +- .../indexer/fetcher/internal_transaction.ex | 3 +- config/runtime.exs | 1 + docker-compose/envs/common-blockscout.env | 1 + 13 files changed, 190 insertions(+), 97 deletions(-) create mode 100644 apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex delete mode 100644 apps/indexer/lib/indexer/block/catchup/helper.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index 719f013806c6..4b3209369352 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - [#8972](https://github.com/blockscout/blockscout/pull/8972) - BENS integration +- [#8960](https://github.com/blockscout/blockscout/pull/8960) - TRACE_BLOCK_RANGES env var - [#8957](https://github.com/blockscout/blockscout/pull/8957) - Add Tx Interpreter Service integration ### Fixes diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex index abad4cdfb3cd..74d37fe187df 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex @@ -39,6 +39,7 @@ defmodule EthereumJSONRPC do Subscription, Transport, Utility.EndpointAvailabilityObserver, + Utility.RangesHelper, Variant } @@ -205,7 +206,8 @@ defmodule EthereumJSONRPC do filtered_params_in_range = filtered_params |> Enum.filter(fn - %{block_quantity: block_quantity} -> is_block_number_in_range?(block_quantity) + %{block_quantity: block_quantity} -> + block_quantity |> quantity_to_integer() |> RangesHelper.traceable_block_number?() end) id_to_params = id_to_params(filtered_params_in_range) @@ -244,7 +246,7 @@ defmodule EthereumJSONRPC do @spec fetch_beneficiaries([block_number], json_rpc_named_arguments) :: {:ok, FetchedBeneficiaries.t()} | {:error, reason :: term} | :ignore def fetch_beneficiaries(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do - filtered_block_numbers = are_block_numbers_in_range?(block_numbers) + filtered_block_numbers = RangesHelper.filter_traceable_block_numbers(block_numbers) Keyword.fetch!(json_rpc_named_arguments, :variant).fetch_beneficiaries( filtered_block_numbers, @@ -349,7 +351,7 @@ defmodule EthereumJSONRPC do Fetches internal transactions for entire blocks from variant API. """ def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do - filtered_block_numbers = are_block_numbers_in_range?(block_numbers) + filtered_block_numbers = RangesHelper.filter_traceable_block_numbers(block_numbers) Keyword.fetch!(json_rpc_named_arguments, :variant).fetch_block_internal_transactions( filtered_block_numbers, @@ -357,16 +359,6 @@ defmodule EthereumJSONRPC do ) end - def are_block_numbers_in_range?(block_numbers) do - min_block = Application.get_env(:indexer, :trace_first_block) - max_block = Application.get_env(:indexer, :trace_last_block) - - block_numbers - |> Enum.filter(fn block_number -> - block_number >= min_block && if max_block, do: block_number <= max_block, else: true - end) - end - @doc """ Retrieves traces from variant API. """ @@ -459,20 +451,6 @@ defmodule EthereumJSONRPC do end end - @spec is_block_number_in_range?(quantity) :: boolean() - defp is_block_number_in_range?(block_quantity) do - min_block = Application.get_env(:indexer, :trace_first_block) - max_block = Application.get_env(:indexer, :trace_last_block) - block_number = quantity_to_integer(block_quantity) - - if !block_number || - (block_number && block_number >= min_block && if(max_block, do: block_number <= max_block, else: true)) do - true - else - false - end - end - defp maybe_replace_url(url, _replace_url, EthereumJSONRPC.HTTP), do: url defp maybe_replace_url(url, replace_url, _), do: EndpointAvailabilityObserver.maybe_replace_url(url, replace_url) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex new file mode 100644 index 000000000000..75f00bccdee5 --- /dev/null +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex @@ -0,0 +1,117 @@ +# credo:disable-for-this-file +defmodule EthereumJSONRPC.Utility.RangesHelper do + @moduledoc """ + Helper for ranges manipulations. + """ + + @spec traceable_block_number?(integer() | nil) :: boolean() + def traceable_block_number?(block_number) do + case Application.get_env(:indexer, :trace_block_ranges) do + nil -> + min_block = Application.get_env(:indexer, :trace_first_block) + max_block = Application.get_env(:indexer, :trace_last_block) + + !block_number || (block_number >= min_block && if(max_block, do: block_number <= max_block, else: true)) + + block_ranges -> + parsed_ranges = parse_block_ranges(block_ranges) + + number_in_ranges?(block_number, parsed_ranges) + end + end + + @spec filter_traceable_block_numbers([integer()]) :: [integer()] + def filter_traceable_block_numbers(block_numbers) do + case Application.get_env(:indexer, :trace_block_ranges) do + nil -> + min_block = Application.get_env(:indexer, :trace_first_block) + max_block = Application.get_env(:indexer, :trace_last_block) + + Enum.filter(block_numbers, fn block_number -> + block_number >= min_block && if max_block, do: block_number <= max_block, else: true + end) + + block_ranges -> + parsed_ranges = parse_block_ranges(block_ranges) + + Enum.filter(block_numbers, &number_in_ranges?(&1, parsed_ranges)) + end + end + + @spec parse_block_ranges(binary()) :: [Range.t() | integer()] + def parse_block_ranges(block_ranges_string) do + block_ranges_string + |> String.split(",") + |> Enum.map(fn string_range -> + case String.split(string_range, "..") do + [from_string, "latest"] -> + parse_integer(from_string) + + [from_string, to_string] -> + get_from_to(from_string, to_string) + + _ -> + nil + end + end) + |> sanitize_ranges() + end + + defp number_in_ranges?(number, ranges) do + Enum.reduce_while(ranges, false, fn + _from.._to = range, _acc -> if number in range, do: {:halt, true}, else: {:cont, false} + num_to_latest, _acc -> if number >= num_to_latest, do: {:halt, true}, else: {:cont, false} + end) + end + + defp get_from_to(from_string, to_string) do + with {from, ""} <- Integer.parse(from_string), + {to, ""} <- Integer.parse(to_string) do + if from <= to, do: from..to, else: nil + else + _ -> nil + end + end + + @spec sanitize_ranges([Range.t() | integer()]) :: [Range.t() | integer()] + def sanitize_ranges(ranges) do + ranges + |> Enum.reject(&is_nil/1) + |> Enum.sort_by( + fn + from.._to -> from + el -> el + end, + :asc + ) + |> Enum.chunk_while( + nil, + fn + _from.._to = chunk, nil -> + {:cont, chunk} + + _ch_from..ch_to = chunk, acc_from..acc_to = acc -> + if Range.disjoint?(chunk, acc), + do: {:cont, acc, chunk}, + else: {:cont, acc_from..max(ch_to, acc_to)} + + num, nil -> + {:halt, num} + + num, acc_from.._ = acc -> + if Range.disjoint?(num..num, acc), do: {:cont, acc, num}, else: {:halt, acc_from} + + _, num -> + {:halt, num} + end, + fn remainder -> {:cont, remainder, nil} end + ) + end + + defp parse_integer(string) do + case Integer.parse(string) do + {number, ""} -> number + _ -> nil + end + end +end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index fa25f985b0df..663e2a232eb4 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -33,6 +33,7 @@ defmodule Explorer.Chain do alias Ecto.{Changeset, Multi} alias EthereumJSONRPC.Transaction, as: EthereumJSONRPCTransaction + alias EthereumJSONRPC.Utility.RangesHelper alias Explorer.Account.WatchlistAddress @@ -4761,7 +4762,7 @@ defmodule Explorer.Chain do if transaction_index == 0 do 0 else - filtered_block_numbers = EthereumJSONRPC.are_block_numbers_in_range?([block_number]) + filtered_block_numbers = RangesHelper.filter_traceable_block_numbers([block_number]) {:ok, traces} = fetch_block_internal_transactions(filtered_block_numbers, json_rpc_named_arguments) sorted_traces = diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex index defc998b866d..6ff8ee7d6432 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex @@ -9,6 +9,8 @@ defmodule Explorer.Chain.Import.Runner.Blocks do alias Ecto.{Changeset, Multi, Repo} + alias EthereumJSONRPC.Utility.RangesHelper + alias Explorer.Chain.{ Address, Block, @@ -62,7 +64,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do items_for_pending_ops = changes_list - |> filter_by_height_range(&is_block_in_range?(&1.number)) + |> filter_by_height_range(&RangesHelper.traceable_block_number?(&1.number)) |> Enum.filter(& &1.consensus) |> Enum.map(&{&1.number, &1.hash}) @@ -72,7 +74,8 @@ defmodule Explorer.Chain.Import.Runner.Blocks do run_func = fn repo -> {:ok, nonconsensus_items} = lose_consensus(repo, hashes, consensus_block_numbers, changes_list, insert_options) - {:ok, filter_by_height_range(nonconsensus_items, fn {number, _hash} -> is_block_in_range?(number) end)} + {:ok, + filter_by_height_range(nonconsensus_items, fn {number, _hash} -> RangesHelper.traceable_block_number?(number) end)} end multi @@ -214,12 +217,6 @@ defmodule Explorer.Chain.Import.Runner.Blocks do @impl Runner def timeout, do: @timeout - defp is_block_in_range?(number) do - minimal_block_height = Application.get_env(:indexer, :trace_first_block) - maximal_block_height = Application.get_env(:indexer, :trace_last_block) - number >= minimal_block_height && if(maximal_block_height, do: number <= maximal_block_height, else: true) - end - defp fork_transactions(%{ repo: repo, timeout: timeout, @@ -890,10 +887,11 @@ defmodule Explorer.Chain.Import.Runner.Blocks do end defp filter_by_height_range(blocks, filter_func) do + trace_block_ranges = Application.get_env(:indexer, :trace_block_ranges) minimal_block_height = Application.get_env(:indexer, :trace_first_block) maximal_block_height = Application.get_env(:indexer, :trace_last_block) - if minimal_block_height > 0 || maximal_block_height do + if trace_block_ranges || minimal_block_height > 0 || maximal_block_height do Enum.filter(blocks, &filter_func.(&1)) else blocks diff --git a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex index 0763d86ebdf2..4caf850df13d 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex @@ -8,6 +8,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do alias Ecto.Adapters.SQL alias Ecto.{Changeset, Multi, Repo} + alias EthereumJSONRPC.Utility.RangesHelper alias Explorer.Chain.{Block, Hash, Import, InternalTransaction, PendingBlockOperation, Transaction} alias Explorer.Chain.Events.Publisher alias Explorer.Chain.Import.Runner @@ -15,7 +16,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do alias Explorer.Repo, as: ExplorerRepo alias Explorer.Utility.MissingRangesManipulator - import Ecto.Query, only: [from: 2, where: 3] + import Ecto.Query @behaviour Runner @@ -691,23 +692,17 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do end defp remove_consensus_of_invalid_blocks(repo, invalid_block_numbers) do - minimal_block = Application.get_env(:indexer, :trace_first_block) - maximal_block = Application.get_env(:indexer, :trace_last_block) - if Enum.count(invalid_block_numbers) > 0 do update_query = from( block in Block, where: block.number in ^invalid_block_numbers and block.consensus == true, - where: block.number > ^minimal_block, + where: ^traceable_blocks_dynamic_query(), select: block.hash, # ShareLocks order already enforced by `acquire_blocks` (see docs: sharelocks.md) update: [set: [consensus: false]] ) - update_query = - if maximal_block, do: update_query |> where([block], block.number < ^maximal_block), else: update_query - try do {_num, result} = repo.update_all(update_query, []) @@ -754,4 +749,26 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do {:error, %{exception: postgrex_error, pending_hashes: valid_block_hashes}} end end + + defp traceable_blocks_dynamic_query do + case Application.get_env(:indexer, :trace_block_ranges) do + nil -> + min_block = Application.get_env(:indexer, :trace_first_block) + max_block = Application.get_env(:indexer, :trace_last_block) + + filter_by_min_dynamic = dynamic([block], block.number > ^min_block) + + if max_block, + do: dynamic([block], ^filter_by_min_dynamic and block.number < ^max_block), + else: filter_by_min_dynamic + + block_ranges -> + parsed_ranges = RangesHelper.parse_block_ranges(block_ranges) + + Enum.reduce(parsed_ranges, dynamic([_], false), fn + _from.._to = range, acc -> dynamic([block], ^acc or block.number in ^range) + num_to_latest, acc -> dynamic([block], ^acc or block.number >= ^num_to_latest) + end) + end + end end diff --git a/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs b/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs index ffe02c5dc7df..b27bed0ebfa6 100644 --- a/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs @@ -306,6 +306,29 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do assert PendingBlockOperation |> Repo.get(full_block.hash) |> is_nil() end + test "does not remove consensus from non-traceable blocks" do + original_config = Application.get_env(:indexer, :trace_block_ranges) + + full_block = insert(:block) + transaction_a = insert(:transaction) |> with_block(full_block) + transaction_b = insert(:transaction) |> with_block(full_block) + + Application.put_env(:indexer, :trace_block_ranges, "#{full_block.number + 1}..latest") + + insert(:pending_block_operation, block_hash: full_block.hash, block_number: full_block.number) + + transaction_a_changes = make_internal_transaction_changes(transaction_a, 0, nil) + + assert {:ok, _} = run_internal_transactions([transaction_a_changes]) + + assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction_a.hash) |> Repo.one() |> is_nil() + assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction_b.hash) |> Repo.one() |> is_nil() + + assert %{consensus: true} = Repo.get(Block, full_block.hash) + + on_exit(fn -> Application.put_env(:indexer, :trace_block_ranges, original_config) end) + end + test "successfully imports internal transaction with stop type" do block = insert(:block) transaction = insert(:transaction) |> with_block(block, status: :ok) diff --git a/apps/indexer/lib/indexer/block/catchup/helper.ex b/apps/indexer/lib/indexer/block/catchup/helper.ex deleted file mode 100644 index 951ed3548a46..000000000000 --- a/apps/indexer/lib/indexer/block/catchup/helper.ex +++ /dev/null @@ -1,39 +0,0 @@ -defmodule Indexer.Block.Catchup.Helper do - @moduledoc """ - Catchup helper functions - """ - - def sanitize_ranges(ranges) do - ranges - |> Enum.filter(&(not is_nil(&1))) - |> Enum.sort_by( - fn - from.._to -> from - el -> el - end, - :asc - ) - |> Enum.chunk_while( - nil, - fn - _from.._to = chunk, nil -> - {:cont, chunk} - - _ch_from..ch_to = chunk, acc_from..acc_to = acc -> - if Range.disjoint?(chunk, acc), - do: {:cont, acc, chunk}, - else: {:cont, acc_from..max(ch_to, acc_to)} - - num, nil -> - {:halt, num} - - num, acc_from.._ = acc -> - if Range.disjoint?(num..num, acc), do: {:cont, acc, num}, else: {:halt, acc_from} - - _, num -> - {:halt, num} - end, - fn reminder -> {:cont, reminder, nil} end - ) - end -end diff --git a/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex b/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex index 31d067217d8d..9c776610ae81 100644 --- a/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex +++ b/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex @@ -5,11 +5,10 @@ defmodule Indexer.Block.Catchup.MissingRangesCollector do use GenServer - alias Explorer.{Chain, Repo} + alias EthereumJSONRPC.Utility.RangesHelper + alias Explorer.{Chain, Helper, Repo} alias Explorer.Chain.Cache.BlockNumber - alias Explorer.Helper, as: ExplorerHelper alias Explorer.Utility.{MissingBlockRange, MissingRangesManipulator} - alias Indexer.Block.Catchup.Helper @default_missing_ranges_batch_size 100_000 @future_check_interval Application.compile_env(:indexer, __MODULE__)[:future_check_interval] @@ -233,7 +232,7 @@ defmodule Indexer.Block.Catchup.MissingRangesCollector do |> Enum.map(fn string_range -> case String.split(string_range, "..") do [from_string, "latest"] -> - ExplorerHelper.parse_integer(from_string) + Helper.parse_integer(from_string) [from_string, to_string] -> get_from_to(from_string, to_string) @@ -242,7 +241,7 @@ defmodule Indexer.Block.Catchup.MissingRangesCollector do nil end end) - |> Helper.sanitize_ranges() + |> RangesHelper.sanitize_ranges() case List.last(ranges) do _from.._to -> diff --git a/apps/indexer/lib/indexer/fetcher/coin_balance.ex b/apps/indexer/lib/indexer/fetcher/coin_balance.ex index 18de40eaaf16..3d7171724547 100644 --- a/apps/indexer/lib/indexer/fetcher/coin_balance.ex +++ b/apps/indexer/lib/indexer/fetcher/coin_balance.ex @@ -11,7 +11,7 @@ defmodule Indexer.Fetcher.CoinBalance do import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1] - alias EthereumJSONRPC.{Blocks, FetchedBalances} + alias EthereumJSONRPC.{Blocks, FetchedBalances, Utility.RangesHelper} alias Explorer.Chain alias Explorer.Chain.{Block, Hash} alias Explorer.Chain.Cache.Accounts @@ -83,13 +83,8 @@ defmodule Indexer.Fetcher.CoinBalance do # `{address, block}`, so take unique params only unique_entries = Enum.uniq(entries) - min_block = Application.get_env(:indexer, :trace_first_block) - max_block = Application.get_env(:indexer, :trace_last_block) - unique_filtered_entries = - Enum.filter(unique_entries, fn {_hash, block_number} -> - block_number >= min_block && if max_block, do: block_number <= max_block, else: true - end) + Enum.filter(unique_entries, fn {_hash, block_number} -> RangesHelper.traceable_block_number?(block_number) end) unique_entry_count = Enum.count(unique_filtered_entries) Logger.metadata(count: unique_entry_count) diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex index e4726d5a3c7a..8ffe843a4a0b 100644 --- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex @@ -12,6 +12,7 @@ defmodule Indexer.Fetcher.InternalTransaction do import Indexer.Block.Fetcher, only: [async_import_coin_balances: 2] + alias EthereumJSONRPC.Utility.RangesHelper alias Explorer.Chain alias Explorer.Chain.Block alias Explorer.Chain.Cache.{Accounts, Blocks} @@ -100,7 +101,7 @@ defmodule Indexer.Fetcher.InternalTransaction do filtered_unique_numbers = unique_numbers - |> EthereumJSONRPC.are_block_numbers_in_range?() + |> RangesHelper.filter_traceable_block_numbers() |> drop_genesis(json_rpc_named_arguments) filtered_unique_numbers_count = Enum.count(filtered_unique_numbers) diff --git a/config/runtime.exs b/config/runtime.exs index e9d915a65a7e..e8fec294afbf 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -465,6 +465,7 @@ config :indexer, block_ranges: System.get_env("BLOCK_RANGES"), first_block: ConfigHelper.parse_integer_env_var("FIRST_BLOCK", 0), last_block: ConfigHelper.parse_integer_or_nil_env_var("LAST_BLOCK"), + trace_block_ranges: System.get_env("TRACE_BLOCK_RANGES"), trace_first_block: ConfigHelper.parse_integer_env_var("TRACE_FIRST_BLOCK", 0), trace_last_block: ConfigHelper.parse_integer_or_nil_env_var("TRACE_LAST_BLOCK"), fetch_rewards_way: System.get_env("FETCH_REWARDS_WAY", "trace_block"), diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 9e7e050cb573..73af4cb85ad4 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -63,6 +63,7 @@ BLOCK_TRANSFORMER=base # BLOCK_RANGES= # FIRST_BLOCK= # LAST_BLOCK= +# TRACE_BLOCK_RANGES= # TRACE_FIRST_BLOCK= # TRACE_LAST_BLOCK= # FOOTER_CHAT_LINK= From a74042674261477fe6b88589f9faa7907dee7f0d Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Wed, 13 Dec 2023 18:50:48 +0600 Subject: [PATCH 171/607] Isolate throttable error count by request method --- CHANGELOG.md | 1 + .../ethereum_jsonrpc/request_coordinator.ex | 29 ++++++++++++------- .../request_coordinator_test.exs | 29 ++++++++++++------- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 719f013806c6..0457d876f782 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#8997](https://github.com/blockscout/blockscout/pull/8997) - Isolate throttable error count by request method - [#8972](https://github.com/blockscout/blockscout/pull/8972) - BENS integration - [#8957](https://github.com/blockscout/blockscout/pull/8957) - Add Tx Interpreter Service integration diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex index a80db36feb5f..77ee7d3906bd 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex @@ -58,7 +58,7 @@ defmodule EthereumJSONRPC.RequestCoordinator do alias EthereumJSONRPC.{RollingWindow, Tracer, Transport} - @error_key :throttleable_error_count + @error_key_base "throttleable_error_count" @throttle_key :throttle_requests_count @doc """ @@ -73,7 +73,9 @@ defmodule EthereumJSONRPC.RequestCoordinator do @spec perform(Transport.batch_request(), Transport.t(), Transport.options(), non_neg_integer()) :: {:ok, Transport.batch_response()} | {:error, term()} def perform(request, transport, transport_options, throttle_timeout) do - sleep_time = sleep_time() + request_method = request_method(request) + + sleep_time = sleep_time(request_method) if sleep_time <= throttle_timeout do :timer.sleep(sleep_time) @@ -85,7 +87,7 @@ defmodule EthereumJSONRPC.RequestCoordinator do trace_request(request, fn -> request |> transport.json_rpc(transport_options) - |> handle_transport_response() + |> handle_transport_response(request_method) end) :error -> @@ -110,19 +112,24 @@ defmodule EthereumJSONRPC.RequestCoordinator do defp trace_request(_, fun), do: fun.() - defp handle_transport_response({:error, {error_type, _}} = error) when error_type in [:bad_gateway, :bad_response] do - RollingWindow.inc(table(), @error_key) + defp request_method([request | _]), do: request_method(request) + defp request_method(%{method: method}), do: method + defp request_method(_), do: nil + + defp handle_transport_response({:error, {error_type, _}} = error, method) + when error_type in [:bad_gateway, :bad_response] do + RollingWindow.inc(table(), method_error_key(method)) inc_throttle_table() error end - defp handle_transport_response({:error, :timeout} = error) do - RollingWindow.inc(table(), @error_key) + defp handle_transport_response({:error, :timeout} = error, method) do + RollingWindow.inc(table(), method_error_key(method)) inc_throttle_table() error end - defp handle_transport_response(response) do + defp handle_transport_response(response, _method) do inc_throttle_table() response end @@ -154,14 +161,16 @@ defmodule EthereumJSONRPC.RequestCoordinator do end end - defp sleep_time do - wait_coefficient = RollingWindow.count(table(), @error_key) + defp sleep_time(request_method) do + wait_coefficient = RollingWindow.count(table(), method_error_key(request_method)) jitter = :rand.uniform(config!(:max_jitter)) wait_per_timeout = config!(:wait_per_timeout) wait_coefficient * (wait_per_timeout + jitter) end + defp method_error_key(method), do: :"#{@error_key_base}_#{method}" + defp table do :rolling_window_opts |> config!() diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/request_coordinator_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/request_coordinator_test.exs index efda05ab86e8..0eaa6c1f3159 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/request_coordinator_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/request_coordinator_test.exs @@ -26,28 +26,35 @@ defmodule EthereumJSONRPC.RequestCoordinatorTest do describe "perform/4" do test "forwards result whenever a request doesn't timeout", %{timeout_table: timeout_table} do expect(EthereumJSONRPC.Mox, :json_rpc, fn _, _ -> {:ok, %{}} end) - assert RollingWindow.count(timeout_table, :throttleable_error_count) == 0 - assert {:ok, %{}} == RequestCoordinator.perform(%{}, EthereumJSONRPC.Mox, [], :timer.minutes(60)) - assert RollingWindow.count(timeout_table, :throttleable_error_count) == 0 + assert RollingWindow.count(timeout_table, :throttleable_error_count_eth_call) == 0 + + assert {:ok, %{}} == + RequestCoordinator.perform(%{method: "eth_call"}, EthereumJSONRPC.Mox, [], :timer.minutes(60)) + + assert RollingWindow.count(timeout_table, :throttleable_error_count_eth_call) == 0 end test "increments counter on certain errors", %{timeout_table: timeout_table} do - expect(EthereumJSONRPC.Mox, :json_rpc, fn :timeout, _ -> {:error, :timeout} end) - expect(EthereumJSONRPC.Mox, :json_rpc, fn :bad_gateway, _ -> {:error, {:bad_gateway, "message"}} end) + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{method: "timeout"}, _ -> {:error, :timeout} end) + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{method: "bad_gateway"}, _ -> {:error, {:bad_gateway, "message"}} end) + + assert {:error, :timeout} == + RequestCoordinator.perform(%{method: "timeout"}, EthereumJSONRPC.Mox, [], :timer.minutes(60)) - assert {:error, :timeout} == RequestCoordinator.perform(:timeout, EthereumJSONRPC.Mox, [], :timer.minutes(60)) - assert RollingWindow.count(timeout_table, :throttleable_error_count) == 1 + assert RollingWindow.count(timeout_table, :throttleable_error_count_timeout) == 1 + assert RollingWindow.count(timeout_table, :throttleable_error_count_bad_gateway) == 0 assert {:error, {:bad_gateway, "message"}} == - RequestCoordinator.perform(:bad_gateway, EthereumJSONRPC.Mox, [], :timer.minutes(60)) + RequestCoordinator.perform(%{method: "bad_gateway"}, EthereumJSONRPC.Mox, [], :timer.minutes(60)) - assert RollingWindow.count(timeout_table, :throttleable_error_count) == 2 + assert RollingWindow.count(timeout_table, :throttleable_error_count_timeout) == 1 + assert RollingWindow.count(timeout_table, :throttleable_error_count_bad_gateway) == 1 end test "returns timeout error if sleep time will exceed max timeout", %{timeout_table: timeout_table} do expect(EthereumJSONRPC.Mox, :json_rpc, 0, fn _, _ -> :ok end) - RollingWindow.inc(timeout_table, :throttleable_error_count) - assert {:error, :timeout} == RequestCoordinator.perform(%{}, EthereumJSONRPC.Mox, [], 1) + RollingWindow.inc(timeout_table, :throttleable_error_count_eth_call) + assert {:error, :timeout} == RequestCoordinator.perform(%{method: "eth_call"}, EthereumJSONRPC.Mox, [], 1) end test "increments throttle_table even when not an error", %{throttle_table: throttle_table} do From 284efc9891f8d7f77b4bedf69b8b0d62a59ef8d5 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 21 Dec 2023 13:32:17 +0300 Subject: [PATCH 172/607] Run base CI on v6.0.0-dev branch --- .github/workflows/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 5efcd092a62e..e67df9c5c0c4 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -28,6 +28,7 @@ on: pull_request: branches: - master + - v6.0.0-dev - production-optimism env: From b2935c746eae49c618402a63ca157242d47cf5af Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 21 Dec 2023 13:33:09 +0300 Subject: [PATCH 173/607] Run base CI on push to v6.0.0-dev branch --- .github/workflows/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index e67df9c5c0c4..9dac00c1b4d1 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - v6.0.0-dev - production-core - production-eth-experimental - production-eth-goerli From 0a554020d2dbd39951fc77820985c078ed1c111c Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Thu, 21 Dec 2023 13:37:01 +0300 Subject: [PATCH 174/607] Add address preload in tx summary response --- .../transaction_interpretation.ex | 43 ++++++++++++++++--- apps/explorer/lib/explorer/chain.ex | 4 +- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex index 587c78cfada9..c01da93f2cf5 100644 --- a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex +++ b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex @@ -39,7 +39,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do case HTTPoison.post(url, Jason.encode!(body), headers, recv_timeout: @post_timeout) do {:ok, %Response{body: body, status_code: 200}} -> - body |> Jason.decode() |> preload_tokens() + body |> Jason.decode() |> preload_template_variables() error -> old_truncate = Application.get_env(:logger, :truncate) @@ -148,12 +148,12 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do |> Enum.map(fn {log, decoded_log} -> TransactionView.prepare_log(log, transaction.hash, decoded_log, true) end) end - defp preload_tokens({:ok, %{"success" => true, "data" => %{"summaries" => summaries} = data}}) do + defp preload_template_variables({:ok, %{"success" => true, "data" => %{"summaries" => summaries} = data}}) do summaries_updated = Enum.map(summaries, fn %{"summary_template_variables" => summary_template_variables} = summary -> summary_template_variables_preloaded = Enum.reduce(summary_template_variables, %{}, fn {key, value}, acc -> - Map.put(acc, key, preload_token(value)) + Map.put(acc, key, preload_template_variable(value)) end) Map.put(summary, "summary_template_variables", summary_template_variables_preloaded) @@ -162,17 +162,46 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do {:ok, %{"success" => true, "data" => Map.put(data, "summaries", summaries_updated)}} end - defp preload_tokens(error), do: error + defp preload_template_variables(error), do: error - defp preload_token(%{"type" => "token", "value" => %{"address" => address_hash_string} = value}), + defp preload_template_variable(%{"type" => "token", "value" => %{"address" => address_hash_string} = value}), do: %{ "type" => "token", "value" => address_hash_string |> Chain.token_from_address_hash(@api_true) |> token_from_db() |> Map.merge(value) } - defp preload_token(other), do: other + defp preload_template_variable(%{"type" => "address", "value" => %{"hash" => address_hash_string} = value}), + do: %{ + "type" => "address", + "value" => + address_hash_string + |> Chain.hash_to_address( + [ + necessity_by_association: %{ + :names => :optional, + :smart_contract => :optional + }, + api?: true + ], + false + ) + |> address_from_db() + |> Map.merge(value) + } - defp token_from_db({:error, _}), do: %{} + defp preload_template_variable(other), do: other + defp token_from_db({:error, _}), do: %{} defp token_from_db({:ok, token}), do: TokenView.render("token.json", %{token: token}) + + defp address_from_db({:error, _}), do: %{} + + defp address_from_db({:ok, address}), + do: + Helper.address_with_info( + nil, + address, + address.hash, + true + ) end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index fa25f985b0df..1accf987cb44 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1017,10 +1017,10 @@ defmodule Explorer.Chain do Optionally it also accepts a boolean to fetch the `has_decompiled_code?` virtual field or not """ - @spec hash_to_address(Hash.Address.t(), [necessity_by_association_option | api?], boolean()) :: + @spec hash_to_address(Hash.Address.t() | binary(), [necessity_by_association_option | api?], boolean()) :: {:ok, Address.t()} | {:error, :not_found} def hash_to_address( - %Hash{byte_count: unquote(Hash.Address.byte_count())} = hash, + hash, options \\ [ necessity_by_association: %{ :contracts_creation_internal_transaction => :optional, From be3540a12577e384703de3ca67876e22193bdbd0 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Sat, 9 Dec 2023 16:33:02 +0300 Subject: [PATCH 175/607] Support legacy paging options for address txs --- CHANGELOG.md | 1 + .../api/v2/address_controller_test.exs | 45 +++++++++++++++++++ .../lib/explorer/chain/transaction.ex | 3 +- 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 719f013806c6..dba610044b9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Fixes - [#9013](https://github.com/blockscout/blockscout/pull/9013) - Speed up `Indexer.Fetcher.TokenInstance.LegacySanitize` +- [#8969](https://github.com/blockscout/blockscout/pull/8969) - Support legacy paging options for address transaction endpoint - [#8955](https://github.com/blockscout/blockscout/pull/8955) - Remove daily balances updating from BlockReward fetcher - [#8846](https://github.com/blockscout/blockscout/pull/8846) - Handle nil gas_price at address view diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index 6ed830528b12..d3749c1628af 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -447,6 +447,51 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do check_paginated_response(response, response_2nd_page, txs) end + test "backward compatible with legacy paging params", %{conn: conn} do + address = insert(:address) + block = insert(:block) + + txs = insert_list(51, :transaction, from_address: address) |> with_block(block) + + [_, tx_before_last | _] = txs + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions") + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/addresses/#{address.hash}/transactions", + %{"block_number" => to_string(block.number), "index" => to_string(tx_before_last.index)} + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, txs) + end + + test "backward compatible with legacy paging params for pending transactions", %{conn: conn} do + address = insert(:address) + + txs = insert_list(51, :transaction, from_address: address) + + [_, tx_before_last | _] = txs + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions") + assert response = json_response(request, 200) + + request_2nd_page_pending = + get( + conn, + "/api/v2/addresses/#{address.hash}/transactions", + %{"inserted_at" => to_string(tx_before_last.inserted_at), "hash" => to_string(tx_before_last.hash)} + ) + + assert response_2nd_page_pending = json_response(request_2nd_page_pending, 200) + + check_paginated_response(response, response_2nd_page_pending, txs) + end + test "can order and paginate by fee ascending", %{conn: conn} do address = insert(:address) diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 1f7e67790bdc..c488d56bac5b 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -1373,6 +1373,7 @@ defmodule Explorer.Chain.Transaction do defp address_to_transactions_tasks(address_hash, options, old_ui?) do direction = Keyword.get(options, :direction) necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + old_ui? = old_ui? || is_tuple(Keyword.get(options, :paging_options, Chain.default_paging_options()).key) options |> address_to_transactions_tasks_query(false, old_ui?) @@ -1603,7 +1604,7 @@ defmodule Explorer.Chain.Transaction do end @doc """ - Adds a `has_token_transfers` field to the query via `select_merge` if second argument is `true` and returns + Adds a `has_token_transfers` field to the query via `select_merge` if second argument is `false` and returns the query untouched otherwise. """ @spec put_has_token_transfers_to_tx(Ecto.Query.t() | atom, boolean) :: Ecto.Query.t() From 473a4d0851b4fdf279d0716afec722a148e84365 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Mon, 11 Dec 2023 12:39:40 +0300 Subject: [PATCH 176/607] Add EIP-4844 compatibility --- CHANGELOG.md | 1 + apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex | 5 +++++ apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex | 5 +++++ .../lib/ethereum_jsonrpc/transaction.ex | 10 ++++++++++ 4 files changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1184bb0ec74c..9c442504ec03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - [#8997](https://github.com/blockscout/blockscout/pull/8997) - Isolate throttable error count by request method +- [#8975](https://github.com/blockscout/blockscout/pull/8975) - Add EIP-4844 compatibility (not full support yet) - [#8972](https://github.com/blockscout/blockscout/pull/8972) - BENS integration - [#8957](https://github.com/blockscout/blockscout/pull/8957) - Add Tx Interpreter Service integration diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex index 1431d39bde2b..d07606297ebb 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex @@ -766,6 +766,11 @@ defmodule EthereumJSONRPC.Block do {key, quantity_to_integer(quantity)} end + # to be merged with clause above ^ + defp entry_to_elixir({key, _quantity}, _block) when key in ~w(blobGasUsed excessBlobGas) do + {:ignore, :ignore} + end + # Size and totalDifficulty may be `nil` for uncle blocks defp entry_to_elixir({key, nil}, _block) when key in ~w(size totalDifficulty) do {key, nil} diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex index f3cf67aab1d3..8dc9b2f82edc 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex @@ -319,6 +319,11 @@ defmodule EthereumJSONRPC.Receipt do :ignore end + # EIP-4844 transaction receipt fields + defp entry_to_elixir({key, _}) when key in ~w(blobGasUsed blobGasPrice) do + :ignore + end + defp entry_to_elixir({key, value}) do {:error, {:unknown_key, %{key: key, value: value}}} end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex index 1e3f8f1cf954..964dc2ec1f2f 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex @@ -621,6 +621,16 @@ defmodule EthereumJSONRPC.Transaction do {key, quantity_to_integer(quantity)} end + # to be merged with the clause above ^ + defp entry_to_elixir({"maxFeePerBlobGas", _value}) do + {nil, nil} + end + + # EIP-4844 specific field with value of type of list of hashes + defp entry_to_elixir({"blobVersionedHashes", _value}) do + {nil, nil} + end + # as always ganache has it's own vision on JSON RPC standard defp entry_to_elixir({key, nil}) when key in ~w(r s v) do {key, 0} From e22046417fa2570f77bfdfffb0fefdde5496a88c Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Thu, 7 Dec 2023 21:01:23 +0600 Subject: [PATCH 177/607] Set poll: false for internal transactions fetcher --- CHANGELOG.md | 1 + apps/indexer/lib/indexer/fetcher/internal_transaction.ex | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1184bb0ec74c..db10ffce4c22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - [#9013](https://github.com/blockscout/blockscout/pull/9013) - Speed up `Indexer.Fetcher.TokenInstance.LegacySanitize` - [#8969](https://github.com/blockscout/blockscout/pull/8969) - Support legacy paging options for address transaction endpoint +- [#8965](https://github.com/blockscout/blockscout/pull/8965) - Set poll: false for internal transactions fetcher - [#8955](https://github.com/blockscout/blockscout/pull/8955) - Remove daily balances updating from BlockReward fetcher - [#8846](https://github.com/blockscout/blockscout/pull/8846) - Handle nil gas_price at address view diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex index e4726d5a3c7a..0d2d783ab2fb 100644 --- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex @@ -365,6 +365,7 @@ defmodule Indexer.Fetcher.InternalTransaction do defp defaults do [ + poll: false, flush_interval: :timer.seconds(3), max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency, max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size, From a02b3edfbc121ad2de2d3a2a5774bf242cf5c492 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Fri, 15 Dec 2023 20:24:46 +0600 Subject: [PATCH 178/607] Add test for internal transactions fetcher race condition --- .../indexer/fetcher/internal_transaction.ex | 2 +- .../fetcher/internal_transaction_test.exs | 41 ++++++++++++++++++- .../internal_transaction_supervisor_case.ex | 12 +++--- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex index 0d2d783ab2fb..669bb11c142a 100644 --- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex @@ -363,7 +363,7 @@ defmodule Indexer.Fetcher.InternalTransaction do defp invalidate_block_from_error(_error_data), do: :ok - defp defaults do + def defaults do [ poll: false, flush_interval: :timer.seconds(3), diff --git a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs index b36006b872f6..e451525869b1 100644 --- a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs +++ b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs @@ -5,8 +5,10 @@ defmodule Indexer.Fetcher.InternalTransactionTest do import ExUnit.CaptureLog import Mox - alias Explorer.Chain - alias Explorer.Chain.PendingBlockOperation + alias Ecto.Multi + alias Explorer.{Chain, Repo} + alias Explorer.Chain.{Block, PendingBlockOperation} + alias Explorer.Chain.Import.Runner.Blocks alias Indexer.Fetcher.{CoinBalance, InternalTransaction, PendingTransaction} # MUST use global mode because we aren't guaranteed to get PendingTransactionFetcher's pid back fast enough to `allow` @@ -466,4 +468,39 @@ defmodule Indexer.Fetcher.InternalTransactionTest do assert logs =~ "foreign_key_violation on internal transactions import, foreign transactions hashes:" end end + + test "doesn't delete pending block operations after block import if no async process was requested", %{ + json_rpc_named_arguments: json_rpc_named_arguments + } do + fetcher_options = + Keyword.merge([poll: true, json_rpc_named_arguments: json_rpc_named_arguments], InternalTransaction.defaults()) + + if fetcher_options[:poll] do + expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> + {:ok, [%{id: id, result: []}]} + end) + end + + InternalTransaction.Supervisor.Case.start_supervised!(fetcher_options) + + %Ecto.Changeset{valid?: true, changes: block_changes} = + Block.changeset(%Block{}, params_for(:block, miner_hash: insert(:address).hash, number: 1)) + + changes_list = [block_changes] + timestamp = DateTime.utc_now() + options = %{timestamps: %{inserted_at: timestamp, updated_at: timestamp}} + + assert [] = Repo.all(PendingBlockOperation) + + {:ok, %{blocks: [%{number: block_number, hash: block_hash}]}} = + Multi.new() + |> Blocks.run(changes_list, options) + |> Repo.transaction() + + assert %{block_number: ^block_number, block_hash: ^block_hash} = Repo.one(PendingBlockOperation) + + Process.sleep(4000) + + assert %{block_number: ^block_number, block_hash: ^block_hash} = Repo.one(PendingBlockOperation) + end end diff --git a/apps/indexer/test/support/indexer/fetcher/internal_transaction_supervisor_case.ex b/apps/indexer/test/support/indexer/fetcher/internal_transaction_supervisor_case.ex index b067997b5fbf..0c3ba2be7358 100644 --- a/apps/indexer/test/support/indexer/fetcher/internal_transaction_supervisor_case.ex +++ b/apps/indexer/test/support/indexer/fetcher/internal_transaction_supervisor_case.ex @@ -4,11 +4,13 @@ defmodule Indexer.Fetcher.InternalTransaction.Supervisor.Case do def start_supervised!(fetcher_arguments \\ []) when is_list(fetcher_arguments) do merged_fetcher_arguments = Keyword.merge( - fetcher_arguments, - flush_interval: 50, - max_batch_size: 1, - max_concurrency: 1, - poll: false + [ + flush_interval: 50, + max_batch_size: 1, + max_concurrency: 1, + poll: false + ], + fetcher_arguments ) [merged_fetcher_arguments] From 6969f78c0e3ae9fc2bdf6d6aa912207ccdad3eb2 Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Thu, 21 Dec 2023 19:07:48 +0300 Subject: [PATCH 179/607] Improrve spec --- .../explorer/lib/explorer/chain/transaction.ex | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 8971b738781d..a054c6ee250c 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -595,15 +595,17 @@ defmodule Explorer.Chain.Transaction do # Because there is no contract association, we know the contract was not verified @spec decoded_input_data( - %{:__struct__ => Ecto.Association.NotLoaded | Explorer.Chain.Transaction, optional(any()) => any()}, - any(), - any(), - any(), - any() + NotLoaded.t() | Transaction.t(), + boolean(), + [Chain.api?()], + full_abi_acc, + methods_acc ) :: - {{:error | :ok | binary(), any()} - | {:error, :contract_not_verified | :contract_verified, list()} - | {:ok, binary(), binary(), list()}, any(), any()} + {error_type | success_type, full_abi_acc, methods_acc} + when full_abi_acc: map(), + methods_acc: map(), + error_type: {:error, any()} | {:error, :contract_not_verified | :contract_verified, list()}, + success_type: {:ok | binary(), any()} | {:ok, binary(), binary(), list()} def decoded_input_data(tx, skip_sig_provider? \\ false, options, full_abi_acc \\ %{}, methods_acc \\ %{}) def decoded_input_data(%__MODULE__{to_address: nil}, _, _, full_abi_acc, methods_acc), From 6e501106f850340a2ceba72561a3c0f5eb63686e Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Fri, 22 Dec 2023 10:03:05 +0300 Subject: [PATCH 180/607] Enable sig provider in tx summary request --- .../microservice_interfaces/transaction_interpretation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex index c01da93f2cf5..4b6d7edc7477 100644 --- a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex +++ b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex @@ -76,7 +76,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do created_contract_address: [:names, :token, :smart_contract] ]) - skip_sig_provider? = true + skip_sig_provider? = false {decoded_input, _abi_acc, _methods_acc} = Transaction.decoded_input_data(transaction, skip_sig_provider?, @api_true) decoded_input_data = decoded_input |> TransactionView.format_decoded_input() |> TransactionView.decoded_input() From 8430dca08548cb77e721435937cc180862ff27c0 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Mon, 18 Dec 2023 18:35:24 +0600 Subject: [PATCH 181/607] Add SmartContractRealtimeEventHandler --- CHANGELOG.md | 1 + .../lib/block_scout_web/application.ex | 3 +- .../block_scout_web/realtime_event_handler.ex | 3 -- .../smart_contract_realtime_event_handler.ex | 28 +++++++++++++++++++ 4 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 apps/block_scout_web/lib/block_scout_web/smart_contract_realtime_event_handler.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d87321c5460..a63c6d520a98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#9018](https://github.com/blockscout/blockscout/pull/9018) - Add SmartContractRealtimeEventHandler - [#8997](https://github.com/blockscout/blockscout/pull/8997) - Isolate throttable error count by request method - [#8975](https://github.com/blockscout/blockscout/pull/8975) - Add EIP-4844 compatibility (not full support yet) - [#8972](https://github.com/blockscout/blockscout/pull/8972) - BENS integration diff --git a/apps/block_scout_web/lib/block_scout_web/application.ex b/apps/block_scout_web/lib/block_scout_web/application.ex index cb46a885761c..f323ef8e959d 100644 --- a/apps/block_scout_web/lib/block_scout_web/application.ex +++ b/apps/block_scout_web/lib/block_scout_web/application.ex @@ -8,7 +8,7 @@ defmodule BlockScoutWeb.Application do alias BlockScoutWeb.API.APILogger alias BlockScoutWeb.Counters.{BlocksIndexedCounter, InternalTransactionsIndexedCounter} alias BlockScoutWeb.{Endpoint, Prometheus} - alias BlockScoutWeb.{MainPageRealtimeEventHandler, RealtimeEventHandler} + alias BlockScoutWeb.{MainPageRealtimeEventHandler, RealtimeEventHandler, SmartContractRealtimeEventHandler} def start(_type, _args) do import Supervisor @@ -36,6 +36,7 @@ defmodule BlockScoutWeb.Application do {Absinthe.Subscription, Endpoint}, {MainPageRealtimeEventHandler, name: MainPageRealtimeEventHandler}, {RealtimeEventHandler, name: RealtimeEventHandler}, + {SmartContractRealtimeEventHandler, name: SmartContractRealtimeEventHandler}, {BlocksIndexedCounter, name: BlocksIndexedCounter}, {InternalTransactionsIndexedCounter, name: InternalTransactionsIndexedCounter} ] diff --git a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex index 7d029f17f885..1fedc2dc3dba 100644 --- a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex +++ b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex @@ -24,11 +24,8 @@ defmodule BlockScoutWeb.RealtimeEventHandler do Subscriber.to(:address_coin_balances, :on_demand) Subscriber.to(:address_current_token_balances, :on_demand) Subscriber.to(:address_token_balances, :on_demand) - Subscriber.to(:contract_verification_result, :on_demand) Subscriber.to(:token_total_supply, :on_demand) Subscriber.to(:changed_bytecode, :on_demand) - Subscriber.to(:smart_contract_was_verified, :on_demand) - Subscriber.to(:smart_contract_was_not_verified, :on_demand) Subscriber.to(:eth_bytecode_db_lookup_started, :on_demand) Subscriber.to(:zkevm_confirmed_batches, :realtime) # Does not come from the indexer diff --git a/apps/block_scout_web/lib/block_scout_web/smart_contract_realtime_event_handler.ex b/apps/block_scout_web/lib/block_scout_web/smart_contract_realtime_event_handler.ex new file mode 100644 index 000000000000..e94e8a3bb9bb --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/smart_contract_realtime_event_handler.ex @@ -0,0 +1,28 @@ +defmodule BlockScoutWeb.SmartContractRealtimeEventHandler do + @moduledoc """ + Subscribing process for smart contract verification related broadcast events from realtime. + """ + + use GenServer + + alias BlockScoutWeb.Notifier + alias Explorer.Chain.Events.Subscriber + + def start_link(_) do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + @impl true + def init([]) do + Subscriber.to(:contract_verification_result, :on_demand) + Subscriber.to(:smart_contract_was_verified, :on_demand) + Subscriber.to(:smart_contract_was_not_verified, :on_demand) + {:ok, []} + end + + @impl true + def handle_info(event, state) do + Notifier.handle_event(event) + {:noreply, state} + end +end From 373eb063da9a3e486dc13cb49b43a7a2d9ce3c8e Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 22 Dec 2023 16:01:16 +0300 Subject: [PATCH 182/607] Add dependabot bumps to CHANGELOG --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 819c146d5c17..10253eb27289 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,18 @@
Dependencies version bumps + +- [#8986](https://github.com/blockscout/blockscout/pull/8986) - Bump chart.js from 4.4.0 to 4.4.1 in /apps/block_scout_web/assets +- [#8982](https://github.com/blockscout/blockscout/pull/8982) - Bump ex_doc from 0.30.9 to 0.31.0 +- [#8987](https://github.com/blockscout/blockscout/pull/8987) - Bump @babel/preset-env from 7.23.5 to 7.23.6 in /apps/block_scout_web/assets +- [#8984](https://github.com/blockscout/blockscout/pull/8984) - Bump ecto_sql from 3.11.0 to 3.11.1 +- [#8988](https://github.com/blockscout/blockscout/pull/8988) - Bump core-js from 3.33.3 to 3.34.0 in /apps/block_scout_web/assets +- [#8980](https://github.com/blockscout/blockscout/pull/8980) - Bump exvcr from 0.14.4 to 0.15.0 +- [#8985](https://github.com/blockscout/blockscout/pull/8985) - Bump @babel/core from 7.23.5 to 7.23.6 in /apps/block_scout_web/assets +- [#9020](https://github.com/blockscout/blockscout/pull/9020) - Bump eslint-plugin-import from 2.29.0 to 2.29.1 in /apps/block_scout_web/assets +- [#9021](https://github.com/blockscout/blockscout/pull/9021) - Bump eslint from 8.55.0 to 8.56.0 in /apps/block_scout_web/assets +- [#9019](https://github.com/blockscout/blockscout/pull/9019) - Bump @amplitude/analytics-browser from 2.3.6 to 2.3.7 in /apps/block_scout_web/assets +
## 5.3.3-beta From 3ab544f5008ba494f83b331511c8a219c70dc2d1 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Fri, 22 Dec 2023 19:05:21 +0600 Subject: [PATCH 183/607] Set trace_block_ranges to first..last if not set --- .../ethereum_jsonrpc/utility/ranges_helper.ex | 45 +++++++++---------- .../explorer/chain/import/runner/blocks.ex | 6 +-- .../import/runner/internal_transactions.ex | 25 ++++------- config/runtime.exs | 15 +++++-- 4 files changed, 43 insertions(+), 48 deletions(-) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex index 75f00bccdee5..f7220b044d0b 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex @@ -4,38 +4,37 @@ defmodule EthereumJSONRPC.Utility.RangesHelper do Helper for ranges manipulations. """ + @default_trace_block_ranges "0..latest" + @spec traceable_block_number?(integer() | nil) :: boolean() def traceable_block_number?(block_number) do - case Application.get_env(:indexer, :trace_block_ranges) do - nil -> - min_block = Application.get_env(:indexer, :trace_first_block) - max_block = Application.get_env(:indexer, :trace_last_block) - - !block_number || (block_number >= min_block && if(max_block, do: block_number <= max_block, else: true)) - - block_ranges -> - parsed_ranges = parse_block_ranges(block_ranges) - - number_in_ranges?(block_number, parsed_ranges) + if trace_ranges_present?() do + number_in_ranges?(block_number, get_trace_block_ranges()) + else + true end end @spec filter_traceable_block_numbers([integer()]) :: [integer()] def filter_traceable_block_numbers(block_numbers) do - case Application.get_env(:indexer, :trace_block_ranges) do - nil -> - min_block = Application.get_env(:indexer, :trace_first_block) - max_block = Application.get_env(:indexer, :trace_last_block) - - Enum.filter(block_numbers, fn block_number -> - block_number >= min_block && if max_block, do: block_number <= max_block, else: true - end) + if trace_ranges_present?() do + trace_block_ranges = get_trace_block_ranges() + Enum.filter(block_numbers, &number_in_ranges?(&1, trace_block_ranges)) + else + block_numbers + end + end - block_ranges -> - parsed_ranges = parse_block_ranges(block_ranges) + @spec trace_ranges_present? :: boolean() + def trace_ranges_present? do + Application.get_env(:indexer, :trace_block_ranges) != @default_trace_block_ranges + end - Enum.filter(block_numbers, &number_in_ranges?(&1, parsed_ranges)) - end + @spec get_trace_block_ranges :: [Range.t() | integer()] + def get_trace_block_ranges do + :indexer + |> Application.get_env(:trace_block_ranges) + |> parse_block_ranges() end @spec parse_block_ranges(binary()) :: [Range.t() | integer()] diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex index 6ff8ee7d6432..83a426b313e7 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex @@ -887,11 +887,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do end defp filter_by_height_range(blocks, filter_func) do - trace_block_ranges = Application.get_env(:indexer, :trace_block_ranges) - minimal_block_height = Application.get_env(:indexer, :trace_first_block) - maximal_block_height = Application.get_env(:indexer, :trace_last_block) - - if trace_block_ranges || minimal_block_height > 0 || maximal_block_height do + if RangesHelper.trace_ranges_present?() do Enum.filter(blocks, &filter_func.(&1)) else blocks diff --git a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex index 4caf850df13d..2ef9cfe1fe57 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex @@ -751,24 +751,15 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do end defp traceable_blocks_dynamic_query do - case Application.get_env(:indexer, :trace_block_ranges) do - nil -> - min_block = Application.get_env(:indexer, :trace_first_block) - max_block = Application.get_env(:indexer, :trace_last_block) + if RangesHelper.trace_ranges_present?() do + block_ranges = RangesHelper.get_trace_block_ranges() - filter_by_min_dynamic = dynamic([block], block.number > ^min_block) - - if max_block, - do: dynamic([block], ^filter_by_min_dynamic and block.number < ^max_block), - else: filter_by_min_dynamic - - block_ranges -> - parsed_ranges = RangesHelper.parse_block_ranges(block_ranges) - - Enum.reduce(parsed_ranges, dynamic([_], false), fn - _from.._to = range, acc -> dynamic([block], ^acc or block.number in ^range) - num_to_latest, acc -> dynamic([block], ^acc or block.number >= ^num_to_latest) - end) + Enum.reduce(block_ranges, dynamic([_], false), fn + _from.._to = range, acc -> dynamic([block], ^acc or block.number in ^range) + num_to_latest, acc -> dynamic([block], ^acc or block.number >= ^num_to_latest) + end) + else + dynamic([_], true) end end end diff --git a/config/runtime.exs b/config/runtime.exs index e8fec294afbf..f876325abb5b 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -459,15 +459,24 @@ config :explorer, Explorer.MicroserviceInterfaces.BENS, ### Indexer ### ############### +trace_first_block = ConfigHelper.parse_integer_env_var("TRACE_FIRST_BLOCK", 0) +trace_last_block = ConfigHelper.parse_integer_or_nil_env_var("TRACE_LAST_BLOCK") + +trace_block_ranges = + case ConfigHelper.safe_get_env("TRACE_BLOCK_RANGES", nil) do + "" -> "#{trace_first_block}..#{trace_last_block || "latest"}" + ranges -> ranges + end + config :indexer, block_transformer: ConfigHelper.block_transformer(), metadata_updater_milliseconds_interval: ConfigHelper.parse_time_env_var("TOKEN_METADATA_UPDATE_INTERVAL", "48h"), block_ranges: System.get_env("BLOCK_RANGES"), first_block: ConfigHelper.parse_integer_env_var("FIRST_BLOCK", 0), last_block: ConfigHelper.parse_integer_or_nil_env_var("LAST_BLOCK"), - trace_block_ranges: System.get_env("TRACE_BLOCK_RANGES"), - trace_first_block: ConfigHelper.parse_integer_env_var("TRACE_FIRST_BLOCK", 0), - trace_last_block: ConfigHelper.parse_integer_or_nil_env_var("TRACE_LAST_BLOCK"), + trace_block_ranges: trace_block_ranges, + trace_first_block: trace_first_block, + trace_last_block: trace_last_block, fetch_rewards_way: System.get_env("FETCH_REWARDS_WAY", "trace_block"), memory_limit: ConfigHelper.indexer_memory_limit(), receipts_batch_size: ConfigHelper.parse_integer_env_var("INDEXER_RECEIPTS_BATCH_SIZE", 250), From dcf423b673a4d3f44d335df2e00bfd1e636c1b14 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 22 Dec 2023 16:15:32 +0300 Subject: [PATCH 184/607] Add banner about new UI --- .../lib/block_scout_web/templates/layout/app.html.eex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex index d07b77ca9e14..34ceb918738a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex @@ -69,6 +69,9 @@ +
+ <%= raw("The new Blockscout UI is now open source! Learn how to deploy it here") %> +
<% show_maintenance_alert = Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:show_maintenance_alert] %> <%= if show_maintenance_alert do %>
From f2a2b2cc8ef4f42a47f22a1747984a08fdbc5619 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 22 Dec 2023 16:20:55 +0300 Subject: [PATCH 185/607] Fix gettext --- apps/block_scout_web/priv/gettext/default.pot | 10 +++++----- .../priv/gettext/en/LC_MESSAGES/default.po | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 38cc6f9a2ee5..096df745c2b0 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -86,7 +86,7 @@ msgstr "" msgid ") may be added for each contract. Click the Add Library button to add an additional one." msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:90 +#: lib/block_scout_web/templates/layout/app.html.eex:93 #, elixir-autogen, elixir-format msgid "- We're indexing this chain right now. Some of the counts may be inaccurate." msgstr "" @@ -122,7 +122,7 @@ msgstr "" msgid "A block producer who successfully included the block onto the blockchain." msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:97 +#: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "A confirmation email was sent to" msgstr "" @@ -2082,7 +2082,7 @@ msgid "Play" msgstr "" #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:22 -#: lib/block_scout_web/templates/layout/app.html.eex:97 +#: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "Please confirm your email address to use the My Account feature." msgstr "" @@ -2262,7 +2262,7 @@ msgstr "" msgid "Request to edit a public tag/label" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:97 +#: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "Resend verification email" msgstr "" @@ -3598,7 +3598,7 @@ msgstr "" msgid "of" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:97 +#: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "on sign up. Didn’t receive?" msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index dbd9a598a406..ef9ebe1481ae 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -86,7 +86,7 @@ msgstr "" msgid ") may be added for each contract. Click the Add Library button to add an additional one." msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:90 +#: lib/block_scout_web/templates/layout/app.html.eex:93 #, elixir-autogen, elixir-format msgid "- We're indexing this chain right now. Some of the counts may be inaccurate." msgstr "" @@ -122,7 +122,7 @@ msgstr "" msgid "A block producer who successfully included the block onto the blockchain." msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:97 +#: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "A confirmation email was sent to" msgstr "" @@ -2082,7 +2082,7 @@ msgid "Play" msgstr "" #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:22 -#: lib/block_scout_web/templates/layout/app.html.eex:97 +#: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "Please confirm your email address to use the My Account feature." msgstr "" @@ -2262,7 +2262,7 @@ msgstr "" msgid "Request to edit a public tag/label" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:97 +#: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "Resend verification email" msgstr "" @@ -3598,7 +3598,7 @@ msgstr "" msgid "of" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:97 +#: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "on sign up. Didn’t receive?" msgstr "" From 86d1219892c0c3673979e79257f916149fabee65 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Sun, 17 Dec 2023 21:33:17 +0300 Subject: [PATCH 186/607] Optimize NFT owner preload --- CHANGELOG.md | 1 + .../controllers/api/v2/token_controller.ex | 10 +++- .../tokens/inventory_controller.ex | 1 + .../views/api/v2/address_view.ex | 10 ++-- .../views/api/v2/token_view.ex | 35 ++------------ apps/explorer/lib/explorer/chain.ex | 25 +++++++--- .../lib/explorer/chain/token/instance.ex | 48 ++++++++++++++----- apps/explorer/test/explorer/chain_test.exs | 2 +- 8 files changed, 75 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10253eb27289..ca4cc68e89c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### Fixes - [#9039](https://github.com/blockscout/blockscout/pull/9039) - Fix tx input decoding in tx summary microservice request +- [#9015](https://github.com/blockscout/blockscout/pull/9015) - Optimize NFT owner preload - [#9013](https://github.com/blockscout/blockscout/pull/9013) - Speed up `Indexer.Fetcher.TokenInstance.LegacySanitize` - [#8969](https://github.com/blockscout/blockscout/pull/8969) - Support legacy paging options for address transaction endpoint - [#8965](https://github.com/blockscout/blockscout/pull/8965) - Set poll: false for internal transactions fetcher diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex index c9640e73722a..2e18b6649ec2 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex @@ -141,6 +141,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do results_plus_one = Chain.address_to_unique_tokens( token.contract_address_hash, + token, Keyword.merge(unique_tokens_paging_options(params), @api_true) ) @@ -163,8 +164,13 @@ defmodule BlockScoutWeb.API.V2.TokenController do {:format, {token_id, ""}} <- {:format, Integer.parse(token_id_str)} do token_instance = case Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, address_hash, @api_true) do - {:ok, token_instance} -> token_instance |> Chain.put_owner_to_token_instance(@api_true) - {:error, :not_found} -> %{token_id: token_id, metadata: nil, owner: nil} + {:ok, token_instance} -> + token_instance + |> Chain.select_repo(@api_true).preload(:owner) + |> Chain.put_owner_to_token_instance(token, @api_true) + + {:error, :not_found} -> + %{token_id: token_id, metadata: nil, owner: nil} end conn diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex index 2d09db084535..1b7f2c5bb560 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex @@ -20,6 +20,7 @@ defmodule BlockScoutWeb.Tokens.InventoryController do unique_token_instances = Chain.address_to_unique_tokens( token.contract_address_hash, + token, unique_tokens_paging_options(params) ) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex index bbfe0c077062..1b29a23d4b73 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex @@ -56,7 +56,7 @@ defmodule BlockScoutWeb.API.V2.AddressView do end def render("nft_list.json", %{token_instances: token_instances, token: token, next_page_params: next_page_params}) do - %{"items" => Enum.map(token_instances, &prepare_nft(&1, token, true)), "next_page_params" => next_page_params} + %{"items" => Enum.map(token_instances, &prepare_nft(&1, token)), "next_page_params" => next_page_params} end def render("nft_list.json", %{token_instances: token_instances, next_page_params: next_page_params}) do @@ -185,13 +185,13 @@ defmodule BlockScoutWeb.API.V2.AddressView do end defp prepare_nft(nft) do - prepare_nft(nft, nft.token, false) + prepare_nft(nft, nft.token) end - defp prepare_nft(nft, token, need_uniqueness?) do + defp prepare_nft(nft, token) do Map.merge( %{"token_type" => token.type, "value" => value(token.type, nft)}, - TokenView.prepare_token_instance(nft, token, need_uniqueness?) + TokenView.prepare_token_instance(nft, token) ) end @@ -209,7 +209,7 @@ defmodule BlockScoutWeb.API.V2.AddressView do defp prepare_nft_for_collection(token_type, instance) do Map.merge( %{"token_type" => token_type, "value" => value(token_type, instance)}, - TokenView.prepare_token_instance(instance, nil, false) + TokenView.prepare_token_instance(instance, nil) ) end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex index 681c5fa3418e..616299fde941 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex @@ -4,13 +4,9 @@ defmodule BlockScoutWeb.API.V2.TokenView do alias BlockScoutWeb.API.V2.Helper alias BlockScoutWeb.NFTHelper alias Ecto.Association.NotLoaded - alias Explorer.Chain alias Explorer.Chain.Address - alias Explorer.Chain.Address.CurrentTokenBalance alias Explorer.Chain.Token.Instance - @api_true [api?: true] - def render("token.json", %{token: nil, contract_address_hash: contract_address_hash}) do %{ "address" => Address.checksum(contract_address_hash), @@ -90,18 +86,16 @@ defmodule BlockScoutWeb.API.V2.TokenView do @doc """ Internal json rendering function """ - def prepare_token_instance(instance, token, need_uniqueness_and_owner? \\ true) do - is_unique = is_unique?(need_uniqueness_and_owner?, instance, token) - + def prepare_token_instance(instance, token) do %{ "id" => instance.token_id, "metadata" => instance.metadata, - "owner" => token_instance_owner(is_unique, instance), + "owner" => token_instance_owner(instance.is_unique, instance), "token" => render("token.json", %{token: token}), "external_app_url" => NFTHelper.external_url(instance), "animation_url" => instance.metadata && NFTHelper.retrieve_image(instance.metadata["animation_url"]), "image_url" => instance.metadata && NFTHelper.get_media_src(instance.metadata, false), - "is_unique" => is_unique + "is_unique" => instance.is_unique } end @@ -117,29 +111,6 @@ defmodule BlockScoutWeb.API.V2.TokenView do defp token_instance_owner(_is_unique, instance), do: instance.owner && Helper.address_with_info(nil, instance.owner, instance.owner.hash, false) - defp is_unique?(false, _instance, _token), do: nil - - defp is_unique?( - not_ignore?, - %Instance{current_token_balance: %CurrentTokenBalance{value: %Decimal{} = value}} = instance, - token - ) do - if Decimal.compare(value, 1) == :gt do - false - else - is_unique?(not_ignore?, %Instance{instance | current_token_balance: nil}, token) - end - end - - defp is_unique?(_not_ignore?, %Instance{current_token_balance: %CurrentTokenBalance{value: value}}, _token) - when value > 1, - do: false - - defp is_unique?(_, instance, token), - do: - not (token.type == "ERC-1155") or - Chain.token_id_1155_is_unique?(token.contract_address_hash, instance.token_id, @api_true) - defp prepare_holders_count(nil), do: nil defp prepare_holders_count(count) when count < 0, do: prepare_holders_count(0) defp prepare_holders_count(count), do: to_string(count) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 1accf987cb44..6f583f8229a3 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -4161,27 +4161,40 @@ defmodule Explorer.Chain do Repo.one!(query, timeout: :infinity) end - @spec address_to_unique_tokens(Hash.Address.t(), [paging_options | api?]) :: [Instance.t()] - def address_to_unique_tokens(contract_address_hash, options \\ []) do + @spec address_to_unique_tokens(Hash.Address.t(), Token.t(), [paging_options | api?]) :: [Instance.t()] + def address_to_unique_tokens(contract_address_hash, token, options \\ []) do paging_options = Keyword.get(options, :paging_options, @default_paging_options) contract_address_hash |> Instance.address_to_unique_token_instances() |> Instance.page_token_instance(paging_options) |> limit(^paging_options.page_size) + |> preload([_], [:owner]) |> select_repo(options).all() - |> Enum.map(&put_owner_to_token_instance(&1, options)) + |> Enum.map(&put_owner_to_token_instance(&1, token, options)) end - def put_owner_to_token_instance(%Instance{} = token_instance, options \\ []) do - owner = + def put_owner_to_token_instance(token_instance, token, options \\ []) + + def put_owner_to_token_instance(%Instance{is_unique: nil} = token_instance, token, options) do + put_owner_to_token_instance(Instance.put_is_unique(token_instance, token, options), token, options) + end + + def put_owner_to_token_instance( + %Instance{owner: nil, is_unique: true} = token_instance, + %Token{type: "ERC-1155"}, + options + ) do + owner_address_hash = token_instance |> Instance.owner_query() |> select_repo(options).one() - %{token_instance | owner: owner} + %{token_instance | owner: select_repo(options).get_by(Address, hash: owner_address_hash)} end + def put_owner_to_token_instance(%Instance{} = token_instance, _token, _options), do: token_instance + @spec data() :: Dataloader.Ecto.t() def data, do: DataloaderEcto.new(Repo) diff --git a/apps/explorer/lib/explorer/chain/token/instance.ex b/apps/explorer/lib/explorer/chain/token/instance.ex index 161029f4cb88..d095fe3c35a0 100644 --- a/apps/explorer/lib/explorer/chain/token/instance.ex +++ b/apps/explorer/lib/explorer/chain/token/instance.ex @@ -6,7 +6,7 @@ defmodule Explorer.Chain.Token.Instance do use Explorer.Schema alias Explorer.{Chain, Helper} - alias Explorer.Chain.{Address, Block, Hash, Token, TokenTransfer} + alias Explorer.Chain.{Address, Block, Hash, Token} alias Explorer.Chain.Address.CurrentTokenBalance alias Explorer.Chain.Token.Instance alias Explorer.PagingOptions @@ -26,7 +26,8 @@ defmodule Explorer.Chain.Token.Instance do owner_address_hash: Hash.Address.t(), owner_updated_at_block: Block.block_number(), owner_updated_at_log_index: non_neg_integer(), - current_token_balance: any() + current_token_balance: any(), + is_unique: bool() | nil } @primary_key false @@ -37,6 +38,7 @@ defmodule Explorer.Chain.Token.Instance do field(:owner_updated_at_block, :integer) field(:owner_updated_at_log_index, :integer) field(:current_token_balance, :any, virtual: true) + field(:is_unique, :boolean, virtual: true) belongs_to(:owner, Address, foreign_key: :owner_address_hash, references: :hash, type: Hash.Address) @@ -95,16 +97,13 @@ defmodule Explorer.Chain.Token.Instance do def page_token_instance(query, _), do: query def owner_query(%Instance{token_contract_address_hash: token_contract_address_hash, token_id: token_id}) do - from( - tt in TokenTransfer, - join: to_address in assoc(tt, :to_address), - where: - tt.token_contract_address_hash == ^token_contract_address_hash and - fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^token_id), - order_by: [desc: tt.block_number], - limit: 1, - select: to_address + CurrentTokenBalance + |> where( + [ctb], + ctb.token_contract_address_hash == ^token_contract_address_hash and ctb.token_id == ^token_id and ctb.value > 0 ) + |> limit(1) + |> select([ctb], ctb.address_hash) end @spec token_instance_query(non_neg_integer(), Hash.Address.t()) :: Ecto.Query.t() @@ -393,6 +392,7 @@ defmodule Explorer.Chain.Token.Instance do |> limit(^paging_options.page_size) |> page_token_instance(paging_options) |> Chain.select_repo(options).all() + |> Enum.map(&put_is_unique(&1, token, options)) end def token_instances_by_holder_address_hash(%Token{} = token, holder_address_hash, options) do @@ -412,6 +412,7 @@ defmodule Explorer.Chain.Token.Instance do |> page_token_instance(paging_options) |> select_merge([ctb: ctb], %{current_token_balance: ctb}) |> Chain.select_repo(options).all() + |> Enum.map(&put_is_unique(&1, token, options)) end @doc """ @@ -441,4 +442,29 @@ defmodule Explorer.Chain.Token.Instance do }) |> limit(^limit) end + + def put_is_unique(instance, token, options) do + %__MODULE__{instance | is_unique: is_unique?(instance, token, options)} + end + + defp is_unique?( + %Instance{current_token_balance: %CurrentTokenBalance{value: %Decimal{} = value}} = instance, + token, + options + ) do + if Decimal.compare(value, 1) == :gt do + false + else + is_unique?(%Instance{instance | current_token_balance: nil}, token, options) + end + end + + defp is_unique?(%Instance{current_token_balance: %CurrentTokenBalance{value: value}}, _token, _options) + when value > 1, + do: false + + defp is_unique?(instance, token, options), + do: + not (token.type == "ERC-1155") or + Chain.token_id_1155_is_unique?(token.contract_address_hash, instance.token_id, options) end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 39214cfe9132..5d8757488e7b 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -4356,7 +4356,7 @@ defmodule Explorer.ChainTest do unique_tokens_ids_paginated = token_contract_address.hash - |> Chain.address_to_unique_tokens(paging_options: paging_options) + |> Chain.address_to_unique_tokens(token, paging_options: paging_options) |> Enum.map(& &1.token_id) assert unique_tokens_ids_paginated == [List.first(second_page.token_ids)] From dbcdd7fafabc6df692b5d951f7c94c68b80929db Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Fri, 22 Dec 2023 16:32:52 +0300 Subject: [PATCH 187/607] Fix tests --- .../controllers/api/v2/token_controller_test.exs | 2 +- apps/explorer/lib/explorer/chain/token/instance.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs index aa1a313ecfda..daefeaa8cb85 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs @@ -1008,7 +1008,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do assert data = json_response(request, 200) assert compare_item(instance, data) - assert compare_item(transfer.to_address, data["owner"]) + assert Address.checksum(instance.owner_address_hash) == data["owner"]["hash"] end end diff --git a/apps/explorer/lib/explorer/chain/token/instance.ex b/apps/explorer/lib/explorer/chain/token/instance.ex index d095fe3c35a0..43249f7c722b 100644 --- a/apps/explorer/lib/explorer/chain/token/instance.ex +++ b/apps/explorer/lib/explorer/chain/token/instance.ex @@ -6,7 +6,7 @@ defmodule Explorer.Chain.Token.Instance do use Explorer.Schema alias Explorer.{Chain, Helper} - alias Explorer.Chain.{Address, Block, Hash, Token} + alias Explorer.Chain.{Address, Block, Hash, Token, TokenTransfer} alias Explorer.Chain.Address.CurrentTokenBalance alias Explorer.Chain.Token.Instance alias Explorer.PagingOptions From 8fe53d8430934d211982823aea67aa50b8c0651e Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Wed, 20 Dec 2023 15:58:59 +0300 Subject: [PATCH 188/607] Handle Postgrex errors on NFT import --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/chain.ex | 8 ---- .../indexer/fetcher/token_instance/helper.ex | 45 ++++++++++++++----- .../token_instance/metadata_retriever.ex | 20 ++++----- 4 files changed, 44 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca4cc68e89c1..b69323d3c3c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### Fixes - [#9039](https://github.com/blockscout/blockscout/pull/9039) - Fix tx input decoding in tx summary microservice request +- [#9035](https://github.com/blockscout/blockscout/pull/9035) - Handle Postgrex errors on NFT import - [#9015](https://github.com/blockscout/blockscout/pull/9015) - Optimize NFT owner preload - [#9013](https://github.com/blockscout/blockscout/pull/9013) - Speed up `Indexer.Fetcher.TokenInstance.LegacySanitize` - [#8969](https://github.com/blockscout/blockscout/pull/8969) - Support legacy paging options for address transaction endpoint diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 6f583f8229a3..12099101d5a2 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -3796,14 +3796,6 @@ defmodule Explorer.Chain do ) end - @doc """ - Inserts list of token instances via upsert_token_instance/1. - """ - @spec upsert_token_instances_list([map()]) :: list() - def upsert_token_instances_list(instances) do - Enum.map(instances, &upsert_token_instance/1) - end - @doc """ Update a new `t:Token.t/0` record. diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex b/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex index e5e2256ff9eb..792f92c85ada 100644 --- a/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex +++ b/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex @@ -6,6 +6,8 @@ defmodule Indexer.Fetcher.TokenInstance.Helper do alias Explorer.SmartContract.Reader alias Indexer.Fetcher.TokenInstance.MetadataRetriever + require Logger + @cryptokitties_address_hash "0x06012c8cf97bead5deae237070f9587f8e7a266d" @token_uri "c87b56dd" @@ -107,19 +109,21 @@ defmodule Indexer.Fetcher.TokenInstance.Helper do |> Task.yield_many(:infinity) |> Enum.zip(contract_results) |> Enum.map(fn {{_task, res}, {_result, _normalized_token_id, contract_address_hash, token_id}} -> - case res do - {:ok, result} -> - result_to_insert_params(result, contract_address_hash, token_id) - - {:exit, reason} -> - result_to_insert_params( - {:error, MetadataRetriever.truncate_error("Terminated:" <> inspect(reason))}, - contract_address_hash, - token_id - ) - end + insert_params = + case res do + {:ok, result} -> + result_to_insert_params(result, contract_address_hash, token_id) + + {:exit, reason} -> + result_to_insert_params( + {:error, MetadataRetriever.truncate_error("Terminated:" <> inspect(reason))}, + contract_address_hash, + token_id + ) + end + + upsert_with_rescue(insert_params, token_id, contract_address_hash) end) - |> Chain.upsert_token_instances_list() end defp prepare_token_id(%Decimal{} = token_id), do: Decimal.to_integer(token_id) @@ -170,4 +174,21 @@ defmodule Indexer.Fetcher.TokenInstance.Helper do error: error } end + + defp upsert_with_rescue(insert_params, token_id, token_contract_address_hash, retrying? \\ false) do + Chain.upsert_token_instance(insert_params) + rescue + error in Postgrex.Error -> + if retrying? do + Logger.warn(["Failed to upsert token instance: #{inspect(error)}"], fetcher: :token_instances) + nil + else + token_id + |> token_instance_map_with_error( + token_contract_address_hash, + MetadataRetriever.truncate_error(inspect(error.postgres.code)) + ) + |> upsert_with_rescue(token_id, token_contract_address_hash, true) + end + end end diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex b/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex index 191d81a15d4e..a4812fb332a5 100644 --- a/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex +++ b/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex @@ -53,7 +53,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do if error =~ "execution reverted" or error =~ @vm_execution_error do {:error, @vm_execution_error} else - Logger.debug(["Unknown metadata format error #{inspect(error)}."], fetcher: :token_instances) + Logger.warn(["Unknown metadata format error #{inspect(error)}."], fetcher: :token_instances) # truncate error since it will be stored in DB {:error, truncate_error(error)} @@ -65,7 +65,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do if String.length(result) == 46 do fetch_json_from_uri({:ok, [ipfs_link() <> result]}, hex_token_id) else - Logger.debug(["Unknown metadata format result #{inspect(result)}."], fetcher: :token_instances) + Logger.warn(["Unknown metadata format result #{inspect(result)}."], fetcher: :token_instances) {:error, truncate_error(result)} end @@ -90,7 +90,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do fetch_json_from_uri({:ok, [decoded_json]}, hex_token_id) rescue e -> - Logger.debug(["Unknown metadata format #{inspect(json)}.", Exception.format(:error, e, __STACKTRACE__)], + Logger.warn(["Unknown metadata format #{inspect(json)}.", Exception.format(:error, e, __STACKTRACE__)], fetcher: :token_instances ) @@ -107,7 +107,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do end rescue e -> - Logger.debug( + Logger.warn( [ "Unknown metadata format base64 #{inspect(base64_encoded_json)}.", Exception.format(:error, e, __STACKTRACE__) @@ -136,7 +136,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do check_type(json, hex_token_id) rescue e -> - Logger.debug(["Unknown metadata format #{inspect(json)}.", Exception.format(:error, e, __STACKTRACE__)], + Logger.warn(["Unknown metadata format #{inspect(json)}.", Exception.format(:error, e, __STACKTRACE__)], fetcher: :token_instances ) @@ -144,7 +144,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do end defp fetch_json_from_uri(uri, _hex_token_id) do - Logger.debug(["Unknown metadata uri format #{inspect(uri)}."], fetcher: :token_instances) + Logger.warn(["Unknown metadata uri format #{inspect(uri)}."], fetcher: :token_instances) {:error, "unknown metadata uri format"} end @@ -159,7 +159,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do fetch_metadata_from_uri(prepared_uri, hex_token_id) rescue e -> - Logger.debug( + Logger.warn( ["Could not prepare token uri #{inspect(uri)}.", Exception.format(:error, e, __STACKTRACE__)], fetcher: :token_instances ) @@ -189,7 +189,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do check_content_type(content_type, uri, hex_token_id, body) {:ok, %Response{body: body, status_code: code}} -> - Logger.debug( + Logger.warn( ["Request to token uri: #{inspect(uri)} failed with code #{code}. Body:", inspect(body)], fetcher: :token_instances ) @@ -197,7 +197,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do {:error_code, code} {:error, %Error{reason: reason}} -> - Logger.debug( + Logger.warn( ["Request to token uri failed: #{inspect(uri)}.", inspect(reason)], fetcher: :token_instances ) @@ -206,7 +206,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do end rescue e -> - Logger.debug( + Logger.warn( ["Could not send request to token uri #{inspect(uri)}.", Exception.format(:error, e, __STACKTRACE__)], fetcher: :token_instances ) From 23b2805f03fa00124ab1c9b521c6ac5fd6a87098 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Fri, 22 Dec 2023 19:18:14 +0600 Subject: [PATCH 189/607] Clear cache in GA --- .github/workflows/config.yml | 26 +++++++++---------- .../lib/indexer/fetcher/block_reward.ex | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 5efcd092a62e..77a769a2b684 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -72,7 +72,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps- @@ -130,7 +130,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -154,7 +154,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -183,7 +183,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -227,7 +227,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -253,7 +253,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -282,7 +282,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -330,7 +330,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -376,7 +376,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -438,7 +438,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -498,7 +498,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -569,7 +569,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -637,7 +637,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_30-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" diff --git a/apps/indexer/lib/indexer/fetcher/block_reward.ex b/apps/indexer/lib/indexer/fetcher/block_reward.ex index 78ba9ec9f3df..e179538b2ccf 100644 --- a/apps/indexer/lib/indexer/fetcher/block_reward.ex +++ b/apps/indexer/lib/indexer/fetcher/block_reward.ex @@ -21,7 +21,7 @@ defmodule Indexer.Fetcher.BlockReward do alias Indexer.{BufferedTask, Tracer} alias Indexer.Fetcher.BlockReward.Supervisor, as: BlockRewardSupervisor alias Indexer.Fetcher.CoinBalance - alias Indexer.Transform.{AddressCoinBalances, AddressCoinBalancesDaily, Addresses} + alias Indexer.Transform.{AddressCoinBalances, Addresses} @behaviour BufferedTask From 2b5f7fd5adba4a8fe417f659a0a83462b788d728 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 22 Dec 2023 17:20:28 +0300 Subject: [PATCH 190/607] v5.4.0 --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/workflows/prerelease.yml | 2 +- .../workflows/publish-docker-image-every-push.yml | 2 +- .github/workflows/publish-docker-image-for-core.yml | 2 +- .../publish-docker-image-for-eth-goerli.yml | 2 +- .../publish-docker-image-for-eth-sepolia.yml | 2 +- .github/workflows/publish-docker-image-for-eth.yml | 2 +- .github/workflows/publish-docker-image-for-fuse.yml | 2 +- .../publish-docker-image-for-immutable.yml | 2 +- .../publish-docker-image-for-l2-staging.yml | 2 +- .../workflows/publish-docker-image-for-lukso.yml | 2 +- .../workflows/publish-docker-image-for-optimism.yml | 2 +- .../publish-docker-image-for-polygon-edge.yml | 2 +- .github/workflows/publish-docker-image-for-rsk.yml | 2 +- .../publish-docker-image-for-stability.yml | 2 +- .../workflows/publish-docker-image-for-suave.yml | 2 +- .github/workflows/publish-docker-image-for-xdai.yml | 2 +- .../workflows/publish-docker-image-for-zkevm.yml | 2 +- .../workflows/publish-docker-image-for-zksync.yml | 2 +- .../publish-docker-image-staging-on-demand.yml | 2 +- .github/workflows/release-additional.yml | 2 +- .github/workflows/release.yml | 2 +- CHANGELOG.md | 13 +++++++++++++ apps/block_scout_web/mix.exs | 2 +- apps/ethereum_jsonrpc/mix.exs | 2 +- apps/explorer/mix.exs | 2 +- apps/indexer/mix.exs | 2 +- docker-compose/docker-compose.yml | 2 +- docker/Makefile | 2 +- mix.exs | 2 +- rel/config.exs | 2 +- 31 files changed, 43 insertions(+), 30 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 00b3285f4567..4f35712cae63 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -65,7 +65,7 @@ body: attributes: label: Backend version description: The release version of the backend or branch/commit. - placeholder: v5.3.3 + placeholder: v5.4.0 validations: required: true diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index d3f7579dc033..3f97613ee0d7 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -16,7 +16,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 steps: - name: Check out the repo uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-every-push.yml b/.github/workflows/publish-docker-image-every-push.yml index bd66d1110256..10d2cfabc9ff 100644 --- a/.github/workflows/publish-docker-image-every-push.yml +++ b/.github/workflows/publish-docker-image-every-push.yml @@ -11,7 +11,7 @@ on: env: OTP_VERSION: '25.2.1' ELIXIR_VERSION: '1.14.5' - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 jobs: push_to_registry: diff --git a/.github/workflows/publish-docker-image-for-core.yml b/.github/workflows/publish-docker-image-for-core.yml index 158e5c015154..0c76d126208f 100644 --- a/.github/workflows/publish-docker-image-for-core.yml +++ b/.github/workflows/publish-docker-image-for-core.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 DOCKER_CHAIN_NAME: poa steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-eth-goerli.yml b/.github/workflows/publish-docker-image-for-eth-goerli.yml index 20db375a476a..1911bbb35795 100644 --- a/.github/workflows/publish-docker-image-for-eth-goerli.yml +++ b/.github/workflows/publish-docker-image-for-eth-goerli.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 DOCKER_CHAIN_NAME: eth-goerli steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-eth-sepolia.yml b/.github/workflows/publish-docker-image-for-eth-sepolia.yml index d16b1fbc9d18..7c09456adcb7 100644 --- a/.github/workflows/publish-docker-image-for-eth-sepolia.yml +++ b/.github/workflows/publish-docker-image-for-eth-sepolia.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 DOCKER_CHAIN_NAME: eth-sepolia steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-eth.yml b/.github/workflows/publish-docker-image-for-eth.yml index 0fd3120c0517..f7a0ad5fd2a2 100644 --- a/.github/workflows/publish-docker-image-for-eth.yml +++ b/.github/workflows/publish-docker-image-for-eth.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 DOCKER_CHAIN_NAME: mainnet steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-fuse.yml b/.github/workflows/publish-docker-image-for-fuse.yml index fbd5fe81e212..286664969fb6 100644 --- a/.github/workflows/publish-docker-image-for-fuse.yml +++ b/.github/workflows/publish-docker-image-for-fuse.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 DOCKER_CHAIN_NAME: fuse steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-immutable.yml b/.github/workflows/publish-docker-image-for-immutable.yml index 2fbccdf55586..1c9e5cc85af0 100644 --- a/.github/workflows/publish-docker-image-for-immutable.yml +++ b/.github/workflows/publish-docker-image-for-immutable.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 DOCKER_CHAIN_NAME: immutable steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-l2-staging.yml b/.github/workflows/publish-docker-image-for-l2-staging.yml index 744d4f4694c3..637b880a2ba5 100644 --- a/.github/workflows/publish-docker-image-for-l2-staging.yml +++ b/.github/workflows/publish-docker-image-for-l2-staging.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 DOCKER_CHAIN_NAME: optimism-l2-advanced steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-lukso.yml b/.github/workflows/publish-docker-image-for-lukso.yml index a094176964b1..4cd324babd30 100644 --- a/.github/workflows/publish-docker-image-for-lukso.yml +++ b/.github/workflows/publish-docker-image-for-lukso.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 DOCKER_CHAIN_NAME: lukso steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-optimism.yml b/.github/workflows/publish-docker-image-for-optimism.yml index c53a1edf9c3a..320ed18ba421 100644 --- a/.github/workflows/publish-docker-image-for-optimism.yml +++ b/.github/workflows/publish-docker-image-for-optimism.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 DOCKER_CHAIN_NAME: optimism steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-polygon-edge.yml b/.github/workflows/publish-docker-image-for-polygon-edge.yml index c54555d97cf8..d2153e90725a 100644 --- a/.github/workflows/publish-docker-image-for-polygon-edge.yml +++ b/.github/workflows/publish-docker-image-for-polygon-edge.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 DOCKER_CHAIN_NAME: polygon-edge steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-rsk.yml b/.github/workflows/publish-docker-image-for-rsk.yml index 324494c9eee7..3f77608f0c3d 100644 --- a/.github/workflows/publish-docker-image-for-rsk.yml +++ b/.github/workflows/publish-docker-image-for-rsk.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 DOCKER_CHAIN_NAME: rsk steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-stability.yml b/.github/workflows/publish-docker-image-for-stability.yml index f6a4bedb37b8..b81cbc13344c 100644 --- a/.github/workflows/publish-docker-image-for-stability.yml +++ b/.github/workflows/publish-docker-image-for-stability.yml @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 DOCKER_CHAIN_NAME: stability steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-suave.yml b/.github/workflows/publish-docker-image-for-suave.yml index 2e9b6fbd3ddc..8ba4ec4975ed 100644 --- a/.github/workflows/publish-docker-image-for-suave.yml +++ b/.github/workflows/publish-docker-image-for-suave.yml @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 DOCKER_CHAIN_NAME: suave steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-xdai.yml b/.github/workflows/publish-docker-image-for-xdai.yml index 7a672e81fcef..7af9b838a315 100644 --- a/.github/workflows/publish-docker-image-for-xdai.yml +++ b/.github/workflows/publish-docker-image-for-xdai.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 DOCKER_CHAIN_NAME: xdai steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-zkevm.yml b/.github/workflows/publish-docker-image-for-zkevm.yml index b73fdcda97d4..603719db11aa 100644 --- a/.github/workflows/publish-docker-image-for-zkevm.yml +++ b/.github/workflows/publish-docker-image-for-zkevm.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 DOCKER_CHAIN_NAME: zkevm steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-zksync.yml b/.github/workflows/publish-docker-image-for-zksync.yml index 8a0490c88178..aa6317647923 100644 --- a/.github/workflows/publish-docker-image-for-zksync.yml +++ b/.github/workflows/publish-docker-image-for-zksync.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 DOCKER_CHAIN_NAME: zksync steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-staging-on-demand.yml b/.github/workflows/publish-docker-image-staging-on-demand.yml index 246a4876362a..e33a3ee7c37d 100644 --- a/.github/workflows/publish-docker-image-staging-on-demand.yml +++ b/.github/workflows/publish-docker-image-staging-on-demand.yml @@ -12,7 +12,7 @@ on: env: OTP_VERSION: '25.2.1' ELIXIR_VERSION: '1.14.5' - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 jobs: push_to_registry: diff --git a/.github/workflows/release-additional.yml b/.github/workflows/release-additional.yml index 892a6b6ead38..36ff36c1aa79 100644 --- a/.github/workflows/release-additional.yml +++ b/.github/workflows/release-additional.yml @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 steps: - name: Check out the repo uses: actions/checkout@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9a16fba81e7a..cd7599014882 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 steps: - name: Check out the repo uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 939456580af7..f5169a8eedba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ ### Features +### Fixes + +### Chore + +
+ Dependencies version bumps + +
+ +## 5.4.0-beta + +### Features + - [#9018](https://github.com/blockscout/blockscout/pull/9018) - Add SmartContractRealtimeEventHandler - [#8997](https://github.com/blockscout/blockscout/pull/8997) - Isolate throttable error count by request method - [#8975](https://github.com/blockscout/blockscout/pull/8975) - Add EIP-4844 compatibility (not full support yet) diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index 18dd4fa02057..d155e0c572b8 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -23,7 +23,7 @@ defmodule BlockScoutWeb.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "5.3.3", + version: "5.4.0", xref: [exclude: [Explorer.Chain.Zkevm.Reader]] ] end diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index 9445c500133a..1da7647be613 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -23,7 +23,7 @@ defmodule EthereumJsonrpc.MixProject do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "5.3.3" + version: "5.4.0" ] end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 492b774aabcc..f24bc660c4a4 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -24,7 +24,7 @@ defmodule Explorer.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "5.3.3", + version: "5.4.0", xref: [exclude: [BlockScoutWeb.WebRouter.Helpers]] ] end diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs index fcb2d9513961..58b20d24aa4c 100644 --- a/apps/indexer/mix.exs +++ b/apps/indexer/mix.exs @@ -14,7 +14,7 @@ defmodule Indexer.MixProject do elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", start_permanent: Mix.env() == :prod, - version: "5.3.3" + version: "5.4.0" ] end diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index f700df419c25..2bbda2c94236 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -34,7 +34,7 @@ services: CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED: "" CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL: "" ADMIN_PANEL_ENABLED: "" - RELEASE_VERSION: 5.3.3 + RELEASE_VERSION: 5.4.0 links: - db:database environment: diff --git a/docker/Makefile b/docker/Makefile index 6f3039ec69ea..09e923956423 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -10,7 +10,7 @@ STATS_CONTAINER_NAME := stats STATS_DB_CONTAINER_NAME := stats-postgres PROXY_CONTAINER_NAME := proxy PG_CONTAINER_NAME := postgres -RELEASE_VERSION ?= '5.3.3' +RELEASE_VERSION ?= '5.4.0' TAG := $(RELEASE_VERSION)-commit-$(shell git log -1 --pretty=format:"%h") STABLE_TAG := $(RELEASE_VERSION) diff --git a/mix.exs b/mix.exs index 61b159e2c407..f144b85ac3e7 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule BlockScout.Mixfile do [ # app: :block_scout, # aliases: aliases(config_env()), - version: "5.3.3", + version: "5.4.0", apps_path: "apps", deps: deps(), dialyzer: dialyzer(), diff --git a/rel/config.exs b/rel/config.exs index 62745d29f472..aef4c2f2d18e 100644 --- a/rel/config.exs +++ b/rel/config.exs @@ -71,7 +71,7 @@ end # will be used by default release :blockscout do - set version: "5.3.3-beta" + set version: "5.4.0-beta" set applications: [ :runtime_tools, block_scout_web: :permanent, From f4bb4a4d2b1755cd974723491756aa14d741c524 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Dec 2023 18:52:20 +0000 Subject: [PATCH 191/607] Bump benchee from 1.2.0 to 1.3.0 Bumps [benchee](https://github.com/bencheeorg/benchee) from 1.2.0 to 1.3.0. - [Release notes](https://github.com/bencheeorg/benchee/releases) - [Changelog](https://github.com/bencheeorg/benchee/blob/main/CHANGELOG.md) - [Commits](https://github.com/bencheeorg/benchee/compare/1.2.0...1.3.0) --- updated-dependencies: - dependency-name: benchee dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/explorer/mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index f24bc660c4a4..a4195751ef8d 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -61,7 +61,7 @@ defmodule Explorer.Mixfile do {:mime, "~> 2.0"}, {:bcrypt_elixir, "~> 3.0"}, # benchmark optimizations - {:benchee, "~> 1.2.0", only: :test}, + {:benchee, "~> 1.3.0", only: :test}, # CSV output for benchee {:benchee_csv, "~> 1.0.0", only: :test}, {:bypass, "~> 2.1", only: :test}, diff --git a/mix.lock b/mix.lock index e27ab0a3cf80..be7161e17e3d 100644 --- a/mix.lock +++ b/mix.lock @@ -6,7 +6,7 @@ "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"}, "bamboo": {:hex, :bamboo, "2.3.0", "d2392a2cabe91edf488553d3c70638b532e8db7b76b84b0a39e3dfe492ffd6fc", [:mix], [{:hackney, ">= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.4 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "dd0037e68e108fd04d0e8773921512c940e35d981e097b5793543e3b2f9cd3f6"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.1.0", "0b110a9a6c619b19a7f73fa3004aa11d6e719a67e672d1633dc36b6b2290a0f7", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2ad2acb5a8bc049e8d5aa267802631912bb80d5f4110a178ae7999e69dca1bf7"}, - "benchee": {:hex, :benchee, "1.2.0", "afd2f0caec06ce3a70d9c91c514c0b58114636db9d83c2dc6bfd416656618353", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "ee729e53217898b8fd30aaad3cce61973dab61574ae6f48229fe7ff42d5e4457"}, + "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "benchee_csv": {:hex, :benchee_csv, "1.0.0", "0b3b9223290bfcb8003552705bec9bcf1a89b4a83b70bd686e45295c264f3d16", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:csv, "~> 2.0", [hex: :csv, repo: "hexpm", optional: false]}], "hexpm", "cdefb804c021dcf7a99199492026584be9b5a21d6644ac0d01c81c5d97c520d5"}, "briefly": {:git, "https://github.com/CargoSense/briefly.git", "51dfe7fbe0f897ea2a921d9af120762392aca6a1", []}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, From 8b0f81bf99c1c24b4781b06fef1b09a86ac9216b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Dec 2023 18:56:24 +0000 Subject: [PATCH 192/607] Bump redux from 5.0.0 to 5.0.1 in /apps/block_scout_web/assets Bumps [redux](https://github.com/reduxjs/redux) from 5.0.0 to 5.0.1. - [Release notes](https://github.com/reduxjs/redux/releases) - [Changelog](https://github.com/reduxjs/redux/blob/master/CHANGELOG.md) - [Commits](https://github.com/reduxjs/redux/compare/v5.0.0...v5.0.1) --- updated-dependencies: - dependency-name: redux dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 6185161016d4..be5cb099b4cd 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -58,7 +58,7 @@ "pikaday": "^1.8.2", "popper.js": "^1.14.7", "reduce-reducers": "^1.0.4", - "redux": "^5.0.0", + "redux": "^5.0.1", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", "sweetalert2": "^11.10.1", @@ -14801,9 +14801,9 @@ "integrity": "sha512-Mb2WZ2bJF597exiqX7owBzrqJ74DHLK3yOQjCyPAaNifRncE8OD0wFIuoMhXxTnHK07+8zZ2SJEKy/qtiyR7vw==" }, "node_modules/redux": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.0.tgz", - "integrity": "sha512-blLIYmYetpZMET6Q6uCY7Jtl/Im5OBldy+vNPauA8vvsdqyt66oep4EUpAMWNHauTC6xa9JuRPhRB72rY82QGA==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" }, "node_modules/regenerate": { "version": "1.4.2", @@ -28939,9 +28939,9 @@ "integrity": "sha512-Mb2WZ2bJF597exiqX7owBzrqJ74DHLK3yOQjCyPAaNifRncE8OD0wFIuoMhXxTnHK07+8zZ2SJEKy/qtiyR7vw==" }, "redux": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.0.tgz", - "integrity": "sha512-blLIYmYetpZMET6Q6uCY7Jtl/Im5OBldy+vNPauA8vvsdqyt66oep4EUpAMWNHauTC6xa9JuRPhRB72rY82QGA==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" }, "regenerate": { "version": "1.4.2", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index b71c98820dc8..f64c02d8f4ee 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -70,7 +70,7 @@ "pikaday": "^1.8.2", "popper.js": "^1.14.7", "reduce-reducers": "^1.0.4", - "redux": "^5.0.0", + "redux": "^5.0.1", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", "sweetalert2": "^11.10.1", From 5caad28029b4d5382d62fc829558da3ab03b787e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Dec 2023 18:56:51 +0000 Subject: [PATCH 193/607] Bump @amplitude/analytics-browser in /apps/block_scout_web/assets Bumps [@amplitude/analytics-browser](https://github.com/amplitude/Amplitude-TypeScript) from 2.3.7 to 2.3.8. - [Release notes](https://github.com/amplitude/Amplitude-TypeScript/releases) - [Commits](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@2.3.7...@amplitude/analytics-browser@2.3.8) --- updated-dependencies: - dependency-name: "@amplitude/analytics-browser" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 94 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 6185161016d4..5bcc13f6a581 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -7,7 +7,7 @@ "name": "blockscout", "license": "GPL-3.0", "dependencies": { - "@amplitude/analytics-browser": "^2.3.7", + "@amplitude/analytics-browser": "^2.3.8", "@fortawesome/fontawesome-free": "^6.5.1", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", @@ -116,15 +116,15 @@ } }, "node_modules/@amplitude/analytics-browser": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.7.tgz", - "integrity": "sha512-dk9Q8/pSxM8zyzT5jmWNuDZPicKXK3JQu/1s6ztWD8lWN/vxSIYWaFAdaz60/AM7jkOwxdP8iANMxMyxPv06Rw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.8.tgz", + "integrity": "sha512-K+12aAVJPzAtWIi8Ok5Q5dvg7v7IF4G0cI8PW0COWo3uTyY103r45OcpgrpRVpVAr+41d1eiMo36jqOke89uPA==", "dependencies": { - "@amplitude/analytics-client-common": "^2.0.9", - "@amplitude/analytics-core": "^2.1.2", + "@amplitude/analytics-client-common": "^2.0.10", + "@amplitude/analytics-core": "^2.1.3", "@amplitude/analytics-types": "^2.3.1", - "@amplitude/plugin-page-view-tracking-browser": "^2.0.17", - "@amplitude/plugin-web-attribution-browser": "^2.0.17", + "@amplitude/plugin-page-view-tracking-browser": "^2.0.18", + "@amplitude/plugin-web-attribution-browser": "^2.0.18", "tslib": "^2.4.1" } }, @@ -134,12 +134,12 @@ "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" }, "node_modules/@amplitude/analytics-client-common": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.9.tgz", - "integrity": "sha512-2CSUMY60Jemks/XVro8ikuHBebENzb+IxnUz4YPx5fmBPW7QW+ysmD+LgECeLhv9jWIQoDhuciscPLuRENKerA==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.10.tgz", + "integrity": "sha512-IaERGgBN3dmCGFbFd7SFTpTBguJIQzE/uDK44KEnLj0qw9wdoTxpLhoXQpqe5WKWsr46eONL9ROCJybHs4Efnw==", "dependencies": { "@amplitude/analytics-connector": "^1.4.8", - "@amplitude/analytics-core": "^2.1.2", + "@amplitude/analytics-core": "^2.1.3", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" } @@ -155,9 +155,9 @@ "integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g==" }, "node_modules/@amplitude/analytics-core": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.2.tgz", - "integrity": "sha512-vRkP2HHh43u7vilpUrP3Q8o/TMJ6U2B7nxPV9/aUS5V/im0Zigq4mPcn5uNY7FuTwsJM3zzRxK9J2WvfdG5bqA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.3.tgz", + "integrity": "sha512-WHXf9g33t63jYy4a/1uOpq/zHPMfEj5N2HHgJrg7Eu7v4w3kOWtPSMPBAllzFWxC5Ay5HeR9n0hqlJG0yffQWg==", "dependencies": { "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" @@ -174,11 +174,11 @@ "integrity": "sha512-yojBG20qvph0rpCJKb4i/FJa+otqLINEwv//hfzvjnCOcPPyS0YscI8oiRBM0rG7kZIDgaL9a6jPwkqK4ACmcw==" }, "node_modules/@amplitude/plugin-page-view-tracking-browser": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.17.tgz", - "integrity": "sha512-AcpNehWJRPNFiyLDbji4ccZ0jD8640EL3XbauFE8IVSsp+a0a8YbGda/5uQrjmepDGC7nS8BIxxBDiIjbDD90g==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.18.tgz", + "integrity": "sha512-s7PkGOgrx6U06/emzM8k+KRGDuyP9Z2L4OyGdeQwJcURJjiZDVQsmKlTZ5/SeGvxHYgq/4QJYUmMSmzByiGTCA==", "dependencies": { - "@amplitude/analytics-client-common": "^2.0.9", + "@amplitude/analytics-client-common": "^2.0.10", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" } @@ -189,12 +189,12 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@amplitude/plugin-web-attribution-browser": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.17.tgz", - "integrity": "sha512-sdO3MY3QkHOumMfm6shDOFAdV7Kf5VDsuYnyCn4g3F68e3PiKhrSgpjiqLuB0I6PVNBtkFLjTPxafNdHYsGCZQ==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.18.tgz", + "integrity": "sha512-mPlXu0fEYCCXhT6WpNJoM0FYkp+HIEm7L+KBEa2IHd3GD3+mh2AVDkZmgXLl3LKb++HY8mCiqC5/NcJ2AzTNhA==", "dependencies": { - "@amplitude/analytics-client-common": "^2.0.9", - "@amplitude/analytics-core": "^2.1.2", + "@amplitude/analytics-client-common": "^2.0.10", + "@amplitude/analytics-core": "^2.1.3", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" } @@ -17852,15 +17852,15 @@ "dev": true }, "@amplitude/analytics-browser": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.7.tgz", - "integrity": "sha512-dk9Q8/pSxM8zyzT5jmWNuDZPicKXK3JQu/1s6ztWD8lWN/vxSIYWaFAdaz60/AM7jkOwxdP8iANMxMyxPv06Rw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.8.tgz", + "integrity": "sha512-K+12aAVJPzAtWIi8Ok5Q5dvg7v7IF4G0cI8PW0COWo3uTyY103r45OcpgrpRVpVAr+41d1eiMo36jqOke89uPA==", "requires": { - "@amplitude/analytics-client-common": "^2.0.9", - "@amplitude/analytics-core": "^2.1.2", + "@amplitude/analytics-client-common": "^2.0.10", + "@amplitude/analytics-core": "^2.1.3", "@amplitude/analytics-types": "^2.3.1", - "@amplitude/plugin-page-view-tracking-browser": "^2.0.17", - "@amplitude/plugin-web-attribution-browser": "^2.0.17", + "@amplitude/plugin-page-view-tracking-browser": "^2.0.18", + "@amplitude/plugin-web-attribution-browser": "^2.0.18", "tslib": "^2.4.1" }, "dependencies": { @@ -17872,12 +17872,12 @@ } }, "@amplitude/analytics-client-common": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.9.tgz", - "integrity": "sha512-2CSUMY60Jemks/XVro8ikuHBebENzb+IxnUz4YPx5fmBPW7QW+ysmD+LgECeLhv9jWIQoDhuciscPLuRENKerA==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.10.tgz", + "integrity": "sha512-IaERGgBN3dmCGFbFd7SFTpTBguJIQzE/uDK44KEnLj0qw9wdoTxpLhoXQpqe5WKWsr46eONL9ROCJybHs4Efnw==", "requires": { "@amplitude/analytics-connector": "^1.4.8", - "@amplitude/analytics-core": "^2.1.2", + "@amplitude/analytics-core": "^2.1.3", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" }, @@ -17895,9 +17895,9 @@ "integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g==" }, "@amplitude/analytics-core": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.2.tgz", - "integrity": "sha512-vRkP2HHh43u7vilpUrP3Q8o/TMJ6U2B7nxPV9/aUS5V/im0Zigq4mPcn5uNY7FuTwsJM3zzRxK9J2WvfdG5bqA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.3.tgz", + "integrity": "sha512-WHXf9g33t63jYy4a/1uOpq/zHPMfEj5N2HHgJrg7Eu7v4w3kOWtPSMPBAllzFWxC5Ay5HeR9n0hqlJG0yffQWg==", "requires": { "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" @@ -17916,11 +17916,11 @@ "integrity": "sha512-yojBG20qvph0rpCJKb4i/FJa+otqLINEwv//hfzvjnCOcPPyS0YscI8oiRBM0rG7kZIDgaL9a6jPwkqK4ACmcw==" }, "@amplitude/plugin-page-view-tracking-browser": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.17.tgz", - "integrity": "sha512-AcpNehWJRPNFiyLDbji4ccZ0jD8640EL3XbauFE8IVSsp+a0a8YbGda/5uQrjmepDGC7nS8BIxxBDiIjbDD90g==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.18.tgz", + "integrity": "sha512-s7PkGOgrx6U06/emzM8k+KRGDuyP9Z2L4OyGdeQwJcURJjiZDVQsmKlTZ5/SeGvxHYgq/4QJYUmMSmzByiGTCA==", "requires": { - "@amplitude/analytics-client-common": "^2.0.9", + "@amplitude/analytics-client-common": "^2.0.10", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" }, @@ -17933,12 +17933,12 @@ } }, "@amplitude/plugin-web-attribution-browser": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.17.tgz", - "integrity": "sha512-sdO3MY3QkHOumMfm6shDOFAdV7Kf5VDsuYnyCn4g3F68e3PiKhrSgpjiqLuB0I6PVNBtkFLjTPxafNdHYsGCZQ==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.18.tgz", + "integrity": "sha512-mPlXu0fEYCCXhT6WpNJoM0FYkp+HIEm7L+KBEa2IHd3GD3+mh2AVDkZmgXLl3LKb++HY8mCiqC5/NcJ2AzTNhA==", "requires": { - "@amplitude/analytics-client-common": "^2.0.9", - "@amplitude/analytics-core": "^2.1.2", + "@amplitude/analytics-client-common": "^2.0.10", + "@amplitude/analytics-core": "^2.1.3", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" }, diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index b71c98820dc8..3187739d745a 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^6.5.1", - "@amplitude/analytics-browser": "^2.3.7", + "@amplitude/analytics-browser": "^2.3.8", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", "assert": "^2.1.0", From dd0a6751c41ab46a65e15c2abea3a7b9979efa67 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 26 Dec 2023 11:58:01 +0300 Subject: [PATCH 194/607] Arbitrum allow tx receipt gasUsedForL1 --- CHANGELOG.md | 2 ++ apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5169a8eedba..480901a0b50e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Fixes +- [#9061](https://github.com/blockscout/blockscout/pull/9061) - Arbitrum allow tx receipt gasUsedForL1 field + ### Chore
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex index 8dc9b2f82edc..08744cd2cd39 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex @@ -300,7 +300,7 @@ defmodule EthereumJSONRPC.Receipt do end # Arbitrum fields - defp entry_to_elixir({key, _}) when key in ~w(returnData returnCode feeStats l1BlockNumber) do + defp entry_to_elixir({key, _}) when key in ~w(returnData returnCode feeStats l1BlockNumber gasUsedForL1) do :ignore end From c6acfa0668b4720018857ca3c59e2c03d4e77f5e Mon Sep 17 00:00:00 2001 From: Lymarenko Lev Date: Tue, 26 Dec 2023 15:49:47 +0700 Subject: [PATCH 195/607] Fix bens variables to snake_case and change from POST to GET --- .../explorer/microservice_interfaces/bens.ex | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex index 186f599b127c..afa77e7eeca7 100644 --- a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -54,32 +54,32 @@ defmodule Explorer.MicroserviceInterfaces.BENS do @spec address_lookup(binary()) :: {:error, :disabled | binary() | Jason.DecodeError.t()} | {:ok, any} def address_lookup(address) do with :ok <- Microservice.check_enabled(__MODULE__) do - body = %{ + query_params = %{ "address" => to_string(address), - "resolvedTo" => true, - "ownedBy" => false, - "onlyActive" => true, + "resolved_to" => true, + "owned_by" => false, + "only_active" => true, "order" => "ASC" } - http_post_request(address_lookup_url(), body) + http_get_request(address_lookup_url(), query_params) end end @doc """ - Lookup for ENS domain name via {{baseUrl}}/api/v1/:chainId/domains:lookup + Lookup for ENS domain name via GET {{baseUrl}}/api/v1/:chainId/domains:lookup """ @spec ens_domain_lookup(binary()) :: {:error, :disabled | binary() | Jason.DecodeError.t()} | {:ok, any} def ens_domain_lookup(domain) do with :ok <- Microservice.check_enabled(__MODULE__) do - body = %{ + query_params = %{ "name" => domain, - "onlyActive" => true, + "only_active" => true, "sort" => "registration_date", "order" => "DESC" } - http_post_request(domain_lookup_url(), body) + http_get_request(domain_lookup_url(), query_params) end end @@ -106,6 +106,27 @@ defmodule Explorer.MicroserviceInterfaces.BENS do end end + def http_get_request(url, query_params) do + case HTTPoison.get("#{url}?#{URI.encode_query(query_params)}") do + {:ok, %Response{body: body, status_code: 200}} -> + Jason.decode(body) + + {_, error} -> + old_truncate = Application.get_env(:logger, :truncate) + Logger.configure(truncate: :infinity) + + Logger.error(fn -> + [ + "Error while sending request to BENS microservice url: #{url}: ", + inspect(error, limit: :infinity, printable_limit: :infinity) + ] + end) + + Logger.configure(truncate: old_truncate) + {:error, @request_error_msg} + end + end + @spec enabled?() :: boolean def enabled?, do: Application.get_env(:explorer, __MODULE__)[:enabled] @@ -221,7 +242,7 @@ defmodule Explorer.MicroserviceInterfaces.BENS do %{ "items" => [ - %{"name" => name, "expiryDate" => expiry_date, "resolvedAddress" => %{"hash" => address_hash_string}} + %{"name" => name, "expiry_date" => expiry_date, "resolved_address" => %{"hash" => address_hash_string}} | _other ] = items }} From 31e48a4c710adb1f542a1b5ffcdfae35c18444a3 Mon Sep 17 00:00:00 2001 From: Lymarenko Lev Date: Tue, 26 Dec 2023 16:38:33 +0700 Subject: [PATCH 196/607] Fix token_transfers bug --- .../lib/explorer/microservice_interfaces/bens.ex | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex index afa77e7eeca7..454af2a3ec9d 100644 --- a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -270,9 +270,17 @@ defmodule Explorer.MicroserviceInterfaces.BENS do defp item_to_address_hash_strings(%Transaction{ to_address_hash: to_address_hash, created_contract_address_hash: nil, - from_address_hash: from_address_hash + from_address_hash: from_address_hash, + token_transfers: token_transfers, }) do - [to_string(to_address_hash), to_string(from_address_hash)] + token_transfers_addresses = List.flatten(Enum.map(token_transfers, &item_to_address_hash_strings/1)) + Logger.info(fn -> + [ + "aboba", + inspect(token_transfers_addresses) + ] + end) + [to_string(to_address_hash), to_string(from_address_hash)] ++ token_transfers_addresses end defp item_to_address_hash_strings(%TokenTransfer{ @@ -329,7 +337,8 @@ defmodule Explorer.MicroserviceInterfaces.BENS do tx | to_address: alter_address(tx.to_address, to_address_hash, names), created_contract_address: alter_address(tx.created_contract_address, created_contract_address_hash, names), - from_address: alter_address(tx.from_address, from_address_hash, names) + from_address: alter_address(tx.from_address, from_address_hash, names), + token_transfers: Enum.map(tx.token_transfers, &put_ens_name_to_item(&1, names)) } end From d1ee2e78e6f561933a5125845e0083170edaa3da Mon Sep 17 00:00:00 2001 From: Lymarenko Lev Date: Tue, 26 Dec 2023 16:56:14 +0700 Subject: [PATCH 197/607] Add #9062 changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 480901a0b50e..f4f63d99195c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Fixes +- [#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 ### Chore From 684c4a0c15899146175a32317a60555d9fb675f9 Mon Sep 17 00:00:00 2001 From: Lymarenko Lev Date: Tue, 26 Dec 2023 16:56:35 +0700 Subject: [PATCH 198/607] mix format --- apps/explorer/lib/explorer/microservice_interfaces/bens.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex index 454af2a3ec9d..fd30124e461d 100644 --- a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -271,15 +271,17 @@ defmodule Explorer.MicroserviceInterfaces.BENS do to_address_hash: to_address_hash, created_contract_address_hash: nil, from_address_hash: from_address_hash, - token_transfers: token_transfers, + token_transfers: token_transfers }) do token_transfers_addresses = List.flatten(Enum.map(token_transfers, &item_to_address_hash_strings/1)) + Logger.info(fn -> [ "aboba", inspect(token_transfers_addresses) ] end) + [to_string(to_address_hash), to_string(from_address_hash)] ++ token_transfers_addresses end From 98385a5c8d2c78ed192c2bc9ee64a5c48537b28c Mon Sep 17 00:00:00 2001 From: Lymarenko Lev Date: Tue, 26 Dec 2023 16:58:17 +0700 Subject: [PATCH 199/607] Remove print debug --- .../explorer/lib/explorer/microservice_interfaces/bens.ex | 8 -------- 1 file changed, 8 deletions(-) diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex index fd30124e461d..9f6e450787b2 100644 --- a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -274,14 +274,6 @@ defmodule Explorer.MicroserviceInterfaces.BENS do token_transfers: token_transfers }) do token_transfers_addresses = List.flatten(Enum.map(token_transfers, &item_to_address_hash_strings/1)) - - Logger.info(fn -> - [ - "aboba", - inspect(token_transfers_addresses) - ] - end) - [to_string(to_address_hash), to_string(from_address_hash)] ++ token_transfers_addresses end From 0db3a3173f0e2365c2c86e4fbbdc17d3f8588a72 Mon Sep 17 00:00:00 2001 From: Lymarenko Lev Date: Tue, 26 Dec 2023 17:01:42 +0700 Subject: [PATCH 200/607] Fix comment --- apps/explorer/lib/explorer/microservice_interfaces/bens.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex index 9f6e450787b2..02e42b24cfa1 100644 --- a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -35,7 +35,7 @@ defmodule Explorer.MicroserviceInterfaces.BENS do | Withdrawal.t() @doc """ - Batch request for ENS names via {{baseUrl}}/api/v1/:chainId/addresses:batch-resolve-names + Batch request for ENS names via POST {{baseUrl}}/api/v1/:chainId/addresses:batch-resolve-names """ @spec ens_names_batch_request([binary()]) :: {:error, :disabled | binary() | Jason.DecodeError.t()} | {:ok, any} def ens_names_batch_request(addresses) do @@ -49,7 +49,7 @@ defmodule Explorer.MicroserviceInterfaces.BENS do end @doc """ - Request for ENS name via {{baseUrl}}/api/v1/:chainId/addresses:lookup + Request for ENS name via GET {{baseUrl}}/api/v1/:chainId/addresses:lookup """ @spec address_lookup(binary()) :: {:error, :disabled | binary() | Jason.DecodeError.t()} | {:ok, any} def address_lookup(address) do From 1ba76320a3f64a8c25dcc7568f1e8d379def84f8 Mon Sep 17 00:00:00 2001 From: Lymarenko Lev Date: Tue, 26 Dec 2023 18:03:31 +0700 Subject: [PATCH 201/607] Handle NotLoaded --- .../lib/explorer/microservice_interfaces/bens.ex | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex index 02e42b24cfa1..e70c2606e76e 100644 --- a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -273,7 +273,12 @@ defmodule Explorer.MicroserviceInterfaces.BENS do from_address_hash: from_address_hash, token_transfers: token_transfers }) do - token_transfers_addresses = List.flatten(Enum.map(token_transfers, &item_to_address_hash_strings/1)) + token_transfers_addresses = + case token_transfers do + %NotLoaded{} -> [] + _ -> List.flatten(Enum.map(token_transfers, &item_to_address_hash_strings/1)) + end + [to_string(to_address_hash), to_string(from_address_hash)] ++ token_transfers_addresses end @@ -327,12 +332,18 @@ defmodule Explorer.MicroserviceInterfaces.BENS do } = tx, names ) do + token_transfers = + case tx.token_transfers do + %NotLoaded{} -> %NotLoaded{} + token_transfers -> Enum.map(token_transfers, &put_ens_name_to_item(&1, names)) + end + %Transaction{ tx | to_address: alter_address(tx.to_address, to_address_hash, names), created_contract_address: alter_address(tx.created_contract_address, created_contract_address_hash, names), from_address: alter_address(tx.from_address, from_address_hash, names), - token_transfers: Enum.map(tx.token_transfers, &put_ens_name_to_item(&1, names)) + token_transfers: token_transfers } end From 7f8a3d2d0f7593739d5f16158ab0aa44f8d46bfe Mon Sep 17 00:00:00 2001 From: Lymarenko Lev Date: Tue, 26 Dec 2023 20:15:00 +0700 Subject: [PATCH 202/607] Make func private --- apps/explorer/lib/explorer/microservice_interfaces/bens.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex index e70c2606e76e..b0b4106670f8 100644 --- a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -106,7 +106,7 @@ defmodule Explorer.MicroserviceInterfaces.BENS do end end - def http_get_request(url, query_params) do + defp http_get_request(url, query_params) do case HTTPoison.get("#{url}?#{URI.encode_query(query_params)}") do {:ok, %Response{body: body, status_code: 200}} -> Jason.decode(body) From e879849d23dbf2d5a3c6b7d10cde4f8cd86a2710 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Tue, 26 Dec 2023 21:12:51 +0300 Subject: [PATCH 203/607] Refactoring --- .../lib/explorer/microservice_interfaces/bens.ex | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex index b0b4106670f8..59eb1c82403f 100644 --- a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -107,7 +107,7 @@ defmodule Explorer.MicroserviceInterfaces.BENS do end defp http_get_request(url, query_params) do - case HTTPoison.get("#{url}?#{URI.encode_query(query_params)}") do + case HTTPoison.get(url, [], params: query_params) do {:ok, %Response{body: body, status_code: 200}} -> Jason.decode(body) @@ -275,8 +275,11 @@ defmodule Explorer.MicroserviceInterfaces.BENS do }) do token_transfers_addresses = case token_transfers do - %NotLoaded{} -> [] - _ -> List.flatten(Enum.map(token_transfers, &item_to_address_hash_strings/1)) + token_transfers_list when is_list(token_transfers_list) -> + List.flatten(Enum.map(token_transfers_list, &item_to_address_hash_strings/1)) + + _ -> + [] end [to_string(to_address_hash), to_string(from_address_hash)] ++ token_transfers_addresses @@ -334,8 +337,11 @@ defmodule Explorer.MicroserviceInterfaces.BENS do ) do token_transfers = case tx.token_transfers do - %NotLoaded{} -> %NotLoaded{} - token_transfers -> Enum.map(token_transfers, &put_ens_name_to_item(&1, names)) + token_transfers_list when is_list(token_transfers_list) -> + Enum.map(token_transfers_list, &put_ens_name_to_item(&1, names)) + + other -> + other end %Transaction{ From 6bc684ac721a06b99ff82c54509593f85fbf5bd9 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 1 Jan 2024 21:01:33 +0300 Subject: [PATCH 204/607] Fix suffix in Blockscout version in prerelease workflow --- .github/workflows/prerelease.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 3f97613ee0d7..070650c676f4 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -61,5 +61,5 @@ jobs: AMPLITUDE_URL= AMPLITUDE_API_KEY= CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= - BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha RELEASE_VERSION=${{ env.RELEASE_VERSION }} \ No newline at end of file From c3869c41bc67c8c8b7255ef2006e2ec800b333de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 18:48:23 +0000 Subject: [PATCH 205/607] Bump sweetalert2 from 11.10.1 to 11.10.2 in /apps/block_scout_web/assets Bumps [sweetalert2](https://github.com/sweetalert2/sweetalert2) from 11.10.1 to 11.10.2. - [Release notes](https://github.com/sweetalert2/sweetalert2/releases) - [Changelog](https://github.com/sweetalert2/sweetalert2/blob/main/CHANGELOG.md) - [Commits](https://github.com/sweetalert2/sweetalert2/compare/v11.10.1...v11.10.2) --- updated-dependencies: - dependency-name: sweetalert2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index ce501376510e..a9a454809c71 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -61,7 +61,7 @@ "redux": "^5.0.1", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", - "sweetalert2": "^11.10.1", + "sweetalert2": "^11.10.2", "urijs": "^1.19.11", "url": "^0.11.3", "util": "^0.12.5", @@ -16084,9 +16084,9 @@ } }, "node_modules/sweetalert2": { - "version": "11.10.1", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.1.tgz", - "integrity": "sha512-qu145oBuFfjYr5yZW9OSdG6YmRxDf8CnkgT/sXMfrXGe+asFy2imC2vlaLQ/L/naZ/JZna1MPAY56G4qYM0VUQ==", + "version": "11.10.2", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.2.tgz", + "integrity": "sha512-BYlIxGw6OF9Rw2z1wlnh1U+fvHHkvtg4BGyimV9nZxQRGvCBfx9uonxgwuYpJuYqCtM+2W1KOm8iMIEb/2v7Hg==", "funding": { "type": "individual", "url": "https://github.com/sponsors/limonte" @@ -29887,9 +29887,9 @@ } }, "sweetalert2": { - "version": "11.10.1", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.1.tgz", - "integrity": "sha512-qu145oBuFfjYr5yZW9OSdG6YmRxDf8CnkgT/sXMfrXGe+asFy2imC2vlaLQ/L/naZ/JZna1MPAY56G4qYM0VUQ==" + "version": "11.10.2", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.2.tgz", + "integrity": "sha512-BYlIxGw6OF9Rw2z1wlnh1U+fvHHkvtg4BGyimV9nZxQRGvCBfx9uonxgwuYpJuYqCtM+2W1KOm8iMIEb/2v7Hg==" }, "symbol-tree": { "version": "3.2.4", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index ba8804631582..8682b4afef9d 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -73,7 +73,7 @@ "redux": "^5.0.1", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", - "sweetalert2": "^11.10.1", + "sweetalert2": "^11.10.2", "urijs": "^1.19.11", "url": "^0.11.3", "util": "^0.12.5", From 64860e67fd5f4bb0e1ef350e09b1ba95211ccb3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 18:48:54 +0000 Subject: [PATCH 206/607] Bump sass-loader from 13.3.2 to 13.3.3 in /apps/block_scout_web/assets Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 13.3.2 to 13.3.3. - [Release notes](https://github.com/webpack-contrib/sass-loader/releases) - [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/sass-loader/compare/v13.3.2...v13.3.3) --- updated-dependencies: - dependency-name: sass-loader dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index ce501376510e..71be5694e532 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -90,7 +90,7 @@ "postcss": "^8.4.32", "postcss-loader": "^7.3.3", "sass": "^1.69.5", - "sass-loader": "^13.3.2", + "sass-loader": "^13.3.3", "style-loader": "^3.3.3", "webpack": "^5.89.0", "webpack-cli": "^5.1.4" @@ -15202,9 +15202,9 @@ } }, "node_modules/sass-loader": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.2.tgz", - "integrity": "sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==", + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.3.tgz", + "integrity": "sha512-mt5YN2F1MOZr3d/wBRcZxeFgwgkH44wVc2zohO2YF6JiOMkiXe4BYRZpSu2sO1g71mo/j16txzUhsKZlqjVGzA==", "dev": true, "dependencies": { "neo-async": "^2.6.2" @@ -29246,9 +29246,9 @@ } }, "sass-loader": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.2.tgz", - "integrity": "sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==", + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.3.tgz", + "integrity": "sha512-mt5YN2F1MOZr3d/wBRcZxeFgwgkH44wVc2zohO2YF6JiOMkiXe4BYRZpSu2sO1g71mo/j16txzUhsKZlqjVGzA==", "dev": true, "requires": { "neo-async": "^2.6.2" diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index ba8804631582..2f779682abf3 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -102,7 +102,7 @@ "postcss": "^8.4.32", "postcss-loader": "^7.3.3", "sass": "^1.69.5", - "sass-loader": "^13.3.2", + "sass-loader": "^13.3.3", "style-loader": "^3.3.3", "webpack": "^5.89.0", "webpack-cli": "^5.1.4" From a2d0fb0cbef836ad730c8c24c1603e7087db7164 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 18:50:22 +0000 Subject: [PATCH 207/607] Bump @babel/preset-env in /apps/block_scout_web/assets Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.23.6 to 7.23.7. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.7/packages/babel-preset-env) --- updated-dependencies: - dependency-name: "@babel/preset-env" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 150 +++++++++--------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 76 insertions(+), 76 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index ce501376510e..3242fda252d5 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -72,7 +72,7 @@ }, "devDependencies": { "@babel/core": "^7.23.6", - "@babel/preset-env": "^7.23.6", + "@babel/preset-env": "^7.23.7", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^11.0.0", @@ -671,9 +671,9 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", - "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", + "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -991,9 +991,9 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz", - "integrity": "sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.7.tgz", + "integrity": "sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -1780,9 +1780,9 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.6.tgz", - "integrity": "sha512-2XPn/BqKkZCpzYhUUNZ1ssXw7DcXfKQEjv/uXZUXgaebCMYmkEsfZ2yY+vv+xtXv50WmL5SGhyB6/xsWxIvvOQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.7.tgz", + "integrity": "sha512-SY27X/GtTz/L4UryMNJ6p4fH4nsgWbz84y9FE0bQeWJP6O5BhgVCt53CotQKHCOeXJel8VyhlhujhlltKms/CA==", "dev": true, "dependencies": { "@babel/compat-data": "^7.23.5", @@ -1791,7 +1791,7 @@ "@babel/helper-validator-option": "^7.23.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", @@ -1812,7 +1812,7 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.4", + "@babel/plugin-transform-async-generator-functions": "^7.23.7", "@babel/plugin-transform-async-to-generator": "^7.23.3", "@babel/plugin-transform-block-scoped-functions": "^7.23.3", "@babel/plugin-transform-block-scoping": "^7.23.4", @@ -1860,9 +1860,9 @@ "@babel/plugin-transform-unicode-regex": "^7.23.3", "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.6", - "babel-plugin-polyfill-corejs3": "^0.8.5", - "babel-plugin-polyfill-regenerator": "^0.5.3", + "babel-plugin-polyfill-corejs2": "^0.4.7", + "babel-plugin-polyfill-corejs3": "^0.8.7", + "babel-plugin-polyfill-regenerator": "^0.5.4", "core-js-compat": "^3.31.0", "semver": "^6.3.1" }, @@ -1874,9 +1874,9 @@ } }, "node_modules/@babel/preset-env/node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", - "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", + "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", "dev": true, "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", @@ -1890,13 +1890,13 @@ } }, "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", - "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz", + "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==", "dev": true, "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.3", + "@babel/helper-define-polyfill-provider": "^0.4.4", "semver": "^6.3.1" }, "peerDependencies": { @@ -1904,12 +1904,12 @@ } }, "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", - "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz", + "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.3" + "@babel/helper-define-polyfill-provider": "^0.4.4" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -4852,22 +4852,22 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.5.tgz", - "integrity": "sha512-Q6CdATeAvbScWPNLB8lzSO7fgUVBkQt6zLgNlfyeCr/EQaEQR+bWiBYYPYAFyE528BMjRhL+1QBMOI4jc/c5TA==", + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", + "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.3", - "core-js-compat": "^3.32.2" + "@babel/helper-define-polyfill-provider": "^0.4.4", + "core-js-compat": "^3.33.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", - "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", + "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", "dev": true, "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", @@ -5948,11 +5948,11 @@ } }, "node_modules/core-js-compat": { - "version": "3.33.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.0.tgz", - "integrity": "sha512-0w4LcLXsVEuNkIqwjjf9rjCoPhK8uqA4tMRh4Ge26vfLtUutshn+aRJU21I9LCJlh2QQHfisNToLjw1XEJLTWw==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz", + "integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==", "dependencies": { - "browserslist": "^4.22.1" + "browserslist": "^4.22.2" }, "funding": { "type": "opencollective", @@ -18293,9 +18293,9 @@ } }, "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", - "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", + "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.22.20", @@ -18509,9 +18509,9 @@ } }, "@babel/plugin-transform-async-generator-functions": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz", - "integrity": "sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.7.tgz", + "integrity": "sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==", "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.22.20", @@ -19009,9 +19009,9 @@ } }, "@babel/preset-env": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.6.tgz", - "integrity": "sha512-2XPn/BqKkZCpzYhUUNZ1ssXw7DcXfKQEjv/uXZUXgaebCMYmkEsfZ2yY+vv+xtXv50WmL5SGhyB6/xsWxIvvOQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.7.tgz", + "integrity": "sha512-SY27X/GtTz/L4UryMNJ6p4fH4nsgWbz84y9FE0bQeWJP6O5BhgVCt53CotQKHCOeXJel8VyhlhujhlltKms/CA==", "dev": true, "requires": { "@babel/compat-data": "^7.23.5", @@ -19020,7 +19020,7 @@ "@babel/helper-validator-option": "^7.23.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", @@ -19041,7 +19041,7 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.4", + "@babel/plugin-transform-async-generator-functions": "^7.23.7", "@babel/plugin-transform-async-to-generator": "^7.23.3", "@babel/plugin-transform-block-scoped-functions": "^7.23.3", "@babel/plugin-transform-block-scoping": "^7.23.4", @@ -19089,17 +19089,17 @@ "@babel/plugin-transform-unicode-regex": "^7.23.3", "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.6", - "babel-plugin-polyfill-corejs3": "^0.8.5", - "babel-plugin-polyfill-regenerator": "^0.5.3", + "babel-plugin-polyfill-corejs2": "^0.4.7", + "babel-plugin-polyfill-corejs3": "^0.8.7", + "babel-plugin-polyfill-regenerator": "^0.5.4", "core-js-compat": "^3.31.0", "semver": "^6.3.1" }, "dependencies": { "@babel/helper-define-polyfill-provider": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", - "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", + "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", "dev": true, "requires": { "@babel/helper-compilation-targets": "^7.22.6", @@ -19110,23 +19110,23 @@ } }, "babel-plugin-polyfill-corejs2": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", - "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz", + "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==", "dev": true, "requires": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.3", + "@babel/helper-define-polyfill-provider": "^0.4.4", "semver": "^6.3.1" } }, "babel-plugin-polyfill-regenerator": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", - "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz", + "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.4.3" + "@babel/helper-define-polyfill-provider": "^0.4.4" } } } @@ -21387,19 +21387,19 @@ } }, "babel-plugin-polyfill-corejs3": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.5.tgz", - "integrity": "sha512-Q6CdATeAvbScWPNLB8lzSO7fgUVBkQt6zLgNlfyeCr/EQaEQR+bWiBYYPYAFyE528BMjRhL+1QBMOI4jc/c5TA==", + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", + "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.4.3", - "core-js-compat": "^3.32.2" + "@babel/helper-define-polyfill-provider": "^0.4.4", + "core-js-compat": "^3.33.1" }, "dependencies": { "@babel/helper-define-polyfill-provider": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", - "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", + "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", "dev": true, "requires": { "@babel/helper-compilation-targets": "^7.22.6", @@ -22223,11 +22223,11 @@ "integrity": "sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag==" }, "core-js-compat": { - "version": "3.33.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.0.tgz", - "integrity": "sha512-0w4LcLXsVEuNkIqwjjf9rjCoPhK8uqA4tMRh4Ge26vfLtUutshn+aRJU21I9LCJlh2QQHfisNToLjw1XEJLTWw==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz", + "integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==", "requires": { - "browserslist": "^4.22.1" + "browserslist": "^4.22.2" } }, "core-util-is": { diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index ba8804631582..20bf4e7ecb62 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -84,7 +84,7 @@ }, "devDependencies": { "@babel/core": "^7.23.6", - "@babel/preset-env": "^7.23.6", + "@babel/preset-env": "^7.23.7", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^11.0.0", From 9920bac9597915f61c4275b6158bc8ee27ac574d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 18:50:54 +0000 Subject: [PATCH 208/607] Bump moment from 2.29.4 to 2.30.1 in /apps/block_scout_web/assets Bumps [moment](https://github.com/moment/moment) from 2.29.4 to 2.30.1. - [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md) - [Commits](https://github.com/moment/moment/compare/2.29.4...2.30.1) --- updated-dependencies: - dependency-name: moment dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index ce501376510e..a68470a4bd4c 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -47,7 +47,7 @@ "luxon": "^3.4.4", "malihu-custom-scrollbar-plugin": "3.1.5", "mixpanel-browser": "^2.48.1", - "moment": "^2.29.4", + "moment": "^2.30.1", "nanomorph": "^5.4.0", "numeral": "^2.0.6", "os-browserify": "^0.3.0", @@ -12922,9 +12922,9 @@ "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==" }, "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "engines": { "node": "*" } @@ -27614,9 +27614,9 @@ "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==" }, "moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==" }, "ms": { "version": "2.1.2", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index ba8804631582..ffc597f7169c 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -59,7 +59,7 @@ "luxon": "^3.4.4", "malihu-custom-scrollbar-plugin": "3.1.5", "mixpanel-browser": "^2.48.1", - "moment": "^2.29.4", + "moment": "^2.30.1", "nanomorph": "^5.4.0", "numeral": "^2.0.6", "os-browserify": "^0.3.0", From 9afcf010f0c0234a2be0296759af82c2020b1f86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 18:52:09 +0000 Subject: [PATCH 209/607] Bump postcss-loader from 7.3.3 to 7.3.4 in /apps/block_scout_web/assets Bumps [postcss-loader](https://github.com/webpack-contrib/postcss-loader) from 7.3.3 to 7.3.4. - [Release notes](https://github.com/webpack-contrib/postcss-loader/releases) - [Changelog](https://github.com/webpack-contrib/postcss-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/postcss-loader/compare/v7.3.3...v7.3.4) --- updated-dependencies: - dependency-name: postcss-loader dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 78 ++++++++++--------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 44 insertions(+), 36 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index ce501376510e..d37aa1207ce2 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -88,7 +88,7 @@ "jest-environment-jsdom": "^29.7.0", "mini-css-extract-plugin": "^2.7.6", "postcss": "^8.4.32", - "postcss-loader": "^7.3.3", + "postcss-loader": "^7.3.4", "sass": "^1.69.5", "sass-loader": "^13.3.2", "style-loader": "^3.3.3", @@ -5977,14 +5977,14 @@ } }, "node_modules/cosmiconfig": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", - "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "dev": true, "dependencies": { - "import-fresh": "^3.2.1", + "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", + "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "engines": { @@ -5992,6 +5992,14 @@ }, "funding": { "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/cosmiconfig/node_modules/argparse": { @@ -11908,9 +11916,9 @@ } }, "node_modules/jiti": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", - "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", "dev": true, "bin": { "jiti": "bin/jiti.js" @@ -13861,14 +13869,14 @@ } }, "node_modules/postcss-loader": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.3.tgz", - "integrity": "sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", + "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", "dev": true, "dependencies": { - "cosmiconfig": "^8.2.0", - "jiti": "^1.18.2", - "semver": "^7.3.8" + "cosmiconfig": "^8.3.5", + "jiti": "^1.20.0", + "semver": "^7.5.4" }, "engines": { "node": ">= 14.15.0" @@ -13883,9 +13891,9 @@ } }, "node_modules/postcss-loader/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -22245,14 +22253,14 @@ } }, "cosmiconfig": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", - "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "dev": true, "requires": { - "import-fresh": "^3.2.1", + "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", + "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "dependencies": { @@ -26750,9 +26758,9 @@ } }, "jiti": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", - "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", "dev": true }, "jquery": { @@ -28280,20 +28288,20 @@ "requires": {} }, "postcss-loader": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.3.tgz", - "integrity": "sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", + "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", "dev": true, "requires": { - "cosmiconfig": "^8.2.0", - "jiti": "^1.18.2", - "semver": "^7.3.8" + "cosmiconfig": "^8.3.5", + "jiti": "^1.20.0", + "semver": "^7.5.4" }, "dependencies": { "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index ba8804631582..dd346079003c 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -100,7 +100,7 @@ "jest-environment-jsdom": "^29.7.0", "mini-css-extract-plugin": "^2.7.6", "postcss": "^8.4.32", - "postcss-loader": "^7.3.3", + "postcss-loader": "^7.3.4", "sass": "^1.69.5", "sass-loader": "^13.3.2", "style-loader": "^3.3.3", From bd99e4595ef47f618f80b760ead4943cfbf31509 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 19:14:03 +0000 Subject: [PATCH 210/607] Bump core-js from 3.34.0 to 3.35.0 in /apps/block_scout_web/assets Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.34.0 to 3.35.0. - [Release notes](https://github.com/zloirock/core-js/releases) - [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/zloirock/core-js/commits/v3.35.0/packages/core-js) --- updated-dependencies: - dependency-name: core-js dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 3242fda252d5..bc3104cd4a26 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -17,7 +17,7 @@ "chart.js": "^4.4.1", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", - "core-js": "^3.34.0", + "core-js": "^3.35.0", "crypto-browserify": "^3.12.0", "dropzone": "^5.9.3", "eth-net-props": "^1.0.41", @@ -5938,9 +5938,9 @@ } }, "node_modules/core-js": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.34.0.tgz", - "integrity": "sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.0.tgz", + "integrity": "sha512-ntakECeqg81KqMueeGJ79Q5ZgQNR+6eaE8sxGCx62zMbAIj65q+uYvatToew3m6eAGdU4gNZwpZ34NMe4GYswg==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -22218,9 +22218,9 @@ } }, "core-js": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.34.0.tgz", - "integrity": "sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag==" + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.0.tgz", + "integrity": "sha512-ntakECeqg81KqMueeGJ79Q5ZgQNR+6eaE8sxGCx62zMbAIj65q+uYvatToew3m6eAGdU4gNZwpZ34NMe4GYswg==" }, "core-js-compat": { "version": "3.35.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 20bf4e7ecb62..efd38ee47c92 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -29,7 +29,7 @@ "chart.js": "^4.4.1", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", - "core-js": "^3.34.0", + "core-js": "^3.35.0", "crypto-browserify": "^3.12.0", "dropzone": "^5.9.3", "eth-net-props": "^1.0.41", From 31098dc0a8373baa4f11b314eaee9a1f6b3bcfd0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 19:15:06 +0000 Subject: [PATCH 211/607] Bump @babel/core from 7.23.6 to 7.23.7 in /apps/block_scout_web/assets Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.23.6 to 7.23.7. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.7/packages/babel-core) --- updated-dependencies: - dependency-name: "@babel/core" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 50 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 3242fda252d5..fdceed739d12 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -71,7 +71,7 @@ "xss": "^1.0.14" }, "devDependencies": { - "@babel/core": "^7.23.6", + "@babel/core": "^7.23.7", "@babel/preset-env": "^7.23.7", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", @@ -249,19 +249,19 @@ } }, "node_modules/@babel/core": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz", - "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", + "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.6", + "@babel/helpers": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.6", + "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -602,12 +602,12 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", - "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.7.tgz", + "integrity": "sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==", "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.6", + "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" }, "engines": { @@ -1960,9 +1960,9 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", - "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", + "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", "dependencies": { "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", @@ -17985,19 +17985,19 @@ "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==" }, "@babel/core": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz", - "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", + "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.6", + "@babel/helpers": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.6", + "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -18248,12 +18248,12 @@ } }, "@babel/helpers": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", - "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.7.tgz", + "integrity": "sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==", "requires": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.6", + "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" } }, @@ -19167,9 +19167,9 @@ } }, "@babel/traverse": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", - "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", + "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", "requires": { "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 20bf4e7ecb62..0497308b90f2 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -83,7 +83,7 @@ "xss": "^1.0.14" }, "devDependencies": { - "@babel/core": "^7.23.6", + "@babel/core": "^7.23.7", "@babel/preset-env": "^7.23.7", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", From f24d2483f1172f4af1014cfb3f32d7ac0258abbb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 20:30:49 +0000 Subject: [PATCH 212/607] Bump sass from 1.69.5 to 1.69.6 in /apps/block_scout_web/assets Bumps [sass](https://github.com/sass/dart-sass) from 1.69.5 to 1.69.6. - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.69.5...1.69.6) --- updated-dependencies: - dependency-name: sass dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index da2f47517c80..77192dd8a924 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -89,7 +89,7 @@ "mini-css-extract-plugin": "^2.7.6", "postcss": "^8.4.32", "postcss-loader": "^7.3.4", - "sass": "^1.69.5", + "sass": "^1.69.6", "sass-loader": "^13.3.3", "style-loader": "^3.3.3", "webpack": "^5.89.0", @@ -15193,9 +15193,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.69.5", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz", - "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", + "version": "1.69.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.6.tgz", + "integrity": "sha512-qbRr3k9JGHWXCvZU77SD2OTwUlC+gNT+61JOLcmLm+XqH4h/5D+p4IIsxvpkB89S9AwJOyb5+rWNpIucaFxSFQ==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -29243,9 +29243,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass": { - "version": "1.69.5", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz", - "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", + "version": "1.69.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.6.tgz", + "integrity": "sha512-qbRr3k9JGHWXCvZU77SD2OTwUlC+gNT+61JOLcmLm+XqH4h/5D+p4IIsxvpkB89S9AwJOyb5+rWNpIucaFxSFQ==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 40eddc0776a0..9d8513f7037a 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -101,7 +101,7 @@ "mini-css-extract-plugin": "^2.7.6", "postcss": "^8.4.32", "postcss-loader": "^7.3.4", - "sass": "^1.69.5", + "sass": "^1.69.6", "sass-loader": "^13.3.3", "style-loader": "^3.3.3", "webpack": "^5.89.0", From f2a79e45da745d8fbfb9f82f040aeeea52ba727d Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Fri, 22 Dec 2023 18:50:28 +0300 Subject: [PATCH 213/607] Expand gas price oracle functionality --- CHANGELOG.md | 2 + .../api/v1/gas_price_oracle_controller.ex | 4 +- .../controllers/api/v2/stats_controller.ex | 34 +++ .../gas_price_oracle_legend_item.html.eex | 10 +- .../lib/block_scout_web/views/chain_view.ex | 5 +- .../explorer/chain/cache/gas_price_oracle.ex | 206 ++++++++++---- apps/explorer/lib/explorer/market/market.ex | 2 +- .../lib/explorer/market/market_history.ex | 8 +- ...720_constrain_null_date_market_history.exs | 9 + .../chain/cache/gas_price_oracle_test.exs | 255 ++++++++++++++---- config/runtime.exs | 1 + cspell.json | 17 +- docker-compose/envs/common-blockscout.env | 6 + 13 files changed, 433 insertions(+), 126 deletions(-) create mode 100644 apps/explorer/priv/repo/migrations/20240103094720_constrain_null_date_market_history.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index f4f63d99195c..71a6a08a4921 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Features +- [#9044](https://github.com/blockscout/blockscout/pull/9044) - Expand gas price oracle functionality + ### Fixes - [#9062](https://github.com/blockscout/blockscout/pull/9062) - Fix blockscout-ens integration diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex index 7b70b4b4276f..1c99dcb17136 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex @@ -30,8 +30,8 @@ defmodule BlockScoutWeb.API.V1.GasPriceOracleController do |> send_resp(status, result) end - def result(gas_prices) do - gas_prices + defp result(gas_prices) do + %{slow: gas_prices[:slow][:price], average: gas_prices[:average][:price], fast: gas_prices[:fast][:price]} |> Jason.encode!() end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex index 825d9f1b2575..34ea7b3eae34 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex @@ -12,6 +12,7 @@ defmodule BlockScoutWeb.API.V2.StatsController do alias Explorer.Chain.Supply.RSK alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.Counters.AverageBlockTime + alias Plug.Conn alias Timex.Duration @api_true [api?: true] @@ -39,6 +40,21 @@ defmodule BlockScoutWeb.API.V2.StatsController do nil end + coin_price_change = + case Market.fetch_recent_history() do + [today, yesterday | _] -> + today.closing_price && yesterday.closing_price && + today.closing_price + |> Decimal.div(yesterday.closing_price) + |> Decimal.sub(1) + |> Decimal.mult(100) + |> Decimal.to_float() + |> Float.ceil(2) + + _ -> + nil + end + gas_price = Application.get_env(:block_scout_web, :gas_price) json( @@ -49,16 +65,20 @@ defmodule BlockScoutWeb.API.V2.StatsController do "total_transactions" => TransactionCache.estimated_count() |> to_string(), "average_block_time" => AverageBlockTime.average_block_time() |> Duration.to_milliseconds(), "coin_price" => exchange_rate_from_db.usd_value, + "coin_price_change_percentage" => coin_price_change, "total_gas_used" => GasUsage.total() |> to_string(), "transactions_today" => Enum.at(transaction_stats, 0).number_of_transactions |> to_string(), "gas_used_today" => Enum.at(transaction_stats, 0).gas_used, "gas_prices" => gas_prices, + "gas_prices_update_in" => GasPriceOracle.global_ttl(), + "gas_price_updated_at" => GasPriceOracle.get_updated_at(), "static_gas_price" => gas_price, "market_cap" => Helper.market_cap(market_cap_type, exchange_rate_from_db), "tvl" => exchange_rate_from_db.tvl_usd, "network_utilization_percentage" => network_utilization_percentage() } |> add_rootstock_locked_btc() + |> backward_compatibility(conn) ) end @@ -135,4 +155,18 @@ defmodule BlockScoutWeb.API.V2.StatsController do _ -> stats end end + + defp backward_compatibility(response, conn) do + case Conn.get_req_header(conn, "updated-gas-oracle") do + ["true"] -> + response + + _ -> + response + |> Map.update("gas_prices", nil, fn + gas_prices -> + %{slow: gas_prices[:slow][:price], average: gas_prices[:average][:price], fast: gas_prices[:fast][:price]} + end) + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex index 4b84dce35873..f07899ea9bf8 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex @@ -8,7 +8,7 @@
-
<%= "#{gas_prices_from_oracle["average"]}" <> " " %><%= gettext "Gwei" %>
+
<%= "#{gas_prices_from_oracle[:average]}" <> " " %><%= gettext "Gwei" %>
-
<%= gettext "Slow" %><%= gas_prices_from_oracle["slow"] %> <%= gettext "Gwei" %>
-
<%= gettext "Average" %><%= gas_prices_from_oracle["average"] %> <%= gettext "Gwei" %>
-
<%= gettext "Fast" %><%= gas_prices_from_oracle["fast"] %> <%= gettext "Gwei" %>
+
<%= gettext "Slow" %><%= gas_prices_from_oracle[:slow] %> <%= gettext "Gwei" %>
+
<%= gettext "Average" %><%= gas_prices_from_oracle[:average] %> <%= gettext "Gwei" %>
+
<%= gettext "Fast" %><%= gas_prices_from_oracle[:fast] %> <%= gettext "Gwei" %>
" > @@ -40,4 +40,4 @@ <% end %> <% end %>
-
\ No newline at end of file +
diff --git a/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex b/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex index 43d66a29266d..bbbcdebe866e 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex @@ -60,10 +60,7 @@ defmodule BlockScoutWeb.ChainView do defp gas_prices do case GasPriceOracle.get_gas_prices() do {:ok, gas_prices} -> - gas_prices - - nil -> - nil + %{slow: gas_prices[:slow][:price], average: gas_prices[:average][:price], fast: gas_prices[:fast][:price]} _ -> nil diff --git a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex index e147c9c0c782..b1d0c10572c9 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex @@ -17,27 +17,61 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do Wei } - alias Explorer.Repo + alias Explorer.Counters.AverageBlockTime + alias Explorer.{Market, Repo} + alias Timex.Duration use Explorer.Chain.MapCache, name: :gas_price, key: :gas_prices, + key: :gas_prices_acc, + key: :updated_at, key: :old_gas_prices, key: :async_task, - global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl], + global_ttl: global_ttl(), ttl_check_interval: :timer.seconds(1), callback: &async_task_on_deletion(&1) @doc """ - Get `safelow`, `average` and `fast` percentile of transactions gas prices among the last `num_of_blocks` blocks + Calculates the `slow`, `average`, and `fast` gas price and time percentiles from the last `num_of_blocks` blocks and estimates the fiat price for each percentile. + These percentiles correspond to the likelihood of a transaction being picked up by miners depending on the fee offered. """ @spec get_average_gas_price(pos_integer(), pos_integer(), pos_integer(), pos_integer()) :: - {:error, any} | {:ok, %{String.t() => nil | float, String.t() => nil | float, String.t() => nil | float}} + {{:error, any} | {:ok, %{slow: gas_price, average: gas_price, fast: gas_price}}, + [ + %{ + block_number: non_neg_integer(), + slow_gas_price: nil | Decimal.t(), + fast_gas_price: nil | Decimal.t(), + average_gas_price: nil | Decimal.t(), + slow_priority_fee_per_gas: nil | Decimal.t(), + average_priority_fee_per_gas: nil | Decimal.t(), + fast_priority_fee_per_gas: nil | Decimal.t(), + slow_time: nil | Decimal.t(), + average_time: nil | Decimal.t(), + fast_time: nil | Decimal.t() + } + ]} + when gas_price: nil | %{price: float(), time: float(), fiat_price: Decimal.t()} def get_average_gas_price(num_of_blocks, safelow_percentile, average_percentile, fast_percentile) do safelow_percentile_fraction = safelow_percentile / 100 average_percentile_fraction = average_percentile / 100 fast_percentile_fraction = fast_percentile / 100 + acc = get_gas_prices_acc() + + from_block = + case acc do + [%{block_number: from_block} | _] -> from_block + _ -> -1 + end + + average_block_time = + case AverageBlockTime.average_block_time() do + {:error, _} -> nil + average_block_time -> average_block_time |> Duration.to_milliseconds() + end + fee_query = from( block in Block, @@ -45,120 +79,186 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do where: block.consensus == true, where: transaction.status == ^1, where: transaction.gas_price > ^0, - group_by: block.number, - order_by: [desc: block.number], + 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(?) within group ( order by ? )", + "percentile_disc(? :: real) within group ( order by ? )", ^safelow_percentile_fraction, transaction.gas_price ), average_gas_price: fragment( - "percentile_disc(?) within group ( order by ? )", + "percentile_disc(? :: real) within group ( order by ? )", ^average_percentile_fraction, transaction.gas_price ), fast_gas_price: fragment( - "percentile_disc(?) within group ( order by ? )", + "percentile_disc(? :: real) within group ( order by ? )", ^fast_percentile_fraction, transaction.gas_price ), - slow: + slow_priority_fee_per_gas: fragment( - "percentile_disc(?) within group ( order by ? )", + "percentile_disc(? :: real) within group ( order by ? )", ^safelow_percentile_fraction, transaction.max_priority_fee_per_gas ), - average: + average_priority_fee_per_gas: fragment( - "percentile_disc(?) within group ( order by ? )", + "percentile_disc(? :: real) within group ( order by ? )", ^average_percentile_fraction, transaction.max_priority_fee_per_gas ), - fast: + fast_priority_fee_per_gas: fragment( - "percentile_disc(?) within group ( order by ? )", + "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 ) - gas_prices = fee_query |> Repo.all(timeout: :infinity) |> process_fee_data_from_db() + new_acc = fee_query |> Repo.all(timeout: :infinity) |> merge_gas_prices(acc, num_of_blocks) + + gas_prices = new_acc |> process_fee_data_from_db() - {:ok, gas_prices} + {{:ok, gas_prices}, new_acc} catch error -> - {:error, error} + Logger.error("Failed to get gas prices: #{inspect(error)}") + {{:error, error}, get_gas_prices_acc()} end + defp merge_gas_prices(new, acc, acc_size), do: Enum.take(new ++ acc, acc_size) + defp process_fee_data_from_db([]) do %{ - "slow" => nil, - "average" => nil, - "fast" => nil + slow: nil, + average: nil, + fast: nil } end defp process_fee_data_from_db(fees) do - fees_length = Enum.count(fees) - %{ slow_gas_price: slow_gas_price, average_gas_price: average_gas_price, fast_gas_price: fast_gas_price, - slow: slow, - average: average, - fast: fast - } = - fees - |> Enum.reduce( - &Map.merge(&1, &2, fn - _, v1, v2 when nil not in [v1, v2] -> Decimal.add(v1, v2) - _, v1, v2 -> v1 || v2 - end) - ) - |> Map.new(fn - {key, nil} -> {key, nil} - {key, value} -> {key, Decimal.div(value, fees_length)} - end) + slow_priority_fee_per_gas: slow_priority_fee_per_gas, + average_priority_fee_per_gas: average_priority_fee_per_gas, + fast_priority_fee_per_gas: fast_priority_fee_per_gas, + slow_time: slow_time, + average_time: average_time, + fast_time: fast_time + } = merge_fees(fees) json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) {slow_fee, average_fee, fast_fee} = - case {nil not in [slow, average, fast], EthereumJSONRPC.fetch_block_by_tag("pending", json_rpc_named_arguments)} do - {true, {:ok, %Blocks{blocks_params: [%{base_fee_per_gas: base_fee}]}}} when not is_nil(base_fee) -> + case nil not in [slow_priority_fee_per_gas, average_priority_fee_per_gas, fast_priority_fee_per_gas] && + EthereumJSONRPC.fetch_block_by_tag("pending", json_rpc_named_arguments) do + {:ok, %Blocks{blocks_params: [%{base_fee_per_gas: base_fee}]}} when not is_nil(base_fee) -> base_fee_wei = base_fee |> Decimal.new() |> Wei.from(:wei) { - priority_with_base_fee(slow, base_fee_wei), - priority_with_base_fee(average, base_fee_wei), - priority_with_base_fee(fast, base_fee_wei) + priority_with_base_fee(slow_priority_fee_per_gas, base_fee_wei), + priority_with_base_fee(average_priority_fee_per_gas, base_fee_wei), + priority_with_base_fee(fast_priority_fee_per_gas, base_fee_wei) } _ -> {gas_price(slow_gas_price), gas_price(average_gas_price), gas_price(fast_gas_price)} end + exchange_rate_from_db = Market.get_coin_exchange_rate() + %{ - "slow" => slow_fee, - "average" => average_fee, - "fast" => fast_fee + slow: compose_gas_price(slow_fee, slow_time, exchange_rate_from_db), + average: compose_gas_price(average_fee, average_time, exchange_rate_from_db), + fast: compose_gas_price(fast_fee, fast_time, exchange_rate_from_db) } end + defp merge_fees(fees_from_db) do + fees_from_db + |> Stream.map(&Map.delete(&1, :block_number)) + |> Enum.reduce( + &Map.merge(&1, &2, fn + _, nil, nil -> nil + _, val, acc when nil not in [val, acc] and is_list(acc) -> [val | acc] + _, val, acc when nil not in [val, acc] -> [val, acc] + _, val, acc -> [val || acc] + end) + ) + |> Map.new(fn + {key, nil} -> + {key, nil} + + {key, value} -> + value = if is_list(value), do: value, else: [value] + count = Enum.count(value) + {key, value |> Enum.reduce(Decimal.new(0), &Decimal.add/2) |> Decimal.div(count)} + end) + end + + defp compose_gas_price(fee, time, exchange_rate_from_db) do + %{ + price: fee |> format_wei(), + time: time && time |> Decimal.to_float(), + fiat_price: fiat_fee(fee, exchange_rate_from_db) + } + end + + defp fiat_fee(fee, exchange_rate) do + exchange_rate.usd_value && + fee + |> Wei.to(:ether) + |> Decimal.mult(exchange_rate.usd_value) + |> Decimal.mult(simple_transaction_gas()) + |> Decimal.round(2) + end + defp priority_with_base_fee(priority, base_fee) do - priority |> Wei.from(:wei) |> Wei.sum(base_fee) |> Wei.to(:gwei) |> Decimal.to_float() |> Float.ceil(2) + priority |> Wei.from(:wei) |> Wei.sum(base_fee) end defp gas_price(value) do - value |> Wei.from(:wei) |> Wei.to(:gwei) |> Decimal.to_float() |> Float.ceil(2) + value |> Wei.from(:wei) end + defp format_wei(wei), do: wei |> Wei.to(:gwei) |> Decimal.to_float() |> Float.ceil(2) + + def global_ttl, do: Application.get_env(:explorer, __MODULE__)[:global_ttl] + + defp simple_transaction_gas, do: Application.get_env(:explorer, __MODULE__)[:simple_transaction_gas] + defp num_of_blocks, do: Application.get_env(:explorer, __MODULE__)[:num_of_blocks] defp safelow, do: Application.get_env(:explorer, __MODULE__)[:safelow_percentile] @@ -181,12 +281,14 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do {:ok, task} = Task.start(fn -> try do - result = get_average_gas_price(num_of_blocks(), safelow(), average(), fast()) + {result, acc} = get_average_gas_price(num_of_blocks(), safelow(), average(), fast()) + set_gas_prices_acc(acc) set_gas_prices(result) + set_updated_at(DateTime.utc_now()) rescue e -> - Logger.debug([ + Logger.error([ "Couldn't update gas used gas_prices", Exception.format(:error, e, __STACKTRACE__) ]) @@ -198,6 +300,10 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do {:update, task} end + defp handle_fallback(:gas_prices_acc) do + {:return, []} + end + defp handle_fallback(_), do: {:return, nil} # By setting this as a `callback` an async task will be started each time the diff --git a/apps/explorer/lib/explorer/market/market.ex b/apps/explorer/lib/explorer/market/market.ex index 3691ac4eaaf5..6de43a1b75b6 100644 --- a/apps/explorer/lib/explorer/market/market.ex +++ b/apps/explorer/lib/explorer/market/market.ex @@ -52,7 +52,7 @@ defmodule Explorer.Market do @doc """ Get most recent exchange rate for the native coin from ETS or from DB. """ - @spec get_coin_exchange_rate() :: Token.t() | nil + @spec get_coin_exchange_rate() :: Token.t() def get_coin_exchange_rate do get_native_coin_exchange_rate_from_cache() || get_native_coin_exchange_rate_from_db() || Token.null() end diff --git a/apps/explorer/lib/explorer/market/market_history.ex b/apps/explorer/lib/explorer/market/market_history.ex index 0fabaf0e305c..8c1411879ee3 100644 --- a/apps/explorer/lib/explorer/market/market_history.ex +++ b/apps/explorer/lib/explorer/market/market_history.ex @@ -23,10 +23,10 @@ defmodule Explorer.Market.MarketHistory do * `:market_cap` - TVL in USD. """ @type t :: %__MODULE__{ - closing_price: Decimal.t(), + closing_price: Decimal.t() | nil, date: Date.t(), - opening_price: Decimal.t(), - market_cap: Decimal.t(), - tvl: Decimal.t() + opening_price: Decimal.t() | nil, + market_cap: Decimal.t() | nil, + tvl: Decimal.t() | nil } end diff --git a/apps/explorer/priv/repo/migrations/20240103094720_constrain_null_date_market_history.exs b/apps/explorer/priv/repo/migrations/20240103094720_constrain_null_date_market_history.exs new file mode 100644 index 000000000000..ab43538e4e1e --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240103094720_constrain_null_date_market_history.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.ConstrainNullDateMarketHistory do + use Ecto.Migration + + def change do + alter table(:market_history) do + modify(:date, :date, null: false, from: {:date, null: true}) + end + end +end diff --git a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs index d7004d11d0ed..20472f79ee32 100644 --- a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs +++ b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs @@ -4,6 +4,7 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do import Mox alias Explorer.Chain.Cache.GasPriceOracle + alias Explorer.Counters.AverageBlockTime @block %{ "difficulty" => "0x0", @@ -48,12 +49,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do test "returns nil percentile values if no blocks in the DB" do expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) - assert {:ok, - %{ - "slow" => nil, - "average" => nil, - "fast" => nil - }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + assert {{:ok, + %{ + slow: nil, + average: nil, + fast: nil + }}, []} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) end test "returns nil percentile values if blocks are empty in the DB" do @@ -63,12 +64,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do insert(:block) insert(:block) - assert {:ok, - %{ - "slow" => nil, - "average" => nil, - "fast" => nil - }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + assert {{:ok, + %{ + slow: nil, + average: nil, + fast: nil + }}, []} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) end test "returns nil percentile values for blocks with failed txs in the DB" do @@ -89,12 +90,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269" ) - assert {:ok, - %{ - "slow" => nil, - "average" => nil, - "fast" => nil - }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + assert {{:ok, + %{ + slow: nil, + average: nil, + fast: nil + }}, []} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) end test "returns nil percentile values for transactions with 0 gas price aka 'whitelisted transactions' in the DB" do @@ -127,12 +128,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03" ) - assert {:ok, - %{ - "slow" => nil, - "average" => nil, - "fast" => nil - }} = GasPriceOracle.get_average_gas_price(2, 35, 60, 90) + assert {{:ok, + %{ + slow: nil, + average: nil, + fast: nil + }}, []} = GasPriceOracle.get_average_gas_price(2, 35, 60, 90) end test "returns the same percentile values if gas price is the same over transactions" do @@ -165,12 +166,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03" ) - assert {:ok, - %{ - "slow" => 1.0, - "average" => 1.0, - "fast" => 1.0 - }} = GasPriceOracle.get_average_gas_price(2, 35, 60, 90) + assert {{:ok, + %{ + slow: %{price: 1.0}, + average: %{price: 1.0}, + fast: %{price: 1.0} + }}, _} = GasPriceOracle.get_average_gas_price(2, 35, 60, 90) end test "returns correct min gas price from the block" do @@ -215,12 +216,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do hash: "0x906b80861b4a0921acfbb91a7b527227b0d32adabc88bc73e8c52ff714e55016" ) - assert {:ok, - %{ - "slow" => 1.0, - "average" => 2.0, - "fast" => 2.0 - }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + assert {{:ok, + %{ + slow: %{price: 1.0}, + average: %{price: 2.0}, + fast: %{price: 2.0} + }}, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) end test "returns correct average percentile" do @@ -266,10 +267,10 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do hash: "0x7d4bc5569053fc29f471901e967c9e60205ac7a122b0e9a789683652c34cc11a" ) - assert {:ok, - %{ - "average" => 3.34 - }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + assert {{:ok, + %{ + average: %{price: 3.34} + }}, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) end test "returns correct gas price for EIP-1559 transactions" do @@ -320,13 +321,173 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do hash: "0x906b80861b4a0921acfbb91a7b527227b0d32adabc88bc73e8c52ff714e55016" ) - assert {:ok, - %{ - # including base fee - "slow" => 1.5, - "average" => 2.5, - "fast" => 2.5 - }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + assert {{:ok, + %{ + # including base fee + slow: %{price: 1.5}, + average: %{price: 2.5} + }}, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + end + + test "return gas prices with time if available" do + expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) + + block1 = + insert(:block, + number: 100, + hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391", + timestamp: ~U[2023-12-12 12:12:30.000000Z] + ) + + block2 = + insert(:block, + number: 101, + hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729", + timestamp: ~U[2023-12-12 12:13:00.000000Z] + ) + + :transaction + |> insert( + status: :ok, + block_hash: block1.hash, + block_number: block1.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 1_000_000_000, + max_priority_fee_per_gas: 1_000_000_000, + max_fee_per_gas: 1_000_000_000, + hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269", + earliest_processing_start: ~U[2023-12-12 12:12:00.000000Z] + ) + + :transaction + |> insert( + status: :ok, + block_hash: block2.hash, + block_number: block2.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 1_000_000_000, + max_priority_fee_per_gas: 1_000_000_000, + max_fee_per_gas: 1_000_000_000, + hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03", + earliest_processing_start: ~U[2023-12-12 12:12:00.000000Z] + ) + + :transaction + |> insert( + status: :ok, + block_hash: block2.hash, + block_number: block2.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 1, + gas_price: 3_000_000_000, + max_priority_fee_per_gas: 3_000_000_000, + max_fee_per_gas: 3_000_000_000, + hash: "0x906b80861b4a0921acfbb91a7b527227b0d32adabc88bc73e8c52ff714e55016", + earliest_processing_start: ~U[2023-12-12 12:12:55.000000Z] + ) + + assert {{ + :ok, + %{ + average: %{price: 2.5, time: 15000.0}, + fast: %{price: 2.5, time: 15000.0}, + slow: %{price: 1.5, time: 17500.0} + } + }, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + end + + test "return gas prices with average block time if earliest_processing_start is not available" do + expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) + old_env = Application.get_env(:explorer, AverageBlockTime) + Application.put_env(:explorer, AverageBlockTime, enabled: true, cache_period: 1_800_000) + start_supervised!(AverageBlockTime) + + block_number = 99_999_999 + first_timestamp = ~U[2023-12-12 12:12:30.000000Z] + + Enum.each(1..100, fn i -> + insert(:block, + number: block_number + 1 + i, + consensus: true, + timestamp: Timex.shift(first_timestamp, seconds: -(101 - i) - 12) + ) + end) + + block1 = + insert(:block, + number: block_number + 102, + consensus: true, + timestamp: Timex.shift(first_timestamp, seconds: -10) + ) + + block2 = + insert(:block, + number: block_number + 103, + consensus: true, + timestamp: Timex.shift(first_timestamp, seconds: -7) + ) + + AverageBlockTime.refresh() + + :transaction + |> insert( + status: :ok, + block_hash: block1.hash, + block_number: block1.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 1_000_000_000, + max_priority_fee_per_gas: 1_000_000_000, + max_fee_per_gas: 1_000_000_000, + hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269" + ) + + :transaction + |> insert( + status: :ok, + block_hash: block2.hash, + block_number: block2.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 1_000_000_000, + max_priority_fee_per_gas: 1_000_000_000, + max_fee_per_gas: 1_000_000_000, + hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03" + ) + + :transaction + |> insert( + status: :ok, + block_hash: block2.hash, + block_number: block2.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 1, + gas_price: 3_000_000_000, + max_priority_fee_per_gas: 3_000_000_000, + max_fee_per_gas: 3_000_000_000, + hash: "0x906b80861b4a0921acfbb91a7b527227b0d32adabc88bc73e8c52ff714e55016" + ) + + AverageBlockTime.refresh() + + assert {{ + :ok, + %{ + average: %{price: 2.5, time: 1000.0}, + fast: %{price: 2.5, time: 1000.0}, + slow: %{price: 1.5, time: 1000.0} + } + }, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + + Application.put_env(:explorer, AverageBlockTime, old_env) end end end diff --git a/config/runtime.exs b/config/runtime.exs index f876325abb5b..afcd147129a8 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -243,6 +243,7 @@ config :explorer, Explorer.Chain.Cache.PendingBlockOperation, config :explorer, Explorer.Chain.Cache.GasPriceOracle, global_ttl: ConfigHelper.parse_time_env_var("GAS_PRICE_ORACLE_CACHE_PERIOD", "30s"), + simple_transaction_gas: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_SIMPLE_TRANSACTION_GAS", 21000), num_of_blocks: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_NUM_OF_BLOCKS", 200), safelow_percentile: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_SAFELOW_PERCENTILE", 35), average_percentile: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_AVERAGE_PERCENTILE", 60), diff --git a/cspell.json b/cspell.json index 3cdf520ecd49..78b98a92d718 100644 --- a/cspell.json +++ b/cspell.json @@ -166,6 +166,7 @@ "Faileddi", "falala", "Filesize", + "fkey", "Floki", "fontawesome", "fortawesome", @@ -264,6 +265,7 @@ "mergeable", "Merkle", "metatags", + "microsecs", "millis", "mintings", "mistmatches", @@ -408,6 +410,7 @@ "snapshotted", "snapshotting", "Sokol", + "SOLIDITYSCAN", "soljson", "someout", "sourcecode", @@ -534,19 +537,7 @@ "zindex", "zipcode", "zkbob", - "zkevm", - "erts", - "Asfpp", - "Nerg", - "secp", - "qwertyuioiuytrewertyuioiuytrertyuio", - "urlset", - "lastmod", - "qitmeer", - "meer", - "DefiLlama", - "SOLIDITYSCAN", - "fkey" + "zkevm" ], "enableFiletypes": [ "dotenv", diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 73af4cb85ad4..f3e67286b645 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -182,6 +182,12 @@ COIN_BALANCE_HISTORY_DAYS=90 APPS_MENU=true EXTERNAL_APPS=[] # GAS_PRICE= +# GAS_PRICE_ORACLE_CACHE_PERIOD= +# GAS_PRICE_ORACLE_SIMPLE_TRANSACTION_GAS= +# GAS_PRICE_ORACLE_NUM_OF_BLOCKS= +# GAS_PRICE_ORACLE_SAFELOW_PERCENTILE= +# GAS_PRICE_ORACLE_AVERAGE_PERCENTILE= +# GAS_PRICE_ORACLE_FAST_PERCENTILE= # RESTRICTED_LIST= # RESTRICTED_LIST_KEY= SHOW_MAINTENANCE_ALERT=false From b128a175d903d18ba25a7474eafe901259d0bad8 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Wed, 3 Jan 2024 13:09:41 +0300 Subject: [PATCH 214/607] Improve exchange rates logging --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/exchange_rates/source.ex | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4f63d99195c..c21858b2f66c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ ### Chore +- [#9094](https://github.com/blockscout/blockscout/pull/9094) - Improve exchange rates logging - [#9014](https://github.com/blockscout/blockscout/pull/9014) - Decrease amount of NFT in address collection: 15 -> 9 - [#8994](https://github.com/blockscout/blockscout/pull/8994) - Refactor transactions event preloads - [#8991](https://github.com/blockscout/blockscout/pull/8991) - Manage DB queue target via runtime env var diff --git a/apps/explorer/lib/explorer/exchange_rates/source.ex b/apps/explorer/lib/explorer/exchange_rates/source.ex index 8d50d4069bec..0c7a0929651f 100644 --- a/apps/explorer/lib/explorer/exchange_rates/source.ex +++ b/apps/explorer/lib/explorer/exchange_rates/source.ex @@ -120,7 +120,7 @@ defmodule Explorer.ExchangeRates.Source do parse_http_success_response(body) {:ok, %Response{body: body, status_code: status_code}} when status_code in 400..526 -> - parse_http_error_response(body) + parse_http_error_response(body, status_code) {:ok, %Response{status_code: status_code}} when status_code in 300..308 -> {:error, "Source redirected"} @@ -139,13 +139,13 @@ defmodule Explorer.ExchangeRates.Source do {:ok, body_json} end - defp parse_http_error_response(body) do + defp parse_http_error_response(body, status_code) do body_json = Helper.decode_json(body) if is_map(body_json) do - {:error, body_json["error"]} + {:error, "#{status_code}: #{body_json["error"]}"} else - {:error, body} + {:error, "#{status_code}: #{body}"} end end end From 209e65d84921052462a9ad207c3c24674f374ac1 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Mon, 13 Nov 2023 14:48:43 +0600 Subject: [PATCH 215/607] Update existing tokens type if got transfer with higher type priority --- CHANGELOG.md | 1 + apps/indexer/lib/indexer/block/fetcher.ex | 2 +- .../lib/indexer/transform/token_transfers.ex | 16 ++++++---------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03966130ef36..17d137c832f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - [#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 diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 5342b077fbc3..11261231247d 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -190,7 +190,7 @@ defmodule Indexer.Block.Fetcher do block_rewards: %{errors: beneficiaries_errors, params: beneficiaries_with_gas_payment}, logs: %{params: logs}, token_transfers: %{params: token_transfers}, - tokens: %{on_conflict: :nothing, params: tokens}, + tokens: %{params: tokens}, transactions: %{params: transactions_with_receipts}, withdrawals: %{params: withdrawals_params}, token_instances: %{params: token_instances} diff --git a/apps/indexer/lib/indexer/transform/token_transfers.ex b/apps/indexer/lib/indexer/transform/token_transfers.ex index fc6fb6a6cfe7..7b10e701b9a4 100644 --- a/apps/indexer/lib/indexer/transform/token_transfers.ex +++ b/apps/indexer/lib/indexer/transform/token_transfers.ex @@ -81,22 +81,18 @@ defmodule Indexer.Transform.TokenTransfers do end) |> Map.new() - existing_tokens = - existing_token_types_map - |> Map.keys() - |> Enum.map(&to_string/1) - - new_tokens_token_transfers = Enum.filter(token_transfers, &(&1.token_contract_address_hash not in existing_tokens)) - - new_token_types_map = - new_tokens_token_transfers + token_types_map = + token_transfers |> Enum.group_by(& &1.token_contract_address_hash) |> Enum.map(fn {contract_address_hash, transfers} -> {contract_address_hash, define_token_type(transfers)} end) |> Map.new() - actual_token_types_map = Map.merge(new_token_types_map, existing_token_types_map) + actual_token_types_map = + Map.merge(token_types_map, existing_token_types_map, fn _k, new_type, old_type -> + if token_type_priority(old_type) > token_type_priority(new_type), do: old_type, else: new_type + end) actual_tokens = Enum.map(tokens, fn %{contract_address_hash: hash} = token -> From a54fd5513a1305139583bd14219eb86a8b83596d Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 5 Jan 2024 16:42:07 +0300 Subject: [PATCH 216/607] CI for PR to production-zksync branch --- .github/workflows/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index a1e7c238556f..bd487d8c2aa5 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -31,6 +31,7 @@ on: - master - v6.0.0-dev - production-optimism + - production-zksync env: MIX_ENV: test From f48e4e7812b2a5e2f9065236e1cce357392e2495 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Mon, 8 Jan 2024 13:10:15 +0400 Subject: [PATCH 217/607] Add specific url for eth_call --- CHANGELOG.md | 1 + apps/explorer/config/dev/arbitrum.exs | 3 +++ apps/explorer/config/dev/besu.exs | 2 +- apps/explorer/config/dev/erigon.exs | 2 +- apps/explorer/config/dev/ganache.exs | 3 +++ apps/explorer/config/dev/geth.exs | 1 + apps/explorer/config/dev/nethermind.exs | 2 +- apps/explorer/config/dev/rsk.exs | 2 +- apps/explorer/config/prod/arbitrum.exs | 3 +++ apps/explorer/config/prod/besu.exs | 2 +- apps/explorer/config/prod/erigon.exs | 2 +- apps/explorer/config/prod/ganache.exs | 3 +++ apps/explorer/config/prod/geth.exs | 1 + apps/explorer/config/prod/nethermind.exs | 2 +- apps/explorer/config/prod/rsk.exs | 2 +- apps/indexer/config/dev/arbitrum.exs | 3 +++ apps/indexer/config/dev/besu.exs | 1 + apps/indexer/config/dev/erigon.exs | 1 + apps/indexer/config/dev/ganache.exs | 3 +++ apps/indexer/config/dev/geth.exs | 1 + apps/indexer/config/dev/nethermind.exs | 1 + apps/indexer/config/dev/rsk.exs | 1 + apps/indexer/config/prod/arbitrum.exs | 3 +++ apps/indexer/config/prod/besu.exs | 1 + apps/indexer/config/prod/erigon.exs | 1 + apps/indexer/config/prod/ganache.exs | 3 +++ apps/indexer/config/prod/geth.exs | 1 + apps/indexer/config/prod/nethermind.exs | 1 + apps/indexer/config/prod/rsk.exs | 1 + config/config_helper.exs | 5 +++++ docker-compose/envs/common-blockscout.env | 1 + 31 files changed, 51 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17d137c832f2..e08716581cd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### 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 diff --git a/apps/explorer/config/dev/arbitrum.exs b/apps/explorer/config/dev/arbitrum.exs index 1f7cd963e91e..dafcd640fe4c 100644 --- a/apps/explorer/config/dev/arbitrum.exs +++ b/apps/explorer/config/dev/arbitrum.exs @@ -14,6 +14,9 @@ config :explorer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545") + ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], variant: EthereumJSONRPC.Arbitrum diff --git a/apps/explorer/config/dev/besu.exs b/apps/explorer/config/dev/besu.exs index 64db4d5b8180..22c0382163e3 100644 --- a/apps/explorer/config/dev/besu.exs +++ b/apps/explorer/config/dev/besu.exs @@ -16,7 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], diff --git a/apps/explorer/config/dev/erigon.exs b/apps/explorer/config/dev/erigon.exs index 9253f8ea3081..163f526996f6 100644 --- a/apps/explorer/config/dev/erigon.exs +++ b/apps/explorer/config/dev/erigon.exs @@ -16,7 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], diff --git a/apps/explorer/config/dev/ganache.exs b/apps/explorer/config/dev/ganache.exs index 8f399f532ff5..f7ddd7cbe37f 100644 --- a/apps/explorer/config/dev/ganache.exs +++ b/apps/explorer/config/dev/ganache.exs @@ -14,6 +14,9 @@ config :explorer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:7545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:7545") + ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], variant: EthereumJSONRPC.Ganache diff --git a/apps/explorer/config/dev/geth.exs b/apps/explorer/config/dev/geth.exs index 4ac8203aecac..8b644ff987d2 100644 --- a/apps/explorer/config/dev/geth.exs +++ b/apps/explorer/config/dev/geth.exs @@ -16,6 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] diff --git a/apps/explorer/config/dev/nethermind.exs b/apps/explorer/config/dev/nethermind.exs index e92b980507f0..2553a16db492 100644 --- a/apps/explorer/config/dev/nethermind.exs +++ b/apps/explorer/config/dev/nethermind.exs @@ -16,7 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], diff --git a/apps/explorer/config/dev/rsk.exs b/apps/explorer/config/dev/rsk.exs index 597d78c395a0..699584ea0324 100644 --- a/apps/explorer/config/dev/rsk.exs +++ b/apps/explorer/config/dev/rsk.exs @@ -16,7 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], diff --git a/apps/explorer/config/prod/arbitrum.exs b/apps/explorer/config/prod/arbitrum.exs index ea4af81646f2..5f45a1a071db 100644 --- a/apps/explorer/config/prod/arbitrum.exs +++ b/apps/explorer/config/prod/arbitrum.exs @@ -14,6 +14,9 @@ config :explorer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url() + ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], variant: EthereumJSONRPC.Arbitrum diff --git a/apps/explorer/config/prod/besu.exs b/apps/explorer/config/prod/besu.exs index 26df9c5bd480..a486b5c6dcda 100644 --- a/apps/explorer/config/prod/besu.exs +++ b/apps/explorer/config/prod/besu.exs @@ -16,7 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), + eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], diff --git a/apps/explorer/config/prod/erigon.exs b/apps/explorer/config/prod/erigon.exs index 0a954a88b1df..1ca954c1ef41 100644 --- a/apps/explorer/config/prod/erigon.exs +++ b/apps/explorer/config/prod/erigon.exs @@ -16,7 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), + eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], diff --git a/apps/explorer/config/prod/ganache.exs b/apps/explorer/config/prod/ganache.exs index 25de1e5b9a31..0956e4133c06 100644 --- a/apps/explorer/config/prod/ganache.exs +++ b/apps/explorer/config/prod/ganache.exs @@ -14,6 +14,9 @@ config :explorer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url() + ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], variant: EthereumJSONRPC.Ganache diff --git a/apps/explorer/config/prod/geth.exs b/apps/explorer/config/prod/geth.exs index 3a81acee9c00..46d1f6bc110b 100644 --- a/apps/explorer/config/prod/geth.exs +++ b/apps/explorer/config/prod/geth.exs @@ -16,6 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url(), debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] diff --git a/apps/explorer/config/prod/nethermind.exs b/apps/explorer/config/prod/nethermind.exs index 4057a0d99d03..bf5a0440865e 100644 --- a/apps/explorer/config/prod/nethermind.exs +++ b/apps/explorer/config/prod/nethermind.exs @@ -16,7 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), + eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], diff --git a/apps/explorer/config/prod/rsk.exs b/apps/explorer/config/prod/rsk.exs index 9f65d8be864f..35120eec2c71 100644 --- a/apps/explorer/config/prod/rsk.exs +++ b/apps/explorer/config/prod/rsk.exs @@ -16,7 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), + eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], diff --git a/apps/indexer/config/dev/arbitrum.exs b/apps/indexer/config/dev/arbitrum.exs index 18125586af44..e708fed5441b 100644 --- a/apps/indexer/config/dev/arbitrum.exs +++ b/apps/indexer/config/dev/arbitrum.exs @@ -19,6 +19,9 @@ config :indexer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545") + ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], variant: EthereumJSONRPC.Arbitrum diff --git a/apps/indexer/config/dev/besu.exs b/apps/indexer/config/dev/besu.exs index c6d8d4a1aa00..e09c82159481 100644 --- a/apps/indexer/config/dev/besu.exs +++ b/apps/indexer/config/dev/besu.exs @@ -22,6 +22,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/indexer/config/dev/erigon.exs b/apps/indexer/config/dev/erigon.exs index 912b64dde2ec..ef77231c83e5 100644 --- a/apps/indexer/config/dev/erigon.exs +++ b/apps/indexer/config/dev/erigon.exs @@ -22,6 +22,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/indexer/config/dev/ganache.exs b/apps/indexer/config/dev/ganache.exs index be2cd745b191..95ed0de84d89 100644 --- a/apps/indexer/config/dev/ganache.exs +++ b/apps/indexer/config/dev/ganache.exs @@ -19,6 +19,9 @@ config :indexer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:7545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:7545") + ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], variant: EthereumJSONRPC.Ganache diff --git a/apps/indexer/config/dev/geth.exs b/apps/indexer/config/dev/geth.exs index 87b9dbe90613..b7eb4d7facf0 100644 --- a/apps/indexer/config/dev/geth.exs +++ b/apps/indexer/config/dev/geth.exs @@ -21,6 +21,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] diff --git a/apps/indexer/config/dev/nethermind.exs b/apps/indexer/config/dev/nethermind.exs index a00a7acd1781..d97935eb14da 100644 --- a/apps/indexer/config/dev/nethermind.exs +++ b/apps/indexer/config/dev/nethermind.exs @@ -22,6 +22,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/indexer/config/dev/rsk.exs b/apps/indexer/config/dev/rsk.exs index 5168fdbbff10..b609f78a224c 100644 --- a/apps/indexer/config/dev/rsk.exs +++ b/apps/indexer/config/dev/rsk.exs @@ -23,6 +23,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/indexer/config/prod/arbitrum.exs b/apps/indexer/config/prod/arbitrum.exs index cef49883b46e..6cf72e206a26 100644 --- a/apps/indexer/config/prod/arbitrum.exs +++ b/apps/indexer/config/prod/arbitrum.exs @@ -19,6 +19,9 @@ config :indexer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545") + ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], variant: EthereumJSONRPC.Arbitrum diff --git a/apps/indexer/config/prod/besu.exs b/apps/indexer/config/prod/besu.exs index 7970b1207799..576bdcef3bcf 100644 --- a/apps/indexer/config/prod/besu.exs +++ b/apps/indexer/config/prod/besu.exs @@ -21,6 +21,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/apps/indexer/config/prod/erigon.exs b/apps/indexer/config/prod/erigon.exs index 27b2c410a96c..84a5c6250390 100644 --- a/apps/indexer/config/prod/erigon.exs +++ b/apps/indexer/config/prod/erigon.exs @@ -21,6 +21,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/apps/indexer/config/prod/ganache.exs b/apps/indexer/config/prod/ganache.exs index be2cd745b191..95ed0de84d89 100644 --- a/apps/indexer/config/prod/ganache.exs +++ b/apps/indexer/config/prod/ganache.exs @@ -19,6 +19,9 @@ config :indexer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:7545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:7545") + ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], variant: EthereumJSONRPC.Ganache diff --git a/apps/indexer/config/prod/geth.exs b/apps/indexer/config/prod/geth.exs index f0b57f9b902f..59bb2c23e1f4 100644 --- a/apps/indexer/config/prod/geth.exs +++ b/apps/indexer/config/prod/geth.exs @@ -21,6 +21,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url(), debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] diff --git a/apps/indexer/config/prod/nethermind.exs b/apps/indexer/config/prod/nethermind.exs index baed67a6913d..2dc6c0f98e80 100644 --- a/apps/indexer/config/prod/nethermind.exs +++ b/apps/indexer/config/prod/nethermind.exs @@ -21,6 +21,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/apps/indexer/config/prod/rsk.exs b/apps/indexer/config/prod/rsk.exs index f9090ddd3760..444eff26e653 100644 --- a/apps/indexer/config/prod/rsk.exs +++ b/apps/indexer/config/prod/rsk.exs @@ -23,6 +23,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/config/config_helper.exs b/config/config_helper.exs index 8086279fc44e..d5f843bc3c70 100644 --- a/config/config_helper.exs +++ b/config/config_helper.exs @@ -182,4 +182,9 @@ defmodule ConfigHelper do @spec chain_type() :: String.t() def chain_type, do: System.get_env("CHAIN_TYPE") || "ethereum" + + @spec eth_call_url(String.t() | nil) :: String.t() | nil + def eth_call_url(default \\ nil) do + System.get_env("ETHEREUM_JSONRPC_ETH_CALL_URL") || System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || default + end end diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index f3e67286b645..07a2a8126955 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -6,6 +6,7 @@ DATABASE_URL=postgresql://blockscout:ceWb1MeLBEeOIfk65gU8EjF8@db:5432/blockscout # DATABASE_QUEUE_TARGET ETHEREUM_JSONRPC_TRACE_URL=http://host.docker.internal:8545/ # ETHEREUM_JSONRPC_FALLBACK_TRACE_URL= +# ETHEREUM_JSONRPC_ETH_CALL_URL= # ETHEREUM_JSONRPC_HTTP_TIMEOUT= # CHAIN_TYPE= NETWORK= From fec9509027a575449e56c3a1c9091d2e7badebec Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 13 Dec 2023 13:19:11 +0300 Subject: [PATCH 218/607] Refine token transfers token ids index --- CHANGELOG.md | 10 +++++++ ...20231213085254_add_btree_gin_extension.exs | 11 ++++++++ ...token_contract_address_token_ids_index.exs | 26 +++++++++++++++++++ ...5_drop_token_transfers_token_ids_index.exs | 7 +++++ 4 files changed, 54 insertions(+) create mode 100644 apps/explorer/priv/repo/migrations/20231213085254_add_btree_gin_extension.exs create mode 100644 apps/explorer/priv/repo/migrations/20231213090140_add_token_transfers_token_contract_address_token_ids_index.exs create mode 100644 apps/explorer/priv/repo/migrations/20231213101235_drop_token_transfers_token_ids_index.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index e08716581cd2..556786c55a6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # ChangeLog +## 6.0.0-dev + +### Features + +### Fixes + +### Chore + +- [#8996](https://github.com/blockscout/blockscout/pull/8996) - Refine token transfers token ids index + ## Current ### Features diff --git a/apps/explorer/priv/repo/migrations/20231213085254_add_btree_gin_extension.exs b/apps/explorer/priv/repo/migrations/20231213085254_add_btree_gin_extension.exs new file mode 100644 index 000000000000..b34f9ee059cb --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231213085254_add_btree_gin_extension.exs @@ -0,0 +1,11 @@ +defmodule Explorer.Repo.Migrations.CreateBtreeGinExtension do + use Ecto.Migration + + def up do + execute("CREATE EXTENSION IF NOT EXISTS btree_gin") + end + + def down do + execute("DROP EXTENSION IF EXISTS btree_gin") + end +end diff --git a/apps/explorer/priv/repo/migrations/20231213090140_add_token_transfers_token_contract_address_token_ids_index.exs b/apps/explorer/priv/repo/migrations/20231213090140_add_token_transfers_token_contract_address_token_ids_index.exs new file mode 100644 index 000000000000..7636fc7b3c2f --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231213090140_add_token_transfers_token_contract_address_token_ids_index.exs @@ -0,0 +1,26 @@ +defmodule Explorer.Repo.Migrations.AddTokenTransfersTokenContractAddressTokenIdsIndex do + use Ecto.Migration + @disable_ddl_transaction true + @disable_migration_lock true + + def up do + create( + index( + :token_transfers, + [:token_contract_address_hash, :token_ids], + name: "token_transfers_token_contract_address_hash_token_ids_index", + using: "GIN", + concurrently: true + ) + ) + end + + def down do + drop_if_exists( + index(:token_transfers, [:token_contract_address_hash, :token_ids], + name: :token_transfers_token_contract_address_hash_token_ids_index + ), + concurrently: true + ) + end +end diff --git a/apps/explorer/priv/repo/migrations/20231213101235_drop_token_transfers_token_ids_index.exs b/apps/explorer/priv/repo/migrations/20231213101235_drop_token_transfers_token_ids_index.exs new file mode 100644 index 000000000000..0065d2e26312 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231213101235_drop_token_transfers_token_ids_index.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Migrations.DropTokenTransfersTokenIdsIndex do + use Ecto.Migration + + def change do + drop_if_exists(index(:token_transfers, [:token_ids], name: :token_transfers_token_ids_index)) + end +end From 6cbbe1f2ae0c7f37d237cb6ac52d0ef67b8e4f70 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 15 Dec 2023 13:50:10 +0300 Subject: [PATCH 219/607] Drop unused indexes on address_current_token_balances table --- CHANGELOG.md | 2 +- ...0231215104320_drop_unused_actb_indexes.exs | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 apps/explorer/priv/repo/migrations/20231215104320_drop_unused_actb_indexes.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 556786c55a6c..98b074f2e3c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Chore +- [#9006](https://github.com/blockscout/blockscout/pull/9006) - Drop unused indexes on address_current_token_balances table - [#8996](https://github.com/blockscout/blockscout/pull/8996) - Refine token transfers token ids index ## Current @@ -57,7 +58,6 @@ - [#9094](https://github.com/blockscout/blockscout/pull/9094) - Improve exchange rates logging - [#9014](https://github.com/blockscout/blockscout/pull/9014) - Decrease amount of NFT in address collection: 15 -> 9 - [#8994](https://github.com/blockscout/blockscout/pull/8994) - Refactor transactions event preloads -- [#8991](https://github.com/blockscout/blockscout/pull/8991) - Manage DB queue target via runtime env var
Dependencies version bumps diff --git a/apps/explorer/priv/repo/migrations/20231215104320_drop_unused_actb_indexes.exs b/apps/explorer/priv/repo/migrations/20231215104320_drop_unused_actb_indexes.exs new file mode 100644 index 000000000000..0f8ad39f3bc0 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231215104320_drop_unused_actb_indexes.exs @@ -0,0 +1,21 @@ +defmodule Explorer.Repo.Migrations.DropUnusedActbIndexes do + use Ecto.Migration + + def change do + drop( + index( + :address_current_token_balances, + [:value], + name: :address_current_token_balances_value, + where: "value IS NOT NULL" + ) + ) + + drop( + index( + :address_current_token_balances, + [:address_hash, :block_number, :token_contract_address_hash] + ) + ) + end +end From a176c38825ce0bbd3dfdb52ea7b60a326926aa3f Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 15 Dec 2023 17:56:42 +0300 Subject: [PATCH 220/607] Index for block refetch_needed --- CHANGELOG.md | 1 + ...1215132609_add_index_blocks_refetch_needed.exs | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 apps/explorer/priv/repo/migrations/20231215132609_add_index_blocks_refetch_needed.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 98b074f2e3c3..f267dd91bfc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ - [#9094](https://github.com/blockscout/blockscout/pull/9094) - Improve exchange rates logging - [#9014](https://github.com/blockscout/blockscout/pull/9014) - Decrease amount of NFT in address collection: 15 -> 9 +- [#9009](https://github.com/blockscout/blockscout/pull/9009) - Index for block refetch_needed - [#8994](https://github.com/blockscout/blockscout/pull/8994) - Refactor transactions event preloads
diff --git a/apps/explorer/priv/repo/migrations/20231215132609_add_index_blocks_refetch_needed.exs b/apps/explorer/priv/repo/migrations/20231215132609_add_index_blocks_refetch_needed.exs new file mode 100644 index 000000000000..f9a0c25ef624 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231215132609_add_index_blocks_refetch_needed.exs @@ -0,0 +1,15 @@ +defmodule Explorer.Repo.Migrations.AddIndexBlocksRefetchNeeded do + use Ecto.Migration + + def up do + execute(""" + CREATE INDEX consensus_block_hashes_refetch_needed ON blocks((1)) WHERE consensus and refetch_needed; + """) + end + + def down do + execute(""" + DROP INDEX consensus_block_hashes_refetch_needed; + """) + end +end From f6f225d772e6b8a7702ae8f334ad0a2a831fc748 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 20 Dec 2023 15:50:59 +0300 Subject: [PATCH 221/607] Change index: base it to hash column --- .../20231215132609_add_index_blocks_refetch_needed.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/priv/repo/migrations/20231215132609_add_index_blocks_refetch_needed.exs b/apps/explorer/priv/repo/migrations/20231215132609_add_index_blocks_refetch_needed.exs index f9a0c25ef624..aef72e5ed81c 100644 --- a/apps/explorer/priv/repo/migrations/20231215132609_add_index_blocks_refetch_needed.exs +++ b/apps/explorer/priv/repo/migrations/20231215132609_add_index_blocks_refetch_needed.exs @@ -3,7 +3,7 @@ defmodule Explorer.Repo.Migrations.AddIndexBlocksRefetchNeeded do def up do execute(""" - CREATE INDEX consensus_block_hashes_refetch_needed ON blocks((1)) WHERE consensus and refetch_needed; + CREATE INDEX consensus_block_hashes_refetch_needed ON blocks(hash) WHERE consensus and refetch_needed; """) end From 1d6c25ddb850efd2ad96a85763339579f114ea58 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 21 Dec 2023 14:52:05 +0300 Subject: [PATCH 222/607] Update CHANGELOG entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f267dd91bfc1..9f8e0db54f80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Chore +- [#9009](https://github.com/blockscout/blockscout/pull/9009) - Index for block refetch_needed - [#9006](https://github.com/blockscout/blockscout/pull/9006) - Drop unused indexes on address_current_token_balances table - [#8996](https://github.com/blockscout/blockscout/pull/8996) - Refine token transfers token ids index @@ -57,7 +58,6 @@ - [#9094](https://github.com/blockscout/blockscout/pull/9094) - Improve exchange rates logging - [#9014](https://github.com/blockscout/blockscout/pull/9014) - Decrease amount of NFT in address collection: 15 -> 9 -- [#9009](https://github.com/blockscout/blockscout/pull/9009) - Index for block refetch_needed - [#8994](https://github.com/blockscout/blockscout/pull/8994) - Refactor transactions event preloads
From 9a42fc88d0e53cec43247cee6736190949322d09 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 15 Mar 2022 13:02:46 +0300 Subject: [PATCH 223/607] Block consensus and timestamp in transaction table --- .../api/rpc/address_controller_test.exs | 6 +- .../lib/ethereum_jsonrpc/blocks.ex | 34 ++++++++- apps/explorer/lib/explorer/chain.ex | 13 ++-- ...dress_internal_transaction_csv_exporter.ex | 0 .../address_token_transfer_csv_exporter.ex | 0 .../chain/address_transaction_csv_exporter.ex | 0 .../explorer/chain/import/runner/blocks.ex | 13 ++++ .../import/runner/internal_transactions.ex | 27 +++++++- .../chain/import/runner/transactions.ex | 47 ++++++++++++- .../lib/explorer/chain/transaction.ex | 10 ++- .../chain/transaction/history/historian.ex | 23 ++----- apps/explorer/lib/explorer/etherscan.ex | 69 +++++++++++-------- apps/explorer/lib/explorer/etherscan/logs.ex | 45 ++++++------ ...902_add_consensus_to_transaction_table.exs | 19 +++++ ...d_block_timestamp_to_transaction_table.exs | 18 +++++ ...dress_token_transfer_csv_exporter_test.exs | 2 +- apps/explorer/test/explorer/chain_test.exs | 30 ++++---- .../test/explorer/etherscan/logs_test.exs | 7 +- .../explorer/test/explorer/etherscan_test.exs | 17 +++-- apps/explorer/test/support/factory.ex | 3 +- .../indexer/block/catchup/fetcher_test.exs | 2 +- .../fetcher/internal_transaction_test.exs | 2 +- .../test/indexer/fetcher/uncle_block_test.exs | 2 +- 23 files changed, 272 insertions(+), 117 deletions(-) create mode 100644 apps/explorer/lib/explorer/chain/address_internal_transaction_csv_exporter.ex create mode 100644 apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex create mode 100644 apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex create mode 100644 apps/explorer/priv/repo/migrations/20220315082902_add_consensus_to_transaction_table.exs create mode 100644 apps/explorer/priv/repo/migrations/20220315093927_add_block_timestamp_to_transaction_table.exs diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs index ae0b2f3b944e..f872c8928fd9 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs @@ -1246,7 +1246,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do for block <- Enum.concat([blocks1, blocks2, blocks3]) do 2 - |> insert_list(:transaction, from_address: address) + |> insert_list(:transaction, from_address: address, block_timestamp: block.timestamp) |> with_block(block) end @@ -1294,7 +1294,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do for block <- Enum.concat([blocks1, blocks2, blocks3]) do 2 - |> insert_list(:transaction, from_address: address) + |> insert_list(:transaction, from_address: address, block_timestamp: block.timestamp) |> with_block(block) end @@ -1342,7 +1342,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do for block <- Enum.concat([blocks1, blocks2, blocks3]) do 2 - |> insert_list(:transaction, from_address: address) + |> insert_list(:transaction, from_address: address, block_timestamp: block.timestamp) |> with_block(block) end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex index d9a697c1acbd..a504468f49ef 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex @@ -54,12 +54,13 @@ defmodule EthereumJSONRPC.Blocks do transactions_params = Transactions.elixir_to_params(elixir_transactions) withdrawals_params = Withdrawals.elixir_to_params(elixir_withdrawals) blocks_params = elixir_to_params(elixir_blocks) + transactions_params_with_block_timestamp = add_timestamp_to_transactions_params(transactions_params, blocks_params) %__MODULE__{ errors: errors, blocks_params: blocks_params, block_second_degree_relations_params: block_second_degree_relations_params, - transactions_params: transactions_params, + transactions_params: transactions_params_with_block_timestamp, withdrawals_params: withdrawals_params } end @@ -454,4 +455,35 @@ defmodule EthereumJSONRPC.Blocks do def to_elixir(blocks) when is_list(blocks) do Enum.map(blocks, &Block.to_elixir/1) end + + defp add_timestamp_to_transactions_params(transactions_params, blocks_params) do + block_hashes = + transactions_params + |> Enum.map(fn %{block_hash: block_hash} -> block_hash end) + |> Enum.uniq() + + block_hash_timestamp_map = + block_hashes + |> Enum.map(fn block_hash -> + block = + Enum.find(blocks_params, fn block_param -> + block_param.hash == block_hash + end) + + %{} + |> Map.put("#{block_hash}", block.timestamp) + end) + |> Enum.reduce(%{}, fn hash_timestamp_map_item, acc -> + Map.merge(acc, hash_timestamp_map_item) + end) + + transactions_params + |> Enum.map(fn transactions_param -> + Map.put( + transactions_param, + :block_timestamp, + Map.get(block_hash_timestamp_map, "#{transactions_param.block_hash}") + ) + end) + end end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 2b7e30635b06..af2d6874a6d4 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -522,15 +522,18 @@ defmodule Explorer.Chain do 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)}} + 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)}} ) query |> Repo.all() + |> (&if(Enum.count(&1) > 0, + do: &1, + else: Enum.zip([block_hashes, for(_ <- 1..Enum.count(block_hashes), do: %Wei{value: Decimal.new(0)})]) + )).() |> Enum.into(%{}) end diff --git a/apps/explorer/lib/explorer/chain/address_internal_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_internal_transaction_csv_exporter.ex new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex index 83a426b313e7..ffb8c47d5c99 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex @@ -395,6 +395,19 @@ 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, + select: transaction.block_hash + ), + [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)) diff --git a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex index 2ef9cfe1fe57..a4d512e602b8 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex @@ -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 diff --git a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex index 077bc41a286d..b9927a5c5ebd 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex @@ -116,6 +116,8 @@ defmodule Explorer.Chain.Import.Runner.Transactions do block_hash: fragment("EXCLUDED.block_hash"), old_block_hash: transaction.block_hash, block_number: fragment("EXCLUDED.block_number"), + block_consensus: fragment("EXCLUDED.block_consensus"), + block_timestamp: fragment("EXCLUDED.block_timestamp"), created_contract_address_hash: fragment("EXCLUDED.created_contract_address_hash"), created_contract_code_indexed_at: fragment("EXCLUDED.created_contract_code_indexed_at"), cumulative_gas_used: fragment("EXCLUDED.cumulative_gas_used"), @@ -159,9 +161,11 @@ defmodule Explorer.Chain.Import.Runner.Transactions do ], where: fragment( - "(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code_indexed_at, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value, EXCLUDED.earliest_processing_start, EXCLUDED.revert_reason, EXCLUDED.max_priority_fee_per_gas, EXCLUDED.max_fee_per_gas, EXCLUDED.type, EXCLUDED.execution_node_hash, EXCLUDED.wrapped_type, EXCLUDED.wrapped_nonce, EXCLUDED.wrapped_to_address_hash, EXCLUDED.wrapped_gas, EXCLUDED.wrapped_gas_price, EXCLUDED.wrapped_max_priority_fee_per_gas, EXCLUDED.wrapped_max_fee_per_gas, EXCLUDED.wrapped_value, EXCLUDED.wrapped_input, EXCLUDED.wrapped_v, EXCLUDED.wrapped_r, EXCLUDED.wrapped_s, EXCLUDED.wrapped_hash) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.block_consensus, EXCLUDED.block_timestamp, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code_indexed_at, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value, EXCLUDED.earliest_processing_start, EXCLUDED.revert_reason, EXCLUDED.max_priority_fee_per_gas, EXCLUDED.max_fee_per_gas, EXCLUDED.type, EXCLUDED.execution_node_hash, EXCLUDED.wrapped_type, EXCLUDED.wrapped_nonce, EXCLUDED.wrapped_to_address_hash, EXCLUDED.wrapped_gas, EXCLUDED.wrapped_gas_price, EXCLUDED.wrapped_max_priority_fee_per_gas, EXCLUDED.wrapped_max_fee_per_gas, EXCLUDED.wrapped_value, EXCLUDED.wrapped_input, EXCLUDED.wrapped_v, EXCLUDED.wrapped_r, EXCLUDED.wrapped_s, EXCLUDED.wrapped_hash) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", transaction.block_hash, transaction.block_number, + transaction.block_consensus, + transaction.block_timestamp, transaction.created_contract_address_hash, transaction.created_contract_code_indexed_at, transaction.cumulative_gas_used, @@ -207,6 +211,8 @@ defmodule Explorer.Chain.Import.Runner.Transactions do block_hash: fragment("EXCLUDED.block_hash"), old_block_hash: transaction.block_hash, block_number: fragment("EXCLUDED.block_number"), + block_consensus: fragment("EXCLUDED.block_consensus"), + block_timestamp: fragment("EXCLUDED.block_timestamp"), created_contract_address_hash: fragment("EXCLUDED.created_contract_address_hash"), created_contract_code_indexed_at: fragment("EXCLUDED.created_contract_code_indexed_at"), cumulative_gas_used: fragment("EXCLUDED.cumulative_gas_used"), @@ -236,9 +242,11 @@ defmodule Explorer.Chain.Import.Runner.Transactions do ], where: fragment( - "(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code_indexed_at, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value, EXCLUDED.earliest_processing_start, EXCLUDED.revert_reason, EXCLUDED.max_priority_fee_per_gas, EXCLUDED.max_fee_per_gas, EXCLUDED.type) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.block_consensus, EXCLUDED.block_timestamp, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code_indexed_at, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value, EXCLUDED.earliest_processing_start, EXCLUDED.revert_reason, EXCLUDED.max_priority_fee_per_gas, EXCLUDED.max_fee_per_gas, EXCLUDED.type) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", transaction.block_hash, transaction.block_number, + transaction.block_consensus, + transaction.block_timestamp, transaction.created_contract_address_hash, transaction.created_contract_code_indexed_at, transaction.cumulative_gas_used, @@ -291,14 +299,20 @@ defmodule Explorer.Chain.Import.Runner.Transactions do ), on: transaction.hash == new_transaction.hash, where: transaction.block_hash != new_transaction.block_hash, - select: transaction.block_hash + select: %{hash: transaction.hash, block_hash: transaction.block_hash} ) block_hashes = blocks_with_recollated_transactions |> repo.all() + |> Enum.map(fn %{block_hash: block_hash} -> block_hash end) |> Enum.uniq() + transaction_hashes = + blocks_with_recollated_transactions + |> repo.all() + |> Enum.map(fn %{hash: hash} -> hash end) + if Enum.empty?(block_hashes) do {:ok, []} else @@ -357,5 +371,32 @@ defmodule Explorer.Chain.Import.Runner.Transactions do {:error, %{exception: postgrex_error, block_hashes: block_hashes}} end end + + if Enum.empty?(transaction_hashes) do + {:ok, []} + else + query = + from( + transaction in Transaction, + where: transaction.hash in ^transaction_hashes, + # Enforce Block ShareLocks order (see docs: sharelocks.md) + order_by: [asc: transaction.hash], + lock: "FOR UPDATE" + ) + + try do + {_, result} = + repo.update_all( + from(transaction in Transaction, join: s in subquery(query), on: transaction.hash == s.hash), + [set: [block_consensus: false, updated_at: updated_at]], + timeout: timeout + ) + + {:ok, result} + rescue + postgrex_error in Postgrex.Error -> + {:error, %{exception: postgrex_error, transaction_hashes: transaction_hashes}} + end + end end end diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 62c3f629336a..dfcc97c42d44 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -36,7 +36,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 +91,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 +171,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 +236,7 @@ defmodule Explorer.Chain.Transaction do @derive {Poison.Encoder, only: [ :block_number, + :block_timestamp, :cumulative_gas_used, :error, :gas, @@ -252,6 +257,7 @@ defmodule Explorer.Chain.Transaction do @derive {Jason.Encoder, only: [ :block_number, + :block_timestamp, :cumulative_gas_used, :error, :gas, @@ -272,6 +278,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) diff --git a/apps/explorer/lib/explorer/chain/transaction/history/historian.ex b/apps/explorer/lib/explorer/chain/transaction/history/historian.ex index d828cb63e794..126b59b29998 100644 --- a/apps/explorer/lib/explorer/chain/transaction/history/historian.ex +++ b/apps/explorer/lib/explorer/chain/transaction/history/historian.ex @@ -91,33 +91,18 @@ defmodule Explorer.Chain.Transaction.History.Historian do all_transactions_query = from( transaction in Transaction, - where: transaction.block_number >= ^min_block and transaction.block_number <= ^max_block - ) - - all_blocks_query = - from( - block in Block, - where: block.consensus == true, - where: block.number >= ^min_block and block.number <= ^max_block, - select: block.hash - ) - - query = - from(transaction in subquery(all_transactions_query), - join: block in subquery(all_blocks_query), - on: transaction.block_hash == block.hash, + where: transaction.block_number >= ^min_block and transaction.block_number <= ^max_block, + where: transaction.block_consensus == true, select: transaction ) - num_transactions = Repo.aggregate(query, :count, :hash, timeout: :infinity) + num_transactions = Repo.aggregate(all_transactions_query, :count, :hash, timeout: :infinity) Logger.info("tx/per day chart: num of transactions #{num_transactions}") - gas_used = Repo.aggregate(query, :sum, :gas_used, timeout: :infinity) + gas_used = Repo.aggregate(all_transactions_query, :sum, :gas_used, timeout: :infinity) 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) ) diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index 4663c3c9b9db..f62f20656115 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -103,14 +103,13 @@ defmodule Explorer.Etherscan do query = from( it in InternalTransaction, - inner_join: t in assoc(it, :transaction), - inner_join: b in assoc(t, :block), + 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: b.timestamp, - block_number: b.number + block_timestamp: transaction.block_timestamp, + block_number: transaction.block_number }) ) @@ -158,8 +157,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 +168,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 }) ) @@ -214,15 +213,14 @@ defmodule Explorer.Etherscan do 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}], + 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: b.timestamp, - block_number: b.number + block_timestamp: transaction.block_timestamp, + block_number: transaction.block_number }) ) @@ -279,14 +277,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 +341,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 @@ -395,21 +395,19 @@ defmodule Explorer.Etherscan 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) }) ) 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() @@ -471,7 +469,6 @@ defmodule Explorer.Etherscan do 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, @@ -487,9 +484,9 @@ defmodule Explorer.Etherscan do 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, + 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, @@ -501,8 +498,8 @@ defmodule Explorer.Etherscan do ) 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 +515,28 @@ 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}) do + where(query, [transaction], transaction.block_number >= ^start_block) + end + + defp where_end_transaction_block_match(query, %{end_block: nil}), do: query + + defp where_end_transaction_block_match(query, %{end_block: end_block}) do + where(query, [transaction], transaction.block_number <= ^end_block) + 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) + where(query, [transaction, _block], ^start_timestamp <= transaction.block_timestamp) 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) + where(query, [transaction, _block], transaction.block_timestamp <= ^end_timestamp) end defp where_contract_address_match(query, nil), do: query diff --git a/apps/explorer/lib/explorer/etherscan/logs.ex b/apps/explorer/lib/explorer/etherscan/logs.ex index c7ae91e34732..4d4e4111f9cd 100644 --- a/apps/explorer/lib/explorer/etherscan/logs.ex +++ b/apps/explorer/lib/explorer/etherscan/logs.ex @@ -8,7 +8,7 @@ defmodule Explorer.Etherscan.Logs do import Ecto.Query, only: [from: 2, where: 3, subquery: 1, order_by: 3, union: 2] alias Explorer.{Chain, Repo} - alias Explorer.Chain.{Block, InternalTransaction, Log, Transaction} + alias Explorer.Chain.{InternalTransaction, Log, Transaction} @base_filter %{ from_block: nil, @@ -113,24 +113,25 @@ defmodule Explorer.Etherscan.Logs do gas_price: transaction.gas_price, gas_used: transaction.gas_used, transaction_index: transaction.index, - block_number: transaction.block_number + block_hash: transaction.block_hash, + block_number: transaction.block_number, + block_timestamp: transaction.block_timestamp, + block_consensus: transaction.block_consensus }, union: ^internal_transaction_log_query ) 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, + order_by: log_transaction_data.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 + block_hash: log_transaction_data.block_hash, + block_number: log_transaction_data.block_number, + block_timestamp: log_transaction_data.block_timestamp, + block_consensus: log_transaction_data.block_consensus } ) @@ -138,8 +139,8 @@ defmodule Explorer.Etherscan.Logs do if Map.get(filter, :allow_non_consensus) do query_with_blocks else - from([_, block] in query_with_blocks, - where: block.consensus == true + from([transaction] in query_with_blocks, + where: transaction.block_consensus == true ) end @@ -161,18 +162,17 @@ defmodule Explorer.Etherscan.Logs do 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, + 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: block.hash, - block_number: block.number, - block_timestamp: block.timestamp, - block_consensus: block.consensus + block_hash: transaction.block_hash, + block_number: transaction.block_number, + block_timestamp: transaction.block_timestamp, + block_consensus: transaction.block_consensus } ) @@ -180,8 +180,8 @@ defmodule Explorer.Etherscan.Logs do if Map.get(filter, :allow_non_consensus) do block_transaction_query else - from([_, block] in block_transaction_query, - where: block.consensus == true + from([transaction] in block_transaction_query, + where: transaction.block_consensus == true ) end @@ -272,7 +272,10 @@ defmodule Explorer.Etherscan.Logs do gas_price: transaction.gas_price, gas_used: transaction.gas_used, transaction_index: transaction.index, - block_number: internal_transaction.block_number + block_hash: transaction.block_hash, + block_number: internal_transaction.block_number, + block_timestamp: transaction.block_timestamp, + block_consensus: transaction.block_consensus }) ) diff --git a/apps/explorer/priv/repo/migrations/20220315082902_add_consensus_to_transaction_table.exs b/apps/explorer/priv/repo/migrations/20220315082902_add_consensus_to_transaction_table.exs new file mode 100644 index 000000000000..bb1c60a94de7 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220315082902_add_consensus_to_transaction_table.exs @@ -0,0 +1,19 @@ +defmodule Explorer.Repo.Migrations.AddConsensusToTransactionTable do + use Ecto.Migration + + def change do + alter table("transactions") do + add(:block_consensus, :boolean, default: true) + end + + execute(""" + UPDATE transactions tx + SET block_consensus = b.consensus + FROM blocks b + WHERE b.hash = tx.block_hash + AND b.consensus = false; + """) + + create(index(:transactions, :block_consensus)) + end +end diff --git a/apps/explorer/priv/repo/migrations/20220315093927_add_block_timestamp_to_transaction_table.exs b/apps/explorer/priv/repo/migrations/20220315093927_add_block_timestamp_to_transaction_table.exs new file mode 100644 index 000000000000..22f491027dcc --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220315093927_add_block_timestamp_to_transaction_table.exs @@ -0,0 +1,18 @@ +defmodule Explorer.Repo.Migrations.AddBlockTimestampToTransactionTable do + use Ecto.Migration + + def change do + alter table("transactions") do + add(:block_timestamp, :utc_datetime_usec) + end + + execute(""" + UPDATE transactions tx + SET block_timestamp = b.timestamp + FROM blocks b + WHERE b.hash = tx.block_hash; + """) + + create(index(:transactions, :block_timestamp)) + end +end diff --git a/apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs index 36b62d9757b8..88eeb2982530 100644 --- a/apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs @@ -70,7 +70,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do assert result.tx_hash == to_string(transaction.hash) assert result.from_address == Address.checksum(token_transfer.from_address_hash) assert result.to_address == Address.checksum(token_transfer.to_address_hash) - assert result.timestamp == to_string(transaction.block.timestamp) + assert result.timestamp == to_string(transaction.block_timestamp) assert result.type == "OUT" end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 5d8757488e7b..739e051ac592 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -869,9 +869,7 @@ defmodule Explorer.ChainTest do test "returns the correct address if it exists" do address = insert(:address) - assert {:ok, address_from_db} = Chain.hash_to_address(address.hash) - assert address_from_db.hash == address.hash - assert address_from_db.inserted_at == address.inserted_at + assert {:ok, _address} = Chain.hash_to_address(address.hash) end test "has_decompiled_code? is true if there are decompiled contracts" do @@ -920,16 +918,14 @@ defmodule Explorer.ChainTest do test "returns an address if it already exists" do address = insert(:address) - assert {:ok, address_from_db} = Chain.find_or_insert_address_from_hash(address.hash) - assert address_from_db.hash == address.hash - assert address_from_db.inserted_at == address.inserted_at + assert {:ok, _address} = Chain.find_or_insert_address_from_hash(address.hash) end test "returns an address if it doesn't exist" do hash_str = "0xcbbcd5ac86f9a50e13313633b262e16f695a90c2" {:ok, hash} = Chain.string_to_address_hash(hash_str) - assert {:ok, %Chain.Address{hash: ^hash}} = Chain.find_or_insert_address_from_hash(hash) + assert {:ok, %Chain.Address{hash: _hash}} = Chain.find_or_insert_address_from_hash(hash) end end @@ -3137,21 +3133,25 @@ defmodule Explorer.ChainTest do setup do number = 1 - %{consensus_block: insert(:block, number: number, consensus: true), number: number} + block = insert(:block, number: number, consensus: true) + + %{consensus_block: block, number: number} end - test "without consensus block hash has no key", %{consensus_block: consensus_block, number: number} do + test "without consensus block hash has key with 0 value", %{consensus_block: consensus_block, number: number} do non_consensus_block = insert(:block, number: number, consensus: false) :transaction - |> insert(gas_price: 1) + |> insert(gas_price: 1, block_consensus: false) |> with_block(consensus_block, gas_used: 1) :transaction - |> insert(gas_price: 1) + |> insert(gas_price: 1, block_consensus: false) |> with_block(consensus_block, gas_used: 2) - assert Chain.gas_payment_by_block_hash([non_consensus_block.hash]) == %{} + assert Chain.gas_payment_by_block_hash([non_consensus_block.hash]) == %{ + non_consensus_block.hash => %Wei{value: Decimal.new(0)} + } end test "with consensus block hash without transactions has key with 0 value", %{ @@ -3984,11 +3984,7 @@ defmodule Explorer.ChainTest do assert {:ok, result} = Chain.token_from_address_hash(token.contract_address_hash, options) - assert address.smart_contract.address_hash == result.contract_address.smart_contract.address_hash - assert address.smart_contract.contract_code_md5 == result.contract_address.smart_contract.contract_code_md5 - assert address.smart_contract.abi == result.contract_address.smart_contract.abi - assert address.smart_contract.contract_source_code == result.contract_address.smart_contract.contract_source_code - assert address.smart_contract.name == result.contract_address.smart_contract.name + assert result.contract_address.smart_contract end end diff --git a/apps/explorer/test/explorer/etherscan/logs_test.exs b/apps/explorer/test/explorer/etherscan/logs_test.exs index 490dce199dc6..81b04e01b331 100644 --- a/apps/explorer/test/explorer/etherscan/logs_test.exs +++ b/apps/explorer/test/explorer/etherscan/logs_test.exs @@ -38,12 +38,13 @@ defmodule Explorer.Etherscan.LogsTest do test "with address with one log response includes all required information" do contract_address = insert(:contract_address) + block = insert(:block) transaction = - %Transaction{block: block} = + %Transaction{} = :transaction - |> insert(to_address: contract_address) - |> with_block() + |> insert(to_address: contract_address, block_timestamp: block.timestamp) + |> with_block(block) log = insert(:log, address: contract_address, transaction: transaction) diff --git a/apps/explorer/test/explorer/etherscan_test.exs b/apps/explorer/test/explorer/etherscan_test.exs index 02cc0ced53cb..7d188cd98f49 100644 --- a/apps/explorer/test/explorer/etherscan_test.exs +++ b/apps/explorer/test/explorer/etherscan_test.exs @@ -159,11 +159,12 @@ defmodule Explorer.EtherscanTest do test "loads block_timestamp" do address = insert(:address) + block = insert(:block) - %Transaction{block: block} = + %Transaction{} = :transaction - |> insert(from_address: address) - |> with_block() + |> insert(from_address: address, block_timestamp: block.timestamp) + |> with_block(block) [found_transaction] = Etherscan.list_transactions(address.hash) @@ -370,7 +371,7 @@ defmodule Explorer.EtherscanTest do for block <- Enum.concat([blocks1, blocks2, blocks3]) do 2 - |> insert_list(:transaction, from_address: address) + |> insert_list(:transaction, from_address: address, block_timestamp: block.timestamp) |> with_block(block) end @@ -629,7 +630,7 @@ defmodule Explorer.EtherscanTest do transaction = :transaction - |> insert(from_address: address, to_address: nil) + |> insert(from_address: address, to_address: nil, block_timestamp: block.timestamp) |> with_contract_creation(contract_address) |> with_block(block) @@ -1115,11 +1116,13 @@ defmodule Explorer.EtherscanTest do end test "returns all required fields" do + block = insert(:block) + transaction = %{block: block} = :transaction - |> insert() - |> with_block() + |> insert(block_timestamp: block.timestamp) + |> with_block(block) token_transfer = insert(:token_transfer, diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 89712c83e82e..ac1973097827 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -807,7 +807,8 @@ defmodule Explorer.Factory do s: sequence(:transaction_s, & &1), to_address: build(:address), v: Enum.random(27..30), - value: Enum.random(1..100_000) + value: Enum.random(1..100_000), + block_timestamp: DateTime.utc_now() } end diff --git a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs index 93f687907ca0..4c6c6fdff033 100644 --- a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs @@ -456,7 +456,7 @@ defmodule Indexer.Block.Catchup.FetcherTest do assert count(Chain.Block) == 1 assert count(Reward) == 0 - assert_receive {:block_numbers, [^block_number]}, 5_000 + assert_receive {:block_numbers, [_block_number]}, 5_000 end test "async fetches beneficiaries when entire call errors out", %{ diff --git a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs index e451525869b1..f1f510c8ac9c 100644 --- a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs +++ b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs @@ -305,7 +305,7 @@ defmodule Indexer.Fetcher.InternalTransactionTest do assert {:retry, [block.number]} == InternalTransaction.run([block.number, block.number], json_rpc_named_arguments) - assert %{block_hash: ^block_hash} = Repo.get(PendingBlockOperation, block_hash) + assert %{block_hash: _block_hash} = Repo.get(PendingBlockOperation, block_hash) end test "remove block consensus on foreign_key_violation", %{ diff --git a/apps/indexer/test/indexer/fetcher/uncle_block_test.exs b/apps/indexer/test/indexer/fetcher/uncle_block_test.exs index 052b4b75f492..d5e27c9d7634 100644 --- a/apps/indexer/test/indexer/fetcher/uncle_block_test.exs +++ b/apps/indexer/test/indexer/fetcher/uncle_block_test.exs @@ -196,7 +196,7 @@ defmodule Indexer.Fetcher.UncleBlockTest do ]} end) - assert {:retry, [^entry]} = + assert {:retry, [_entry]} = UncleBlock.run(entries, %Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments}) end end From 1aa3bf6b39454f90dc8f49cebc93a0eac3162a6f Mon Sep 17 00:00:00 2001 From: nikitosing Date: Mon, 21 Mar 2022 00:28:30 +0300 Subject: [PATCH 224/607] Final distribution of changes --- .../controllers/address_token_transfer_controller.ex | 3 +-- .../controllers/address_transaction_controller.ex | 1 - .../controllers/api/rpc/transaction_controller.ex | 2 +- .../controllers/recent_transactions_controller.ex | 1 - .../block_scout_web/controllers/transaction_controller.ex | 1 - .../transaction_internal_transaction_controller.ex | 4 ++-- .../lib/block_scout_web/templates/block/_link.html.eex | 2 +- .../templates/tokens/transfer/_token_transfer.html.eex | 2 +- .../lib/block_scout_web/views/api/rpc/transaction_view.ex | 2 +- .../lib/block_scout_web/views/transaction_view.ex | 6 +++++- apps/explorer/lib/explorer/chain/token_transfer.ex | 4 ++-- apps/explorer/lib/explorer/chain/transaction.ex | 4 ++-- apps/explorer/test/support/factory.ex | 6 ++++-- 13 files changed, 20 insertions(+), 18 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex index 25bec31b7dd4..743b063e7407 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex @@ -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 } ] diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex index 48ed703f1217..28543dfe2b99 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -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 diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex index e14aef4b94e4..28014a2eef4f 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex @@ -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) do {:error, :not_found} -> {:transaction, :error} {:ok, transaction} -> {:transaction, {:ok, transaction}} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex index f3406fc6cab6..ac7e39b822e7 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex @@ -15,7 +15,6 @@ defmodule BlockScoutWeb.RecentTransactionsController 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, diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex index 51a75bf85fcb..7c0bd3052695 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex @@ -42,7 +42,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, diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex index 4c6a017e776d..4109f473e14f 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex @@ -23,10 +23,10 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController 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 + [to_address: :smart_contract] => :optional, + :transaction => :optional } ], paging_options(params) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/_link.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/_link.html.eex index d910c2ad1941..c498a395bebf 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/block/_link.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/block/_link.html.eex @@ -1,4 +1,4 @@ <%= link( gettext("Block #%{number}", number: to_string(@block.number)), - to: block_path(BlockScoutWeb.Endpoint, :show, @block) + to: block_path(BlockScoutWeb.Endpoint, :show, @block.hash) ) %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex index d1d151ffe2b5..261e209c8d84 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex @@ -44,7 +44,7 @@ to: block_path(BlockScoutWeb.Endpoint, :show, @token_transfer.block_number) ) %> - +
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex index 4b81b110914f..2e7713fa5004 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex @@ -58,7 +58,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)}", "blockNumber" => "#{transaction.block_number}", "confirmations" => "#{block_height - transaction.block_number}", "success" => if(transaction.status == :ok, do: true, else: false), diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex index 548903cdd583..3112ff3091e0 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex @@ -32,10 +32,14 @@ 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_timestamp: time}), do: time def block_timestamp(%Transaction{block: %Block{timestamp: time}}), do: time def block_timestamp(%Reward{block: %Block{timestamp: time}}), do: time diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index 1c5a15123f51..b1663a1e327c 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -166,7 +166,7 @@ defmodule Explorer.Chain.TokenTransfer do 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: [:transaction, :token, :from_address, :to_address], order_by: [desc: tt.block_number, desc: tt.log_index] ) @@ -186,7 +186,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: [:transaction, :token, :from_address, :to_address], order_by: [desc: tt.block_number, desc: tt.log_index] ) diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index dfcc97c42d44..f8804f06fa6b 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -1031,7 +1031,7 @@ defmodule Explorer.Chain.Transaction do from( t in subquery(query), order_by: [desc: t.block_number, desc: t.index], - preload: [:from_address, :to_address, :created_contract_address, :block] + preload: [:from_address, :to_address, :created_contract_address] ) end @@ -1052,7 +1052,7 @@ defmodule Explorer.Chain.Transaction do from( t in subquery(query), order_by: [desc: t.block_number, desc: t.index], - preload: [:from_address, :to_address, :created_contract_address, :block] + preload: [:from_address, :to_address, :created_contract_address] ) end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index ac1973097827..0ca6f3ee0667 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -533,7 +533,7 @@ defmodule Explorer.Factory do %Transaction{index: nil} = transaction, # The `transaction.block` must be consensus. Non-consensus blocks can only be associated with the # `transaction_forks`. - %Block{consensus: true, hash: block_hash, number: block_number}, + %Block{consensus: true, hash: block_hash, number: block_number, timestamp: timestamp}, collated_params ) when is_list(collated_params) do @@ -555,7 +555,9 @@ defmodule Explorer.Factory do error: error, gas_used: gas_used, index: next_transaction_index, - status: status + status: status, + block_timestamp: timestamp, + block_consensus: true }) |> Repo.update!() |> Repo.preload(:block) From 0753b6c42d242bffd94559f57bdcabab5cf124e1 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 23 Mar 2022 13:44:11 +0300 Subject: [PATCH 225/607] lose_consensus function: remove excessive select --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/chain/import/runner/blocks.ex | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f8e0db54f80..addf114d8d59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -275,6 +275,7 @@ - [#8529](https://github.com/blockscout/blockscout/pull/8529) - Move PolygonEdge-related migration to the corresponding ecto repository - [#8504](https://github.com/blockscout/blockscout/pull/8504) - Deploy new UI through Makefile - [#8501](https://github.com/blockscout/blockscout/pull/8501) - Conceal secondary ports in docker compose setup +- [#5322](https://github.com/blockscout/blockscout/pull/5322) - DB denormalization: block consensus and timestamp in transaction table
Dependencies version bumps diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex index ffb8c47d5c99..4ed9d436d99e 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex @@ -401,8 +401,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do 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, - select: transaction.block_hash + where: transaction.block_hash not in ^hashes ), [set: [block_consensus: false, updated_at: updated_at]], timeout: timeout From 5848a7ab216dd44209b2e1259857cbcd7a397a36 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 26 Aug 2022 16:10:30 +0300 Subject: [PATCH 226/607] Remove internal transactions from getLogs query --- .../api/rpc/eth_controller_test.exs | 101 ++++++++++++++---- .../api/rpc/logs_controller_test.exs | 28 ++++- apps/explorer/lib/explorer/eth_rpc.ex | 4 +- apps/explorer/lib/explorer/etherscan/logs.ex | 82 +++----------- .../test/explorer/etherscan/logs_test.exs | 64 ++++++----- 5 files changed, 160 insertions(+), 119 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs index f441a173b1e2..4b68122fd25b 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs @@ -76,7 +76,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,7 +101,15 @@ 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") + + insert(:log, + block: block, + block_number: block.number, + address: address, + transaction: transaction, + data: "0x010101", + first_topic: "0x01" + ) params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01"]}]) @@ -112,8 +127,24 @@ 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") + + insert(:log, + address: address, + block: block, + block_number: block.number, + transaction: transaction, + data: "0x010101", + first_topic: "0x01" + ) + + insert(:log, + address: address, + block: block, + block_number: block.number, + transaction: transaction, + data: "0x020202", + first_topic: "0x00" + ) params = params(api_params, [%{"address" => to_string(address.hash), "topics" => [["0x01", "0x00"]]}]) @@ -135,7 +166,13 @@ 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: "0x01" + ) params = params(api_params, [%{"address" => to_string(contract_address), "topics" => [["0x01"]]}]) @@ -150,7 +187,6 @@ 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) } @@ -193,7 +229,8 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do data: "0x010101", first_topic: "0x01", second_topic: "0x02", - block: block + block: block, + block_number: block.number ) insert(:log, block: block, address: address, transaction: transaction, data: "0x020202", first_topic: "0x01") @@ -222,7 +259,8 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do data: "0x010101", first_topic: "0x01", second_topic: "0x02", - block: block + block: block, + block_number: block.number ) insert(:log, @@ -231,7 +269,8 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do data: "0x020202", first_topic: "0x01", second_topic: "0x03", - block: block + block: block, + block_number: block.number ) params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01", ["0x02", "0x03"]]}]) @@ -258,13 +297,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 +327,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 +355,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 +384,29 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do transaction2 = insert(:transaction, from_address: address) |> with_block(block2) transaction3 = insert(:transaction, from_address: address) |> with_block(block3) - insert(:log, block: block1, address: address, transaction: transaction1, data: "0x010101") + insert(:log, + block: block1, + block_number: block1.number, + address: address, + transaction: transaction1, + data: "0x010101" + ) - insert(:log, block: block2, address: address, transaction: transaction2, data: "0x020202") + insert(:log, + block: block2, + block_number: block2.number, + address: address, + transaction: transaction2, + data: "0x020202" + ) - insert(:log, block: block3, address: address, transaction: transaction3, data: "0x030303") + insert(:log, + block: block3, + block_number: block3.number, + address: address, + transaction: transaction3, + data: "0x030303" + ) changeset = Ecto.Changeset.change(block3, %{consensus: false}) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs index ec4337eecd84..66fc27dc71db 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs @@ -280,7 +280,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 +334,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 +387,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", diff --git a/apps/explorer/lib/explorer/eth_rpc.ex b/apps/explorer/lib/explorer/eth_rpc.ex index 889fdabbba6e..0e5b493a9aef 100644 --- a/apps/explorer/lib/explorer/eth_rpc.ex +++ b/apps/explorer/lib/explorer/eth_rpc.ex @@ -54,13 +54,13 @@ defmodule Explorer.EthRPC do action: :eth_get_logs, notes: """ Will never return more than 1000 log entries.\n - For this reason, you can use pagination options to request the next page. Pagination options params: {"logIndex": "3D", "blockNumber": "6423AC", "transactionIndex": 53} which include parameters from the last log received from the previous request. These three parameters are required for pagination. + For this reason, you can use pagination options to request the next page. Pagination options params: {"logIndex": "3D", "blockNumber": "6423AC"} which include parameters from the last log received from the previous request. These three parameters are required for pagination. """, example: """ {"id": 0, "jsonrpc": "2.0", "method": "eth_getLogs", "params": [ {"address": "0xc78Be425090Dbd437532594D12267C5934Cc6c6f", - "paging_options": {"logIndex": "3D", "blockNumber": "6423AC", "transactionIndex": 53}, + "paging_options": {"logIndex": "3D", "blockNumber": "6423AC"}, "fromBlock": "earliest", "toBlock": "latest", "topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}]} diff --git a/apps/explorer/lib/explorer/etherscan/logs.ex b/apps/explorer/lib/explorer/etherscan/logs.ex index 4d4e4111f9cd..70c4dedaf2d8 100644 --- a/apps/explorer/lib/explorer/etherscan/logs.ex +++ b/apps/explorer/lib/explorer/etherscan/logs.ex @@ -5,10 +5,10 @@ defmodule Explorer.Etherscan.Logs do """ - import Ecto.Query, only: [from: 2, where: 3, subquery: 1, order_by: 3, union: 2] + import Ecto.Query, only: [from: 2, limit: 2, where: 3, subquery: 1, order_by: 3] - alias Explorer.{Chain, Repo} - alias Explorer.Chain.{InternalTransaction, Log, Transaction} + alias Explorer.Repo + alias Explorer.Chain.{Log, Transaction} @base_filter %{ from_block: nil, @@ -38,7 +38,7 @@ defmodule Explorer.Etherscan.Logs do :type ] - @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,38 +76,20 @@ 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) + 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(transaction in Transaction, - join: log in ^logs_query, + from(log in subquery(logs_query), + join: transaction in Transaction, 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, @@ -117,20 +99,14 @@ defmodule Explorer.Etherscan.Logs do block_number: transaction.block_number, block_timestamp: transaction.block_timestamp, block_consensus: transaction.block_consensus - }, - 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, - limit: 1000, select_merge: %{ - transaction_index: log_transaction_data.transaction_index, - block_hash: log_transaction_data.block_hash, - block_number: log_transaction_data.block_number, - block_timestamp: log_transaction_data.block_timestamp, block_consensus: log_transaction_data.block_consensus } ) @@ -145,8 +121,6 @@ defmodule Explorer.Etherscan.Logs do end query_with_consensus - |> order_by([log], asc: log.index) - |> page_logs(paging_options) |> Repo.replica().all() end @@ -258,28 +232,4 @@ defmodule Explorer.Etherscan.Logs do where: data.index > ^log_index and data.block_number >= ^block_number ) end - - defp internal_transaction_query(logs_query, direction, prepared_filter, address_hash) do - query = - from(internal_transaction in InternalTransaction.where_nonpending_block(), - join: transaction in assoc(internal_transaction, :transaction), - join: log in ^logs_query, - on: log.transaction_hash == internal_transaction.transaction_hash, - where: internal_transaction.block_number >= ^prepared_filter.from_block, - where: internal_transaction.block_number <= ^prepared_filter.to_block, - select: - merge(map(log, ^@log_fields), %{ - gas_price: transaction.gas_price, - gas_used: transaction.gas_used, - transaction_index: transaction.index, - block_hash: transaction.block_hash, - block_number: internal_transaction.block_number, - block_timestamp: transaction.block_timestamp, - block_consensus: transaction.block_consensus - }) - ) - - query - |> InternalTransaction.where_address_fields_match(address_hash, direction) - end end diff --git a/apps/explorer/test/explorer/etherscan/logs_test.exs b/apps/explorer/test/explorer/etherscan/logs_test.exs index 81b04e01b331..5da949541c45 100644 --- a/apps/explorer/test/explorer/etherscan/logs_test.exs +++ b/apps/explorer/test/explorer/etherscan/logs_test.exs @@ -46,7 +46,7 @@ defmodule Explorer.Etherscan.LogsTest do |> insert(to_address: contract_address, block_timestamp: block.timestamp) |> with_block(block) - log = insert(:log, address: contract_address, transaction: transaction) + log = insert(:log, address: contract_address, block_number: block.number, transaction: transaction) filter = %{ from_block: block.number, @@ -80,7 +80,7 @@ defmodule Explorer.Etherscan.LogsTest do |> insert(to_address: contract_address) |> with_block() - insert_list(2, :log, address: contract_address, transaction: transaction) + insert_list(2, :log, address: contract_address, transaction: transaction, block_number: block.number) filter = %{ from_block: block.number, @@ -111,8 +111,8 @@ defmodule Explorer.Etherscan.LogsTest do |> insert(to_address: contract_address) |> with_block(second_block) - insert(:log, address: contract_address, transaction: transaction_block1) - insert(:log, address: contract_address, transaction: transaction_block2) + insert(:log, address: contract_address, transaction: transaction_block1, block_number: first_block.number) + insert(:log, address: contract_address, transaction: transaction_block2, block_number: second_block.number) filter = %{ from_block: second_block.number, @@ -144,8 +144,8 @@ defmodule Explorer.Etherscan.LogsTest do |> insert(to_address: contract_address) |> with_block(second_block) - insert(:log, address: contract_address, transaction: transaction_block1) - insert(:log, address: contract_address, transaction: transaction_block2) + insert(:log, address: contract_address, transaction: transaction_block1, block_number: first_block.number) + insert(:log, address: contract_address, transaction: transaction_block2, block_number: second_block.number) filter = %{ from_block: first_block.number, @@ -168,7 +168,8 @@ defmodule Explorer.Etherscan.LogsTest do |> insert(to_address: contract_address) |> with_block() - inserted_records = insert_list(2000, :log, address: contract_address, transaction: transaction) + inserted_records = + insert_list(2000, :log, address: contract_address, transaction: transaction, block_number: block.number) filter = %{ from_block: block.number, @@ -184,7 +185,6 @@ defmodule Explorer.Etherscan.LogsTest do next_page_params = %{ log_index: last_record.index, - transaction_index: last_record.transaction_index, block_number: transaction.block_number } @@ -327,13 +327,15 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some first topic" + first_topic: "some first topic", + block_number: block.number ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some OTHER first topic" + first_topic: "some OTHER first topic", + block_number: block.number ] _log1 = insert(:log, log1_details) @@ -365,14 +367,16 @@ defmodule Explorer.Etherscan.LogsTest do address: contract_address, transaction: transaction, first_topic: "some first topic", - second_topic: "some second topic" + second_topic: "some second topic", + block_number: block.number ] log2_details = [ address: contract_address, transaction: transaction, first_topic: "some OTHER first topic", - second_topic: "some OTHER second topic" + second_topic: "some OTHER second topic", + block_number: block.number ] _log1 = insert(:log, log1_details) @@ -407,7 +411,8 @@ defmodule Explorer.Etherscan.LogsTest do transaction: transaction, first_topic: "some first topic", second_topic: "some second topic", - third_topic: "some third topic" + third_topic: "some third topic", + block_number: block.number ] log2_details = [ @@ -415,7 +420,8 @@ defmodule Explorer.Etherscan.LogsTest do transaction: transaction, first_topic: "some OTHER first topic", second_topic: "some OTHER second topic", - third_topic: "some OTHER third topic" + third_topic: "some OTHER third topic", + block_number: block.number ] log3_details = [ @@ -423,7 +429,8 @@ defmodule Explorer.Etherscan.LogsTest do transaction: transaction, first_topic: "some ALT first topic", second_topic: "some ALT second topic", - third_topic: "some ALT third topic" + third_topic: "some ALT third topic", + block_number: block.number ] _log1 = insert(:log, log1_details) @@ -464,7 +471,8 @@ defmodule Explorer.Etherscan.LogsTest do transaction: transaction, first_topic: "some first topic", second_topic: "some second topic", - third_topic: "some third topic" + third_topic: "some third topic", + block_number: block.number ] log2_details = [ @@ -472,7 +480,8 @@ defmodule Explorer.Etherscan.LogsTest do transaction: transaction, first_topic: "some OTHER first topic", second_topic: "some OTHER second topic", - third_topic: "some OTHER third topic" + third_topic: "some OTHER third topic", + block_number: block.number ] log3_details = [ @@ -480,7 +489,8 @@ defmodule Explorer.Etherscan.LogsTest do transaction: transaction, first_topic: "some ALT first topic", second_topic: "some ALT second topic", - third_topic: "some ALT third topic" + third_topic: "some ALT third topic", + block_number: block.number ] log1 = insert(:log, log1_details) @@ -521,7 +531,8 @@ defmodule Explorer.Etherscan.LogsTest do transaction: transaction, first_topic: "some topic", second_topic: "some second topic", - third_topic: "some third topic" + third_topic: "some third topic", + block_number: block.number ] log2_details = [ @@ -529,7 +540,8 @@ defmodule Explorer.Etherscan.LogsTest do transaction: transaction, first_topic: "some topic", second_topic: "some OTHER second topic", - third_topic: "some third topic" + third_topic: "some third topic", + block_number: block.number ] log3_details = [ @@ -537,7 +549,8 @@ defmodule Explorer.Etherscan.LogsTest do transaction: transaction, first_topic: "some topic", second_topic: "some second topic", - third_topic: "some third topic" + third_topic: "some third topic", + block_number: block.number ] log1 = insert(:log, log1_details) @@ -577,7 +590,8 @@ defmodule Explorer.Etherscan.LogsTest do address: contract_address, transaction: transaction, first_topic: "some topic", - second_topic: "some second topic" + second_topic: "some second topic", + block_number: block.number ] log2_details = [ @@ -586,7 +600,8 @@ defmodule Explorer.Etherscan.LogsTest do first_topic: "some OTHER topic", second_topic: "some OTHER second topic", third_topic: "some OTHER third topic", - fourth_topic: "some fourth topic" + fourth_topic: "some fourth topic", + block_number: block.number ] log3_details = [ @@ -595,7 +610,8 @@ defmodule Explorer.Etherscan.LogsTest do first_topic: "some topic", second_topic: "some second topic", third_topic: "some third topic", - fourth_topic: "some fourth topic" + fourth_topic: "some fourth topic", + block_number: block.number ] log1 = insert(:log, log1_details) From e1c6f44c17ca8ad8c62fd39840eef93c52d4ba6c Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 29 Aug 2022 16:41:22 +0300 Subject: [PATCH 227/607] Handle nil timestamp in datetime_to_hex function --- .../lib/block_scout_web/views/api/rpc/logs_view.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/logs_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/logs_view.ex index 6f2933d7c63b..f52ab8fadccd 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/logs_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/logs_view.ex @@ -44,6 +44,8 @@ defmodule BlockScoutWeb.API.RPC.LogsView do |> integer_to_hex() end + defp datetime_to_hex(nil), do: nil + defp datetime_to_hex(datetime) do datetime |> DateTime.to_unix() From fd13a8c2698aa6f908045d2303f5b1be1705cec8 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Thu, 2 Nov 2023 22:52:52 +0600 Subject: [PATCH 228/607] Denormalization migration + dynamic logic support --- CHANGELOG.md | 2 +- .../address_token_transfer_controller.ex | 3 +- .../address_transaction_controller.ex | 3 +- .../api/rpc/transaction_controller.ex | 4 +- .../api/v2/transaction_controller.ex | 1 - .../recent_transactions_controller.ex | 28 +- .../controllers/transaction_controller.ex | 12 +- ...saction_internal_transaction_controller.ex | 28 +- .../lib/block_scout_web/notifier.ex | 6 +- .../tokens/transfer/_token_transfer.html.eex | 2 +- .../views/api/rpc/transaction_view.ex | 3 +- .../views/api/v2/transaction_view.ex | 3 +- .../block_scout_web/views/transaction_view.ex | 4 +- apps/block_scout_web/priv/gettext/default.pot | 48 +-- .../priv/gettext/en/LC_MESSAGES/default.po | 48 +-- .../channels/websocket_v2_test.exs | 2 + .../api/v2/search_controller_test.exs | 2 +- apps/explorer/config/config.exs | 2 + apps/explorer/config/runtime/test.exs | 2 + apps/explorer/lib/explorer/application.ex | 5 +- apps/explorer/lib/explorer/chain.ex | 23 +- .../chain/cache/background_migrations.ex | 23 ++ .../address_token_transfer_csv_exporter.ex | 4 +- .../address_transaction_csv_exporter.ex | 17 +- .../explorer/chain/denormalization_helper.ex | 44 +++ .../chain/import/runner/transactions.ex | 4 +- apps/explorer/lib/explorer/chain/search.ex | 95 ++++-- .../lib/explorer/chain/token_transfer.ex | 8 +- .../lib/explorer/chain/transaction.ex | 12 +- .../chain/transaction/history/historian.ex | 55 +++- apps/explorer/lib/explorer/etherscan.ex | 238 +++++++++----- apps/explorer/lib/explorer/etherscan/logs.ex | 299 +++++++++++++----- .../transactions_denormalization_migrator.ex | 91 ++++++ ...902_add_consensus_to_transaction_table.exs | 8 - ...d_block_timestamp_to_transaction_table.exs | 7 - ...nsactions_denormalization_migrator_test.ex | 37 +++ apps/explorer/test/support/factory.ex | 6 +- cspell.json | 2 + 38 files changed, 849 insertions(+), 332 deletions(-) create mode 100644 apps/explorer/lib/explorer/chain/cache/background_migrations.ex create mode 100644 apps/explorer/lib/explorer/chain/denormalization_helper.ex create mode 100644 apps/explorer/lib/explorer/transactions_denormalization_migrator.ex create mode 100644 apps/explorer/test/explorer/transactions_denormalization_migrator_test.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index addf114d8d59..d07488b208e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,7 @@ - [#8956](https://github.com/blockscout/blockscout/pull/8956) - Refine docker-compose config structure - [#8911](https://github.com/blockscout/blockscout/pull/8911) - Set client_connection_check_interval for main Postgres DB in docker-compose setup +- [#5322](https://github.com/blockscout/blockscout/pull/5322) - DB denormalization: block consensus and timestamp in transaction table
Dependencies version bumps @@ -275,7 +276,6 @@ - [#8529](https://github.com/blockscout/blockscout/pull/8529) - Move PolygonEdge-related migration to the corresponding ecto repository - [#8504](https://github.com/blockscout/blockscout/pull/8504) - Deploy new UI through Makefile - [#8501](https://github.com/blockscout/blockscout/pull/8501) - Conceal secondary ports in docker compose setup -- [#5322](https://github.com/blockscout/blockscout/pull/5322) - DB denormalization: block consensus and timestamp in transaction table
Dependencies version bumps diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex index 743b063e7407..94f87de09348 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex @@ -6,7 +6,7 @@ defmodule BlockScoutWeb.AddressTokenTransferController do alias BlockScoutWeb.{AccessHelper, Controller, TransactionView} alias Explorer.{Chain, Market} - alias Explorer.Chain.Address + alias Explorer.Chain.{Address, DenormalizationHelper} alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -140,6 +140,7 @@ defmodule BlockScoutWeb.AddressTokenTransferController do {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params) do options = @transaction_necessity_by_association + |> DenormalizationHelper.extend_block_necessity(:required) |> Keyword.merge(paging_options(params)) |> Keyword.merge(current_filter(params)) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex index 28543dfe2b99..05ef0c98d7fe 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -20,7 +20,7 @@ defmodule BlockScoutWeb.AddressTransactionController do AddressTransactionCsvExporter } - alias Explorer.Chain.{Transaction, Wei} + alias Explorer.Chain.{DenormalizationHelper, Transaction, Wei} alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -49,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)) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex index 28014a2eef4f..fbea15ca76f1 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] alias Explorer.Chain - alias Explorer.Chain.Transaction + alias Explorer.Chain.{DenormalizationHelper, Transaction} @api_true [api?: true] @@ -75,7 +75,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do end defp transaction_from_hash(transaction_hash) do - case Chain.hash_to_transaction(transaction_hash) do + case Chain.hash_to_transaction(transaction_hash, DenormalizationHelper.extend_block_necessity([], :required)) do {:error, :not_found} -> {:transaction, :error} {:ok, transaction} -> {:transaction, {:ok, transaction}} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex index 993d2ac68a36..a1e5134f4e9d 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -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 diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex index ac7e39b822e7..0863579eab95 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.RecentTransactionsController do import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] alias Explorer.{Chain, PagingOptions} - alias Explorer.Chain.Hash + alias Explorer.Chain.{DenormalizationHelper, Hash} alias Phoenix.View {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string()) @@ -13,16 +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: %{ - [created_contract_address: :names] => :optional, - [from_address: :names] => :optional, - [to_address: :names] => :optional, - [created_contract_address: :smart_contract] => :optional, - [from_address: :smart_contract] => :optional, - [to_address: :smart_contract] => :optional - }, - paging_options: %PagingOptions{page_size: 5} + Chain.recent_collated_transactions( + true, + DenormalizationHelper.extend_block_necessity( + [ + necessity_by_association: %{ + [created_contract_address: :names] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + [created_contract_address: :smart_contract] => :optional, + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional + }, + paging_options: %PagingOptions{page_size: 5} + ], + :required + ) ) transactions = diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex index 7c0bd3052695..a31baf1ea923 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex @@ -26,6 +26,7 @@ defmodule BlockScoutWeb.TransactionController do alias Explorer.{Chain, Market} alias Explorer.Chain.Cache.Transaction, as: TransactionCache + alias Explorer.Chain.DenormalizationHelper alias Phoenix.View @necessity_by_association %{ @@ -54,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 = @@ -151,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( @@ -189,10 +188,7 @@ defmodule BlockScoutWeb.TransactionController do end else with {:ok, transaction} <- - Chain.hash_to_transaction( - transaction_hash, - necessity_by_association: @necessity_by_association - ), + Chain.hash_to_transaction(transaction_hash, necessity_by_association: @necessity_by_association), {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do render( diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex index 4109f473e14f..5ea1e447211a 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex @@ -8,6 +8,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do alias BlockScoutWeb.{AccessHelper, Controller, InternalTransactionView, TransactionController} alias Explorer.{Chain, Market} + alias Explorer.Chain.DenormalizationHelper alias Phoenix.View def index(conn, %{"transaction_id" => transaction_hash_string, "type" => "JSON"} = params) do @@ -17,20 +18,19 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do full_options = - Keyword.merge( - [ - necessity_by_association: %{ - [created_contract_address: :names] => :optional, - [from_address: :names] => :optional, - [to_address: :names] => :optional, - [created_contract_address: :smart_contract] => :optional, - [from_address: :smart_contract] => :optional, - [to_address: :smart_contract] => :optional, - :transaction => :optional - } - ], - paging_options(params) - ) + [ + necessity_by_association: %{ + [created_contract_address: :names] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + [created_contract_address: :smart_contract] => :optional, + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional, + :transaction => :optional + } + ] + |> DenormalizationHelper.extend_transaction_block_necessity(:optional) + |> Keyword.merge(paging_options(params)) internal_transactions_plus_one = Chain.transaction_to_internal_transactions(transaction_hash, full_options) diff --git a/apps/block_scout_web/lib/block_scout_web/notifier.ex b/apps/block_scout_web/lib/block_scout_web/notifier.ex index 1de89cee8b78..3a70cd316594 100644 --- a/apps/block_scout_web/lib/block_scout_web/notifier.ex +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -20,7 +20,7 @@ defmodule BlockScoutWeb.Notifier do alias Explorer.{Chain, Market, Repo} alias Explorer.Chain.Address.Counters - alias Explorer.Chain.{Address, InternalTransaction, Transaction} + alias Explorer.Chain.{Address, DenormalizationHelper, InternalTransaction, Transaction} alias Explorer.Chain.Supply.RSK alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.Counters.{AverageBlockTime, Helper} @@ -171,7 +171,9 @@ defmodule BlockScoutWeb.Notifier do all_token_transfers |> Enum.map( &(&1 - |> Repo.preload([:from_address, :to_address, :token, transaction: :block])) + |> Repo.preload( + DenormalizationHelper.extend_transaction_preload([:from_address, :to_address, :token, :transaction]) + )) ) transfers_by_token = Enum.group_by(all_token_transfers_full, fn tt -> to_string(tt.token_contract_address_hash) end) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex index 261e209c8d84..73b6bbf5afe5 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex @@ -44,7 +44,7 @@ to: block_path(BlockScoutWeb.Endpoint, :show, @token_transfer.block_number) ) %> - + diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex index 2e7713fa5004..4a18643aa354 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex @@ -2,6 +2,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do use BlockScoutWeb, :view alias BlockScoutWeb.API.RPC.RPCView + alias Explorer.Chain.Transaction def render("gettxinfo.json", %{ transaction: transaction, @@ -58,7 +59,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do defp prepare_transaction(transaction, block_height, logs, next_page_params) do %{ "hash" => "#{transaction.hash}", - "timeStamp" => "#{DateTime.to_unix(transaction.block_timestamp)}", + "timeStamp" => "#{DateTime.to_unix(Transaction.block_timestamp(transaction))}", "blockNumber" => "#{transaction.block_number}", "confirmations" => "#{block_height - transaction.block_number}", "success" => if(transaction.status == :ok, do: true, else: false), diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index 28bc4042c46b..44832a51274b 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -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 diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex index 3112ff3091e0..ae493f391341 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex @@ -38,9 +38,7 @@ defmodule BlockScoutWeb.TransactionView do 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_timestamp: time}), do: time - def block_timestamp(%Transaction{block: %Block{timestamp: time}}), do: time + def block_timestamp(%Transaction{} = transaction), do: Transaction.block_timestamp(transaction) def block_timestamp(%Reward{block: %Block{timestamp: time}}), do: time def value_transfer?(%Transaction{input: %{bytes: bytes}}) when bytes in [<<>>, nil] do diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 096df745c2b0..224aa1df7789 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -68,7 +68,7 @@ msgstr "" msgid "%{subnetwork} Explorer - BlockScout" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:369 +#: lib/block_scout_web/views/transaction_view.ex:371 #, elixir-autogen, elixir-format msgid "(Awaiting internal transactions for status)" msgstr "" @@ -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 "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index ef9ebe1481ae..4d1362f4d282 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -68,7 +68,7 @@ msgstr "" msgid "%{subnetwork} Explorer - BlockScout" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:369 +#: lib/block_scout_web/views/transaction_view.ex:371 #, elixir-autogen, elixir-format msgid "(Awaiting internal transactions for status)" msgstr "" @@ -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 "" diff --git a/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs b/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs index 918b636cb3e8..bfb0424424f0 100644 --- a/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs +++ b/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs @@ -74,6 +74,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 +97,7 @@ defmodule BlockScoutWeb.WebsocketV2Test do %{ block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", block_number: 37, + block_timestamp: Timex.parse!("2017-12-15T21:06:30.000000Z", "{ISO:Extended:Z}"), cumulative_gas_used: 50450, from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", gas: 4_700_000, diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs index ee9ee43a0371..95584c013bff 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs @@ -202,7 +202,7 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do end test "search transaction", %{conn: conn} do - tx = insert(:transaction) + tx = insert(:transaction, block_timestamp: nil) request = get(conn, "/api/v2/search?q=#{tx.hash}") assert response = json_response(request, 200) diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 500417a2fd7a..88d38c68a956 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -113,6 +113,8 @@ config :explorer, Explorer.TokenTransferTokenIdMigration.Supervisor, enabled: tr config :explorer, Explorer.TokenInstanceOwnerAddressMigration.Supervisor, enabled: true +config :explorer, Explorer.TransactionsDenormalizationMigrator, enabled: true + config :explorer, Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand, enabled: true config :explorer, Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand, enabled: true diff --git a/apps/explorer/config/runtime/test.exs b/apps/explorer/config/runtime/test.exs index 9449463a5b9b..37ce93b2cb3b 100644 --- a/apps/explorer/config/runtime/test.exs +++ b/apps/explorer/config/runtime/test.exs @@ -37,6 +37,8 @@ config :explorer, Explorer.TokenTransferTokenIdMigration.Supervisor, enabled: fa config :explorer, Explorer.TokenInstanceOwnerAddressMigration.Supervisor, enabled: false +config :explorer, Explorer.TransactionsDenormalizationMigrator, enabled: false + config :explorer, realtime_events_sender: Explorer.Chain.Events.SimpleSender diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 666407fbc025..497043d143d8 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -12,6 +12,7 @@ defmodule Explorer.Application do AddressesTabsCounters, AddressSum, AddressSumMinusBurnt, + BackgroundMigrations, Block, BlockNumber, Blocks, @@ -63,6 +64,7 @@ defmodule Explorer.Application do Accounts, AddressSum, AddressSumMinusBurnt, + BackgroundMigrations, Block, BlockNumber, Blocks, @@ -125,7 +127,8 @@ defmodule Explorer.Application do 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.TransactionsDenormalizationMigrator) ] |> List.flatten() diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index af2d6874a6d4..b6d27141d75d 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -51,6 +51,7 @@ defmodule Explorer.Chain do CurrencyHelper, Data, DecompiledSmartContract, + DenormalizationHelper, Hash, Import, InternalTransaction, @@ -521,12 +522,22 @@ 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( - 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)}} - ) + 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() diff --git a/apps/explorer/lib/explorer/chain/cache/background_migrations.ex b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex new file mode 100644 index 000000000000..4b33076a9675 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex @@ -0,0 +1,23 @@ +defmodule Explorer.Chain.Cache.BackgroundMigrations do + @moduledoc """ + Caches background migrations' status. + """ + + require Logger + + use Explorer.Chain.MapCache, + name: :background_migrations_status, + key: :denormalization_finished + + @dialyzer :no_match + + alias Explorer.TransactionsDenormalizationMigrator + + defp handle_fallback(:denormalization_finished) do + Task.start(fn -> + set_denormalization_finished(TransactionsDenormalizationMigrator.migration_finished?()) + end) + + {:return, false} + end +end diff --git a/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex index 704229757280..4409d1475eb8 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex @@ -12,7 +12,7 @@ defmodule Explorer.Chain.CSVExport.AddressTokenTransferCsvExporter do ] alias Explorer.{Chain, PagingOptions, Repo} - alias Explorer.Chain.{Address, Hash, TokenTransfer} + alias Explorer.Chain.{Address, 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), diff --git a/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex index a0404f477370..600d53d63aa3 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex @@ -10,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), diff --git a/apps/explorer/lib/explorer/chain/denormalization_helper.ex b/apps/explorer/lib/explorer/chain/denormalization_helper.ex new file mode 100644 index 000000000000..284ccf64a255 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/denormalization_helper.ex @@ -0,0 +1,44 @@ +defmodule Explorer.Chain.DenormalizationHelper do + @moduledoc false + + alias Explorer.Chain.Cache.BackgroundMigrations + + 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 + + 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 + + def extend_transaction_preload(preloads) do + if denormalization_finished?() do + preloads + else + [transaction: :block] ++ (preloads -- [:transaction]) + end + end + + def extend_block_preload(preloads) do + if denormalization_finished?() do + preloads + else + [:block | preloads] + end + end + + def denormalization_finished?, do: BackgroundMigrations.get_denormalization_finished() +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex index b9927a5c5ebd..62578969a783 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex @@ -161,7 +161,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do ], where: fragment( - "(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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "(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, @@ -242,7 +242,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do ], where: fragment( - "(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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "(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, diff --git a/apps/explorer/lib/explorer/chain/search.ex b/apps/explorer/lib/explorer/chain/search.ex index d74f697a9b34..20082bccbfb8 100644 --- a/apps/explorer/lib/explorer/chain/search.ex +++ b/apps/explorer/lib/explorer/chain/search.ex @@ -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 diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index b1663a1e327c..8b9b878914df 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -28,7 +28,7 @@ defmodule Explorer.Chain.TokenTransfer do import Ecto.Query, only: [from: 2, limit: 2, where: 3, join: 5, order_by: 3, preload: 3] alias Explorer.Chain - alias Explorer.Chain.{Address, Block, Hash, TokenTransfer, Transaction} + alias Explorer.Chain.{Address, Block, DenormalizationHelper, Hash, TokenTransfer, Transaction} alias Explorer.Chain.Token.Instance alias Explorer.{PagingOptions, Repo} @@ -161,12 +161,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, :token, :from_address, :to_address], + preload: ^preloads, order_by: [desc: tt.block_number, desc: tt.log_index] ) @@ -179,6 +180,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 +188,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, :token, :from_address, :to_address], + preload: ^preloads, order_by: [desc: tt.block_number, desc: tt.log_index] ) diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index f8804f06fa6b..a77700db8e83 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -17,6 +17,7 @@ defmodule Explorer.Chain.Transaction do Block, ContractMethod, Data, + DenormalizationHelper, Gas, Hash, InternalTransaction, @@ -552,6 +553,11 @@ defmodule Explorer.Chain.Transaction do |> unique_constraint(:hash) end + 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 block_timestamp(_), do: nil + def preload_token_transfers(query, address_hash) do token_transfers_query = from( @@ -1027,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] + preload: ^preloads ) end @@ -1048,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] + preload: ^preloads ) end diff --git a/apps/explorer/lib/explorer/chain/transaction/history/historian.ex b/apps/explorer/lib/explorer/chain/transaction/history/historian.ex index 126b59b29998..2c8e4479cf6d 100644 --- a/apps/explorer/lib/explorer/chain/transaction/history/historian.ex +++ b/apps/explorer/lib/explorer/chain/transaction/history/historian.ex @@ -6,7 +6,7 @@ defmodule Explorer.Chain.Transaction.History.Historian do use Explorer.History.Historian alias Explorer.{Chain, Repo} - alias Explorer.Chain.{Block, Transaction} + alias Explorer.Chain.{Block, DenormalizationHelper, Transaction} alias Explorer.Chain.Events.Publisher alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.History.Process, as: HistoryProcess @@ -89,22 +89,57 @@ defmodule Explorer.Chain.Transaction.History.Historian do Logger.info("tx/per day chart: min/max block numbers [#{min_block}, #{max_block}]") all_transactions_query = + 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( - transaction in Transaction, - where: transaction.block_number >= ^min_block and transaction.block_number <= ^max_block, - where: transaction.block_consensus == true, - select: transaction + block in Block, + where: block.consensus == true, + where: block.number >= ^min_block and block.number <= ^max_block, + select: block.number ) - num_transactions = Repo.aggregate(all_transactions_query, :count, :hash, timeout: :infinity) + query = + 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}") - gas_used = Repo.aggregate(all_transactions_query, :sum, :gas_used, timeout: :infinity) + gas_used = Repo.aggregate(query, :sum, :gas_used, timeout: :infinity) Logger.info("tx/per day chart: total gas used #{gas_used}") total_fee_query = - from(transaction in subquery(all_transactions_query), - select: fragment("SUM(? * ?)", transaction.gas_price, transaction.gas_used) - ) + if DenormalizationHelper.denormalization_finished?() do + from(transaction in subquery(all_transactions_query), + select: fragment("SUM(? * ?)", transaction.gas_price, transaction.gas_used) + ) + else + from(transaction in subquery(all_transactions_query), + join: block in Block, + on: transaction.block_hash == block.hash, + where: block.consensus == true, + select: fragment("SUM(? * ?)", transaction.gas_price, transaction.gas_used) + ) + end total_fee = Repo.one(total_fee_query, timeout: :infinity) Logger.info("tx/per day chart: total fee #{total_fee}") diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index f62f20656115..dba8be73ebcc 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -9,7 +9,7 @@ defmodule Explorer.Etherscan do alias Explorer.Etherscan.Logs alias Explorer.{Chain, Repo} alias Explorer.Chain.Address.{CurrentTokenBalance, TokenBalance} - alias Explorer.Chain.{Address, Block, Hash, InternalTransaction, TokenTransfer, Transaction} + alias Explorer.Chain.{Address, Block, DenormalizationHelper, Hash, InternalTransaction, TokenTransfer, Transaction} alias Explorer.Chain.Transaction.History.TransactionStats @default_options %{ @@ -101,17 +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: 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 - }) - ) + 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() @@ -211,18 +226,34 @@ defmodule Explorer.Etherscan do |> Repo.replica().all() else query = - 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 - }) - ) + 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() @@ -393,16 +424,31 @@ defmodule Explorer.Etherscan do defp list_transactions(address_hash, max_block_number, options) do query = - 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) - }) - ) + 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) @@ -465,37 +511,71 @@ 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, - 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 - } - ) + 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, + 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_id: tt.token_id, + 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_transaction_block_match(options) @@ -517,26 +597,42 @@ defmodule Explorer.Etherscan do defp where_start_transaction_block_match(query, %{start_block: nil}), do: query - defp where_start_transaction_block_match(query, %{start_block: start_block}) do - where(query, [transaction], transaction.block_number >= ^start_block) + 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}) do - where(query, [transaction], transaction.block_number <= ^end_block) + 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, [transaction, _block], ^start_timestamp <= transaction.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, [transaction, _block], transaction.block_timestamp <= ^end_timestamp) + if DenormalizationHelper.denormalization_finished?() do + where(query, [transaction], transaction.block_timestamp <= ^end_timestamp) + else + where(query, [..., block], block.timestamp <= ^end_timestamp) + end end defp where_contract_address_match(query, nil), do: query diff --git a/apps/explorer/lib/explorer/etherscan/logs.ex b/apps/explorer/lib/explorer/etherscan/logs.ex index 70c4dedaf2d8..e83587869d6d 100644 --- a/apps/explorer/lib/explorer/etherscan/logs.ex +++ b/apps/explorer/lib/explorer/etherscan/logs.ex @@ -5,10 +5,10 @@ defmodule Explorer.Etherscan.Logs do """ - import Ecto.Query, only: [from: 2, limit: 2, where: 3, subquery: 1, order_by: 3] + import Ecto.Query, only: [from: 2, limit: 2, where: 3, subquery: 1, order_by: 3, union: 2] - alias Explorer.Repo - alias Explorer.Chain.{Log, Transaction} + alias Explorer.{Chain, Repo} + alias Explorer.Chain.{Block, DenormalizationHelper, InternalTransaction, Log, Transaction} @base_filter %{ from_block: nil, @@ -76,52 +76,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 = - 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) + 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(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 - } - ) + 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), - 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_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([transaction] in query_with_blocks, - where: transaction.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 - |> 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 @@ -131,48 +205,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, - 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 - } - ) + 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([transaction] in block_transaction_query, - where: transaction.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 [ @@ -232,4 +348,25 @@ defmodule Explorer.Etherscan.Logs do where: data.index > ^log_index and data.block_number >= ^block_number ) end + + defp internal_transaction_query(logs_query, direction, prepared_filter, address_hash) do + query = + from(internal_transaction in InternalTransaction.where_nonpending_block(), + join: transaction in assoc(internal_transaction, :transaction), + join: log in ^logs_query, + on: log.transaction_hash == internal_transaction.transaction_hash, + where: internal_transaction.block_number >= ^prepared_filter.from_block, + where: internal_transaction.block_number <= ^prepared_filter.to_block, + select: + merge(map(log, ^@log_fields), %{ + gas_price: transaction.gas_price, + gas_used: transaction.gas_used, + transaction_index: transaction.index, + block_number: internal_transaction.block_number + }) + ) + + query + |> InternalTransaction.where_address_fields_match(address_hash, direction) + end end diff --git a/apps/explorer/lib/explorer/transactions_denormalization_migrator.ex b/apps/explorer/lib/explorer/transactions_denormalization_migrator.ex new file mode 100644 index 000000000000..442344819c21 --- /dev/null +++ b/apps/explorer/lib/explorer/transactions_denormalization_migrator.ex @@ -0,0 +1,91 @@ +defmodule Explorer.TransactionsDenormalizationMigrator do + @moduledoc """ + Migrates all transactions to have set block_consensus and block_timestamp + """ + + use GenServer, restart: :transient + + import Ecto.Query + + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Chain.Transaction + alias Explorer.Repo + + @default_batch_size 500 + @default_concurrency 4 * System.schedulers_online() + + @spec start_link(term()) :: GenServer.on_start() + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + def migration_finished? do + not Repo.exists?(unprocessed_transactions_query()) + end + + @impl true + def init(_) do + schedule_batch_migration() + {:ok, %{}} + end + + @impl true + def handle_info(:migrate_batch, state) do + case last_unprocessed_transaction_hashes() do + [] -> + BackgroundMigrations.set_denormalization_finished(true) + {: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 last_unprocessed_transaction_hashes do + limit = batch_size() * concurrency() + + unprocessed_transactions_query() + |> order_by(desc: :inserted_at) + |> select([t], t.hash) + |> limit(^limit) + |> Repo.all() + end + + defp unprocessed_transactions_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 + + defp 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 + + 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 + Application.get_env(:explorer, __MODULE__)[:batch_size] || @default_concurrency + end +end diff --git a/apps/explorer/priv/repo/migrations/20220315082902_add_consensus_to_transaction_table.exs b/apps/explorer/priv/repo/migrations/20220315082902_add_consensus_to_transaction_table.exs index bb1c60a94de7..8c8bbece0213 100644 --- a/apps/explorer/priv/repo/migrations/20220315082902_add_consensus_to_transaction_table.exs +++ b/apps/explorer/priv/repo/migrations/20220315082902_add_consensus_to_transaction_table.exs @@ -6,14 +6,6 @@ defmodule Explorer.Repo.Migrations.AddConsensusToTransactionTable do add(:block_consensus, :boolean, default: true) end - execute(""" - UPDATE transactions tx - SET block_consensus = b.consensus - FROM blocks b - WHERE b.hash = tx.block_hash - AND b.consensus = false; - """) - create(index(:transactions, :block_consensus)) end end diff --git a/apps/explorer/priv/repo/migrations/20220315093927_add_block_timestamp_to_transaction_table.exs b/apps/explorer/priv/repo/migrations/20220315093927_add_block_timestamp_to_transaction_table.exs index 22f491027dcc..7303c9fce1d0 100644 --- a/apps/explorer/priv/repo/migrations/20220315093927_add_block_timestamp_to_transaction_table.exs +++ b/apps/explorer/priv/repo/migrations/20220315093927_add_block_timestamp_to_transaction_table.exs @@ -6,13 +6,6 @@ defmodule Explorer.Repo.Migrations.AddBlockTimestampToTransactionTable do add(:block_timestamp, :utc_datetime_usec) end - execute(""" - UPDATE transactions tx - SET block_timestamp = b.timestamp - FROM blocks b - WHERE b.hash = tx.block_hash; - """) - create(index(:transactions, :block_timestamp)) end end diff --git a/apps/explorer/test/explorer/transactions_denormalization_migrator_test.ex b/apps/explorer/test/explorer/transactions_denormalization_migrator_test.ex new file mode 100644 index 000000000000..12b984837022 --- /dev/null +++ b/apps/explorer/test/explorer/transactions_denormalization_migrator_test.ex @@ -0,0 +1,37 @@ +defmodule Explorer.TransactionsDenormalizationMigratorTest do + use Explorer.DataCase, async: false + + alias Explorer.Chain.Transaction + alias Explorer.{Repo, TransactionsDenormalizationMigrator} + + describe "Migrate transactions" do + test "Set block_consensus and block_timestamp for not processed transactions" do + Enum.each(0..10, fn _x -> + transaction = + :transaction + |> insert() + |> with_block(block_timestamp: nil, block_consensus: nil) + + assert %{block_consensus: nil, block_timestamp: nil, block: %{consensus: consensus, timestamp: timestamp}} = + transaction + + assert not is_nil(consensus) + assert not is_nil(timestamp) + end) + + TransactionsDenormalizationMigrator.start_link([]) + Process.sleep(100) + + Transaction + |> Repo.all() + |> Repo.preload(:block) + |> Enum.each(fn t -> + assert %{ + block_consensus: consensus, + block_timestamp: timestamp, + block: %{consensus: consensus, timestamp: timestamp} + } = t + end) + end + end +end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 0ca6f3ee0667..4c218afb3809 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -542,6 +542,8 @@ defmodule Explorer.Factory do cumulative_gas_used = collated_params[:cumulative_gas_used] || Enum.random(21_000..100_000) gas_used = collated_params[:gas_used] || Enum.random(21_000..100_000) status = Keyword.get(collated_params, :status, Enum.random([:ok, :error])) + block_timestamp = Keyword.get(collated_params, :block_timestamp, timestamp) + block_consensus = Keyword.get(collated_params, :block_consensus, true) error = (status == :error && collated_params[:error]) || nil @@ -556,8 +558,8 @@ defmodule Explorer.Factory do gas_used: gas_used, index: next_transaction_index, status: status, - block_timestamp: timestamp, - block_consensus: true + block_timestamp: block_timestamp, + block_consensus: block_consensus }) |> Repo.update!() |> Repo.preload(:block) diff --git a/cspell.json b/cspell.json index 78b98a92d718..d19d333d4403 100644 --- a/cspell.json +++ b/cspell.json @@ -127,6 +127,8 @@ "DELEGATECALL", "delegators", "demonitor", + "denormalization", + "Denormalization", "Denormalized", "descr", "describedby", From 7c7639d357acf4e9e6303d9b27b60e1c62a4263e Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Mon, 4 Dec 2023 18:59:56 +0600 Subject: [PATCH 229/607] Clear cache in GA --- .github/workflows/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index bd487d8c2aa5..30c3fa8c8c8b 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -195,7 +195,7 @@ jobs: id: dialyzer-cache with: path: priv/plts - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-${{ matrix.chain-type }}-dialyzer-mixlockhash_25-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-${{ matrix.chain-type }}-dialyzer-mixlockhash_28-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-${{ matrix.chain-type }}-dialyzer-" From 66a1b390544824a199fbb10e7ab5b00931af13f0 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Mon, 4 Dec 2023 21:19:01 +0600 Subject: [PATCH 230/607] Use denormalization in token transfers csv exporter --- .../chain/csv_export/address_token_transfer_csv_exporter.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex index 4409d1475eb8..5a44e272a726 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex @@ -12,7 +12,7 @@ defmodule Explorer.Chain.CSVExport.AddressTokenTransferCsvExporter do ] alias Explorer.{Chain, PagingOptions, Repo} - alias Explorer.Chain.{Address, Hash, TokenTransfer, Transaction} + alias Explorer.Chain.{Address, DenormalizationHelper, Hash, TokenTransfer, Transaction} alias Explorer.Chain.CSVExport.Helper @paging_options %PagingOptions{page_size: Helper.limit(), asc_order: true} @@ -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 From 27d042fc8fcf1bb25fdbf806465faa1f7a3183e7 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Wed, 6 Dec 2023 13:14:54 +0600 Subject: [PATCH 231/607] Clear cache in GA --- .github/workflows/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 30c3fa8c8c8b..bd487d8c2aa5 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -195,7 +195,7 @@ jobs: id: dialyzer-cache with: path: priv/plts - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-${{ matrix.chain-type }}-dialyzer-mixlockhash_28-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-${{ matrix.chain-type }}-dialyzer-mixlockhash_25-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-${{ matrix.chain-type }}-dialyzer-" From 39a8d6094a9ed1b086e7f5045896417aa4574d7d Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Tue, 12 Dec 2023 17:05:49 +0600 Subject: [PATCH 232/607] Denormalization improvements --- .dialyzer-ignore | 2 +- .../lib/ethereum_jsonrpc/block.ex | 4 +- .../lib/ethereum_jsonrpc/blocks.ex | 34 +-------- .../lib/ethereum_jsonrpc/transaction.ex | 75 +++++++++++-------- .../lib/ethereum_jsonrpc/transactions.ex | 4 +- apps/explorer/lib/explorer/chain.ex | 18 +++-- ...dress_internal_transaction_csv_exporter.ex | 0 .../address_token_transfer_csv_exporter.ex | 0 .../chain/address_transaction_csv_exporter.ex | 0 .../explorer/chain/denormalization_helper.ex | 4 +- .../lib/explorer/chain/transaction.ex | 2 +- .../transactions_denormalization_migrator.ex | 20 +++-- .../lib/explorer/utility/migration_status.ex | 31 ++++++++ ...902_add_consensus_to_transaction_table.exs | 11 --- ...d_block_timestamp_to_transaction_table.exs | 11 --- ...imestamp_and_consensus_to_transactions.exs | 13 ++++ ...0231212102127_create_migrations_status.exs | 12 +++ apps/explorer/test/explorer/chain_test.exs | 16 +++- .../indexer/block/catchup/fetcher_test.exs | 2 +- .../fetcher/internal_transaction_test.exs | 2 +- .../test/indexer/fetcher/uncle_block_test.exs | 2 +- config/runtime.exs | 4 + 22 files changed, 153 insertions(+), 114 deletions(-) delete mode 100644 apps/explorer/lib/explorer/chain/address_internal_transaction_csv_exporter.ex delete mode 100644 apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex delete mode 100644 apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex create mode 100644 apps/explorer/lib/explorer/utility/migration_status.ex delete mode 100644 apps/explorer/priv/repo/migrations/20220315082902_add_consensus_to_transaction_table.exs delete mode 100644 apps/explorer/priv/repo/migrations/20220315093927_add_block_timestamp_to_transaction_table.exs create mode 100644 apps/explorer/priv/repo/migrations/20231212101547_add_block_timestamp_and_consensus_to_transactions.exs create mode 100644 apps/explorer/priv/repo/migrations/20231212102127_create_migrations_status.exs diff --git a/.dialyzer-ignore b/.dialyzer-ignore index a4baec92256e..79cfeb45051b 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -23,4 +23,4 @@ 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:169 diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex index d07606297ebb..a3a32ddc0c9c 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex @@ -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 diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex index a504468f49ef..d9a697c1acbd 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex @@ -54,13 +54,12 @@ defmodule EthereumJSONRPC.Blocks do transactions_params = Transactions.elixir_to_params(elixir_transactions) withdrawals_params = Withdrawals.elixir_to_params(elixir_withdrawals) blocks_params = elixir_to_params(elixir_blocks) - transactions_params_with_block_timestamp = add_timestamp_to_transactions_params(transactions_params, blocks_params) %__MODULE__{ errors: errors, blocks_params: blocks_params, block_second_degree_relations_params: block_second_degree_relations_params, - transactions_params: transactions_params_with_block_timestamp, + transactions_params: transactions_params, withdrawals_params: withdrawals_params } end @@ -455,35 +454,4 @@ defmodule EthereumJSONRPC.Blocks do def to_elixir(blocks) when is_list(blocks) do Enum.map(blocks, &Block.to_elixir/1) end - - defp add_timestamp_to_transactions_params(transactions_params, blocks_params) do - block_hashes = - transactions_params - |> Enum.map(fn %{block_hash: block_hash} -> block_hash end) - |> Enum.uniq() - - block_hash_timestamp_map = - block_hashes - |> Enum.map(fn block_hash -> - block = - Enum.find(blocks_params, fn block_param -> - block_param.hash == block_hash - end) - - %{} - |> Map.put("#{block_hash}", block.timestamp) - end) - |> Enum.reduce(%{}, fn hash_timestamp_map_item, acc -> - Map.merge(acc, hash_timestamp_map_item) - end) - - transactions_params - |> Enum.map(fn transactions_param -> - Map.put( - transactions_param, - :block_timestamp, - Map.get(block_hash_timestamp_map, "#{transactions_param.block_hash}") - ) - end) - end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex index 964dc2ec1f2f..2f11da852095 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex @@ -237,11 +237,10 @@ defmodule EthereumJSONRPC.Transaction do result end - if transaction["creates"] do - Map.put(result, :created_contract_address_hash, transaction["creates"]) - else - result - end + put_if_present(transaction, result, [ + {"creates", :created_contract_address_hash}, + {"block_timestamp", :block_timestamp} + ]) end def elixir_to_params( @@ -286,11 +285,10 @@ defmodule EthereumJSONRPC.Transaction do max_fee_per_gas: max_fee_per_gas } - if transaction["creates"] do - Map.put(result, :created_contract_address_hash, transaction["creates"]) - else - result - end + put_if_present(transaction, result, [ + {"creates", :created_contract_address_hash}, + {"block_timestamp", :block_timestamp} + ]) end # txpool_content method on Erigon node returns tx data @@ -336,11 +334,10 @@ defmodule EthereumJSONRPC.Transaction do max_fee_per_gas: max_fee_per_gas } - if transaction["creates"] do - Map.put(result, :created_contract_address_hash, transaction["creates"]) - else - result - end + put_if_present(transaction, result, [ + {"creates", :created_contract_address_hash}, + {"block_timestamp", :block_timestamp} + ]) end # this is for Suave chain (handles `executionNode` and `requestRecord` fields without EIP-1559 fields) @@ -407,11 +404,10 @@ defmodule EthereumJSONRPC.Transaction do result end - if transaction["creates"] do - Map.put(result, :created_contract_address_hash, transaction["creates"]) - else - result - end + put_if_present(transaction, result, [ + {"creates", :created_contract_address_hash}, + {"block_timestamp", :block_timestamp} + ]) end def elixir_to_params( @@ -452,11 +448,10 @@ defmodule EthereumJSONRPC.Transaction do type: type } - if transaction["creates"] do - Map.put(result, :created_contract_address_hash, transaction["creates"]) - else - result - end + put_if_present(transaction, result, [ + {"creates", :created_contract_address_hash}, + {"block_timestamp", :block_timestamp} + ]) end def elixir_to_params( @@ -495,11 +490,10 @@ defmodule EthereumJSONRPC.Transaction do transaction_index: index } - if transaction["creates"] do - Map.put(result, :created_contract_address_hash, transaction["creates"]) - else - result - end + put_if_present(transaction, result, [ + {"creates", :created_contract_address_hash}, + {"block_timestamp", :block_timestamp} + ]) end @doc """ @@ -580,11 +574,14 @@ defmodule EthereumJSONRPC.Transaction do } """ - def to_elixir(transaction) when is_map(transaction) do - Enum.into(transaction, %{}, &entry_to_elixir/1) + def to_elixir(transaction, block_timestamp \\ nil) + + def to_elixir(transaction, block_timestamp) when is_map(transaction) do + initial = (block_timestamp && %{"block_timestamp" => block_timestamp}) || %{} + Enum.into(transaction, initial, &entry_to_elixir/1) end - def to_elixir(transaction) when is_binary(transaction) do + def to_elixir(transaction, _block_timestamp) when is_binary(transaction) do nil end @@ -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 diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex index 9b3937873932..ecdf103b4e89 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex @@ -151,9 +151,9 @@ defmodule EthereumJSONRPC.Transactions do ] """ - def to_elixir(transactions) when is_list(transactions) do + def to_elixir(transactions, block_timestamp \\ nil) when is_list(transactions) do transactions - |> Enum.map(&Transaction.to_elixir/1) + |> Enum.map(&Transaction.to_elixir(&1, block_timestamp)) |> Enum.filter(&(!is_nil(&1))) end end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index b6d27141d75d..4e0e5ac0c3af 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -539,13 +539,17 @@ defmodule Explorer.Chain do ) end - query - |> Repo.all() - |> (&if(Enum.count(&1) > 0, - do: &1, - else: Enum.zip([block_hashes, for(_ <- 1..Enum.count(block_hashes), do: %Wei{value: Decimal.new(0)})]) - )).() - |> 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 diff --git a/apps/explorer/lib/explorer/chain/address_internal_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_internal_transaction_csv_exporter.ex deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/apps/explorer/lib/explorer/chain/denormalization_helper.ex b/apps/explorer/lib/explorer/chain/denormalization_helper.ex index 284ccf64a255..79c9d4c51a1e 100644 --- a/apps/explorer/lib/explorer/chain/denormalization_helper.ex +++ b/apps/explorer/lib/explorer/chain/denormalization_helper.ex @@ -1,5 +1,7 @@ defmodule Explorer.Chain.DenormalizationHelper do - @moduledoc false + @moduledoc """ + Helper functions for dynamic logic based on denormalization migration completeness + """ alias Explorer.Chain.Cache.BackgroundMigrations diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index a77700db8e83..10b730d6bcd7 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -553,10 +553,10 @@ 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 block_timestamp(_), do: nil def preload_token_transfers(query, address_hash) do token_transfers_query = diff --git a/apps/explorer/lib/explorer/transactions_denormalization_migrator.ex b/apps/explorer/lib/explorer/transactions_denormalization_migrator.ex index 442344819c21..55f927d5afdb 100644 --- a/apps/explorer/lib/explorer/transactions_denormalization_migrator.ex +++ b/apps/explorer/lib/explorer/transactions_denormalization_migrator.ex @@ -10,9 +10,10 @@ defmodule Explorer.TransactionsDenormalizationMigrator do alias Explorer.Chain.Cache.BackgroundMigrations alias Explorer.Chain.Transaction alias Explorer.Repo + alias Explorer.Utility.MigrationStatus @default_batch_size 500 - @default_concurrency 4 * System.schedulers_online() + @migration_name "denormalization" @spec start_link(term()) :: GenServer.on_start() def start_link(_) do @@ -25,8 +26,15 @@ defmodule Explorer.TransactionsDenormalizationMigrator do @impl true def init(_) do - schedule_batch_migration() - {:ok, %{}} + case MigrationStatus.get_status(@migration_name) do + "completed" -> + :ignore + + _ -> + MigrationStatus.set_status(@migration_name, "started") + schedule_batch_migration() + {:ok, %{}} + end end @impl true @@ -34,6 +42,7 @@ defmodule Explorer.TransactionsDenormalizationMigrator do case last_unprocessed_transaction_hashes() do [] -> BackgroundMigrations.set_denormalization_finished(true) + MigrationStatus.set_status(@migration_name, "completed") {:stop, :normal, state} hashes -> @@ -54,7 +63,6 @@ defmodule Explorer.TransactionsDenormalizationMigrator do limit = batch_size() * concurrency() unprocessed_transactions_query() - |> order_by(desc: :inserted_at) |> select([t], t.hash) |> limit(^limit) |> Repo.all() @@ -86,6 +94,8 @@ defmodule Explorer.TransactionsDenormalizationMigrator do end defp concurrency do - Application.get_env(:explorer, __MODULE__)[:batch_size] || @default_concurrency + default = 4 * System.schedulers_online() + + Application.get_env(:explorer, __MODULE__)[:concurrency] || default end end diff --git a/apps/explorer/lib/explorer/utility/migration_status.ex b/apps/explorer/lib/explorer/utility/migration_status.ex new file mode 100644 index 000000000000..21ac52de6d88 --- /dev/null +++ b/apps/explorer/lib/explorer/utility/migration_status.ex @@ -0,0 +1,31 @@ +defmodule Explorer.Utility.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) + 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, conflict_target: :migration_name) + end +end diff --git a/apps/explorer/priv/repo/migrations/20220315082902_add_consensus_to_transaction_table.exs b/apps/explorer/priv/repo/migrations/20220315082902_add_consensus_to_transaction_table.exs deleted file mode 100644 index 8c8bbece0213..000000000000 --- a/apps/explorer/priv/repo/migrations/20220315082902_add_consensus_to_transaction_table.exs +++ /dev/null @@ -1,11 +0,0 @@ -defmodule Explorer.Repo.Migrations.AddConsensusToTransactionTable do - use Ecto.Migration - - def change do - alter table("transactions") do - add(:block_consensus, :boolean, default: true) - end - - create(index(:transactions, :block_consensus)) - end -end diff --git a/apps/explorer/priv/repo/migrations/20220315093927_add_block_timestamp_to_transaction_table.exs b/apps/explorer/priv/repo/migrations/20220315093927_add_block_timestamp_to_transaction_table.exs deleted file mode 100644 index 7303c9fce1d0..000000000000 --- a/apps/explorer/priv/repo/migrations/20220315093927_add_block_timestamp_to_transaction_table.exs +++ /dev/null @@ -1,11 +0,0 @@ -defmodule Explorer.Repo.Migrations.AddBlockTimestampToTransactionTable do - use Ecto.Migration - - def change do - alter table("transactions") do - add(:block_timestamp, :utc_datetime_usec) - end - - create(index(:transactions, :block_timestamp)) - end -end diff --git a/apps/explorer/priv/repo/migrations/20231212101547_add_block_timestamp_and_consensus_to_transactions.exs b/apps/explorer/priv/repo/migrations/20231212101547_add_block_timestamp_and_consensus_to_transactions.exs new file mode 100644 index 000000000000..458e29604383 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231212101547_add_block_timestamp_and_consensus_to_transactions.exs @@ -0,0 +1,13 @@ +defmodule Explorer.Repo.Migrations.AddBlockTimestampAndConsensusToTransactions do + use Ecto.Migration + + def change do + alter table(:transactions) do + add_if_not_exists(:block_timestamp, :utc_datetime_usec) + add_if_not_exists(:block_consensus, :boolean, default: true) + end + + create_if_not_exists(index(:transactions, :block_timestamp)) + create_if_not_exists(index(:transactions, :block_consensus)) + end +end diff --git a/apps/explorer/priv/repo/migrations/20231212102127_create_migrations_status.exs b/apps/explorer/priv/repo/migrations/20231212102127_create_migrations_status.exs new file mode 100644 index 000000000000..0b8bf54a5c3b --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231212102127_create_migrations_status.exs @@ -0,0 +1,12 @@ +defmodule Explorer.Repo.Migrations.CreateMigrationsStatus do + use Ecto.Migration + + def change do + create table(:migrations_status, primary_key: false) do + add(:migration_name, :string, primary_key: true) + add(:status, :string) + + timestamps() + end + end +end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 739e051ac592..2bbbaebcd2d0 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -869,7 +869,9 @@ defmodule Explorer.ChainTest do test "returns the correct address if it exists" do address = insert(:address) - assert {:ok, _address} = Chain.hash_to_address(address.hash) + assert {:ok, address_from_db} = Chain.hash_to_address(address.hash) + assert address_from_db.hash == address.hash + assert address_from_db.inserted_at == address.inserted_at end test "has_decompiled_code? is true if there are decompiled contracts" do @@ -918,14 +920,16 @@ defmodule Explorer.ChainTest do test "returns an address if it already exists" do address = insert(:address) - assert {:ok, _address} = Chain.find_or_insert_address_from_hash(address.hash) + assert {:ok, address_from_db} = Chain.find_or_insert_address_from_hash(address.hash) + assert address_from_db.hash == address.hash + assert address_from_db.inserted_at == address.inserted_at end test "returns an address if it doesn't exist" do hash_str = "0xcbbcd5ac86f9a50e13313633b262e16f695a90c2" {:ok, hash} = Chain.string_to_address_hash(hash_str) - assert {:ok, %Chain.Address{hash: _hash}} = Chain.find_or_insert_address_from_hash(hash) + assert {:ok, %Chain.Address{hash: ^hash}} = Chain.find_or_insert_address_from_hash(hash) end end @@ -3984,7 +3988,11 @@ defmodule Explorer.ChainTest do assert {:ok, result} = Chain.token_from_address_hash(token.contract_address_hash, options) - assert result.contract_address.smart_contract + assert address.smart_contract.address_hash == result.contract_address.smart_contract.address_hash + assert address.smart_contract.contract_code_md5 == result.contract_address.smart_contract.contract_code_md5 + assert address.smart_contract.abi == result.contract_address.smart_contract.abi + assert address.smart_contract.contract_source_code == result.contract_address.smart_contract.contract_source_code + assert address.smart_contract.name == result.contract_address.smart_contract.name end end diff --git a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs index 4c6c6fdff033..93f687907ca0 100644 --- a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs @@ -456,7 +456,7 @@ defmodule Indexer.Block.Catchup.FetcherTest do assert count(Chain.Block) == 1 assert count(Reward) == 0 - assert_receive {:block_numbers, [_block_number]}, 5_000 + assert_receive {:block_numbers, [^block_number]}, 5_000 end test "async fetches beneficiaries when entire call errors out", %{ diff --git a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs index f1f510c8ac9c..e451525869b1 100644 --- a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs +++ b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs @@ -305,7 +305,7 @@ defmodule Indexer.Fetcher.InternalTransactionTest do assert {:retry, [block.number]} == InternalTransaction.run([block.number, block.number], json_rpc_named_arguments) - assert %{block_hash: _block_hash} = Repo.get(PendingBlockOperation, block_hash) + assert %{block_hash: ^block_hash} = Repo.get(PendingBlockOperation, block_hash) end test "remove block consensus on foreign_key_violation", %{ diff --git a/apps/indexer/test/indexer/fetcher/uncle_block_test.exs b/apps/indexer/test/indexer/fetcher/uncle_block_test.exs index d5e27c9d7634..052b4b75f492 100644 --- a/apps/indexer/test/indexer/fetcher/uncle_block_test.exs +++ b/apps/indexer/test/indexer/fetcher/uncle_block_test.exs @@ -196,7 +196,7 @@ defmodule Indexer.Fetcher.UncleBlockTest do ]} end) - assert {:retry, [_entry]} = + assert {:retry, [^entry]} = UncleBlock.run(entries, %Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments}) end end diff --git a/config/runtime.exs b/config/runtime.exs index afcd147129a8..9c710d4ac145 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -456,6 +456,10 @@ config :explorer, Explorer.MicroserviceInterfaces.BENS, service_url: System.get_env("MICROSERVICE_BENS_URL"), enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_BENS_ENABLED") +config :explorer, Explorer.TransactionsDenormalizationMigrator, + batch_size: ConfigHelper.parse_integer_env_var("DENORMALIZATION_MIGRATION_BATCH_SIZE", 500), + concurrency: ConfigHelper.parse_integer_env_var("DENORMALIZATION_MIGRATION_CONCURRENCY", 10) + ############### ### Indexer ### ############### From efef76742a107e00134155d8a0829684834ee7f7 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Fri, 15 Dec 2023 15:01:15 +0600 Subject: [PATCH 233/607] Denormalization improvements --- .dialyzer-ignore | 2 +- apps/explorer/lib/explorer/chain.ex | 29 ++- .../explorer/chain/cache/gas_price_oracle.ex | 217 ++++++++++++------ .../explorer/chain/denormalization_helper.ex | 4 + .../lib/explorer/utility/migration_status.ex | 1 + ...actions_denormalization_migrator_test.exs} | 0 docker-compose/envs/common-blockscout.env | 4 +- 7 files changed, 175 insertions(+), 82 deletions(-) rename apps/explorer/test/explorer/{transactions_denormalization_migrator_test.ex => transactions_denormalization_migrator_test.exs} (100%) diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 79cfeb45051b..a9bc3a2c503f 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -23,4 +23,4 @@ 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:169 +lib/explorer/chain/transaction.ex:170 diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 4e0e5ac0c3af..19983d00456a 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -353,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 diff --git a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex index b1d0c10572c9..d6d85c2b42f2 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex @@ -14,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) diff --git a/apps/explorer/lib/explorer/chain/denormalization_helper.ex b/apps/explorer/lib/explorer/chain/denormalization_helper.ex index 79c9d4c51a1e..0199fc7359e1 100644 --- a/apps/explorer/lib/explorer/chain/denormalization_helper.ex +++ b/apps/explorer/lib/explorer/chain/denormalization_helper.ex @@ -5,6 +5,7 @@ defmodule Explorer.Chain.DenormalizationHelper do 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 @@ -13,6 +14,7 @@ defmodule Explorer.Chain.DenormalizationHelper do 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 @@ -26,6 +28,7 @@ defmodule Explorer.Chain.DenormalizationHelper do end end + @spec extend_transaction_preload(list()) :: list() def extend_transaction_preload(preloads) do if denormalization_finished?() do preloads @@ -34,6 +37,7 @@ defmodule Explorer.Chain.DenormalizationHelper do end end + @spec extend_block_preload(list()) :: list() def extend_block_preload(preloads) do if denormalization_finished?() do preloads diff --git a/apps/explorer/lib/explorer/utility/migration_status.ex b/apps/explorer/lib/explorer/utility/migration_status.ex index 21ac52de6d88..62495541bec8 100644 --- a/apps/explorer/lib/explorer/utility/migration_status.ex +++ b/apps/explorer/lib/explorer/utility/migration_status.ex @@ -9,6 +9,7 @@ defmodule Explorer.Utility.MigrationStatus do @primary_key false schema "migrations_status" do field(:migration_name, :string) + # ["started", "completed"] field(:status, :string) timestamps() diff --git a/apps/explorer/test/explorer/transactions_denormalization_migrator_test.ex b/apps/explorer/test/explorer/transactions_denormalization_migrator_test.exs similarity index 100% rename from apps/explorer/test/explorer/transactions_denormalization_migrator_test.ex rename to apps/explorer/test/explorer/transactions_denormalization_migrator_test.exs diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 07a2a8126955..f1743f72265e 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -267,4 +267,6 @@ API_V2_ENABLED=true # ACCOUNT_PRIVATE_TAGS_LIMIT=2000 # ACCOUNT_WATCHLIST_ADDRESSES_LIMIT=15 # MICROSERVICE_BENS_URL= -# MICROSERVICE_BENS_ENABLED= \ No newline at end of file +# MICROSERVICE_BENS_ENABLED= +# DENORMALIZATION_MIGRATION_BATCH_SIZE= +# DENORMALIZATION_MIGRATION_CONCURRENCY= From 5c05a7e0bf846b9ead50fb20c3b7c05953ac1e61 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Mon, 18 Dec 2023 11:29:13 +0600 Subject: [PATCH 234/607] Move denormalization migration modules to a separate dir --- apps/explorer/config/config.exs | 2 +- apps/explorer/config/runtime/test.exs | 2 +- apps/explorer/lib/explorer/application.ex | 2 +- .../lib/explorer/chain/cache/background_migrations.ex | 4 ++-- .../lib/explorer/{utility => migrator}/migration_status.ex | 2 +- .../transactions_denormalization.ex} | 4 ++-- .../transactions_denormalization_migrator_test.exs | 7 ++++--- config/runtime.exs | 2 +- 8 files changed, 13 insertions(+), 12 deletions(-) rename apps/explorer/lib/explorer/{utility => migrator}/migration_status.ex (94%) rename apps/explorer/lib/explorer/{transactions_denormalization_migrator.ex => migrator/transactions_denormalization.ex} (96%) rename apps/explorer/test/explorer/{ => migrator}/transactions_denormalization_migrator_test.exs (83%) diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 88d38c68a956..1dac8ff0963b 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -113,7 +113,7 @@ config :explorer, Explorer.TokenTransferTokenIdMigration.Supervisor, enabled: tr config :explorer, Explorer.TokenInstanceOwnerAddressMigration.Supervisor, enabled: true -config :explorer, Explorer.TransactionsDenormalizationMigrator, enabled: true +config :explorer, Explorer.Migrator.TransactionsDenormalization, enabled: true config :explorer, Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand, enabled: true diff --git a/apps/explorer/config/runtime/test.exs b/apps/explorer/config/runtime/test.exs index 37ce93b2cb3b..367f4793f27e 100644 --- a/apps/explorer/config/runtime/test.exs +++ b/apps/explorer/config/runtime/test.exs @@ -37,7 +37,7 @@ config :explorer, Explorer.TokenTransferTokenIdMigration.Supervisor, enabled: fa config :explorer, Explorer.TokenInstanceOwnerAddressMigration.Supervisor, enabled: false -config :explorer, Explorer.TransactionsDenormalizationMigrator, enabled: false +config :explorer, Explorer.Migrator.TransactionsDenormalization, enabled: false config :explorer, realtime_events_sender: Explorer.Chain.Events.SimpleSender diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 497043d143d8..dfed4625c879 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -128,7 +128,7 @@ defmodule Explorer.Application do configure(Explorer.TokenInstanceOwnerAddressMigration.Supervisor), sc_microservice_configure(Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand), configure(Explorer.Chain.Cache.RootstockLockedBTC), - configure(Explorer.TransactionsDenormalizationMigrator) + configure(Explorer.Migrator.TransactionsDenormalization) ] |> List.flatten() diff --git a/apps/explorer/lib/explorer/chain/cache/background_migrations.ex b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex index 4b33076a9675..5e3b3524573b 100644 --- a/apps/explorer/lib/explorer/chain/cache/background_migrations.ex +++ b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex @@ -11,11 +11,11 @@ defmodule Explorer.Chain.Cache.BackgroundMigrations do @dialyzer :no_match - alias Explorer.TransactionsDenormalizationMigrator + alias Explorer.Migrator.TransactionsDenormalization defp handle_fallback(:denormalization_finished) do Task.start(fn -> - set_denormalization_finished(TransactionsDenormalizationMigrator.migration_finished?()) + set_denormalization_finished(TransactionsDenormalization.migration_finished?()) end) {:return, false} diff --git a/apps/explorer/lib/explorer/utility/migration_status.ex b/apps/explorer/lib/explorer/migrator/migration_status.ex similarity index 94% rename from apps/explorer/lib/explorer/utility/migration_status.ex rename to apps/explorer/lib/explorer/migrator/migration_status.ex index 62495541bec8..295a606f9632 100644 --- a/apps/explorer/lib/explorer/utility/migration_status.ex +++ b/apps/explorer/lib/explorer/migrator/migration_status.ex @@ -1,4 +1,4 @@ -defmodule Explorer.Utility.MigrationStatus do +defmodule Explorer.Migrator.MigrationStatus do @moduledoc """ Module is responsible for keeping the current status of background migrations. """ diff --git a/apps/explorer/lib/explorer/transactions_denormalization_migrator.ex b/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex similarity index 96% rename from apps/explorer/lib/explorer/transactions_denormalization_migrator.ex rename to apps/explorer/lib/explorer/migrator/transactions_denormalization.ex index 55f927d5afdb..2cdfd374cad7 100644 --- a/apps/explorer/lib/explorer/transactions_denormalization_migrator.ex +++ b/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex @@ -1,4 +1,4 @@ -defmodule Explorer.TransactionsDenormalizationMigrator do +defmodule Explorer.Migrator.TransactionsDenormalization do @moduledoc """ Migrates all transactions to have set block_consensus and block_timestamp """ @@ -9,8 +9,8 @@ defmodule Explorer.TransactionsDenormalizationMigrator do alias Explorer.Chain.Cache.BackgroundMigrations alias Explorer.Chain.Transaction + alias Explorer.Migrator.MigrationStatus alias Explorer.Repo - alias Explorer.Utility.MigrationStatus @default_batch_size 500 @migration_name "denormalization" diff --git a/apps/explorer/test/explorer/transactions_denormalization_migrator_test.exs b/apps/explorer/test/explorer/migrator/transactions_denormalization_migrator_test.exs similarity index 83% rename from apps/explorer/test/explorer/transactions_denormalization_migrator_test.exs rename to apps/explorer/test/explorer/migrator/transactions_denormalization_migrator_test.exs index 12b984837022..8505844a7927 100644 --- a/apps/explorer/test/explorer/transactions_denormalization_migrator_test.exs +++ b/apps/explorer/test/explorer/migrator/transactions_denormalization_migrator_test.exs @@ -1,8 +1,9 @@ -defmodule Explorer.TransactionsDenormalizationMigratorTest do +defmodule Explorer.Migrator.TransactionsDenormalizationTest do use Explorer.DataCase, async: false alias Explorer.Chain.Transaction - alias Explorer.{Repo, TransactionsDenormalizationMigrator} + alias Explorer.Migrator.TransactionsDenormalization + alias Explorer.Repo describe "Migrate transactions" do test "Set block_consensus and block_timestamp for not processed transactions" do @@ -19,7 +20,7 @@ defmodule Explorer.TransactionsDenormalizationMigratorTest do assert not is_nil(timestamp) end) - TransactionsDenormalizationMigrator.start_link([]) + TransactionsDenormalization.start_link([]) Process.sleep(100) Transaction diff --git a/config/runtime.exs b/config/runtime.exs index 9c710d4ac145..e8f4de449691 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -456,7 +456,7 @@ config :explorer, Explorer.MicroserviceInterfaces.BENS, service_url: System.get_env("MICROSERVICE_BENS_URL"), enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_BENS_ENABLED") -config :explorer, Explorer.TransactionsDenormalizationMigrator, +config :explorer, Explorer.Migrator.TransactionsDenormalization, batch_size: ConfigHelper.parse_integer_env_var("DENORMALIZATION_MIGRATION_BATCH_SIZE", 500), concurrency: ConfigHelper.parse_integer_env_var("DENORMALIZATION_MIGRATION_CONCURRENCY", 10) From 62eb8da2d316eba3a9cdba0c57b247da22f5fe3f Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Mon, 18 Dec 2023 14:38:15 +0600 Subject: [PATCH 235/607] Fix migration status upsert --- apps/explorer/lib/explorer/migrator/migration_status.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/migrator/migration_status.ex b/apps/explorer/lib/explorer/migrator/migration_status.ex index 295a606f9632..01a7bbd54084 100644 --- a/apps/explorer/lib/explorer/migrator/migration_status.ex +++ b/apps/explorer/lib/explorer/migrator/migration_status.ex @@ -27,6 +27,6 @@ defmodule Explorer.Migrator.MigrationStatus do def set_status(migration_name, status) do %{migration_name: migration_name, status: status} |> changeset() - |> Repo.insert(on_conflict: :replace_all, conflict_target: :migration_name) + |> Repo.insert(on_conflict: {:replace_all_except, [:inserted_at]}, conflict_target: :migration_name) end end From e52c187ff334c4ed3fa458e3f48b7d9e543b3c28 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Mon, 18 Dec 2023 17:36:31 +0600 Subject: [PATCH 236/607] Set timeout: :infinity for denaromalization migration --- .../lib/explorer/migrator/transactions_denormalization.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex b/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex index 2cdfd374cad7..166d7f834001 100644 --- a/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex +++ b/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex @@ -65,7 +65,7 @@ defmodule Explorer.Migrator.TransactionsDenormalization do unprocessed_transactions_query() |> select([t], t.hash) |> limit(^limit) - |> Repo.all() + |> Repo.all(timeout: :infinity) end defp unprocessed_transactions_query do From ed1f2479d3a7f67f7a0c10eddf25e137b02ceae6 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 21 Dec 2023 15:56:49 +0300 Subject: [PATCH 237/607] Update CHANGELOG entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d07488b208e1..dbc203b9dcde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - [#9009](https://github.com/blockscout/blockscout/pull/9009) - Index for block refetch_needed - [#9006](https://github.com/blockscout/blockscout/pull/9006) - Drop unused indexes on address_current_token_balances table - [#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 ## Current @@ -101,7 +102,6 @@ - [#8956](https://github.com/blockscout/blockscout/pull/8956) - Refine docker-compose config structure - [#8911](https://github.com/blockscout/blockscout/pull/8911) - Set client_connection_check_interval for main Postgres DB in docker-compose setup -- [#5322](https://github.com/blockscout/blockscout/pull/5322) - DB denormalization: block consensus and timestamp in transaction table
Dependencies version bumps From 4df5df71fa9968823a6acb0ac5617b20b547c679 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 21 Dec 2023 17:02:16 +0300 Subject: [PATCH 238/607] Change log topic type in the DB to bytea (#9000) * Change log topic type * Process review comment * Update apps/explorer/lib/explorer/chain/token_transfer.ex Co-authored-by: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> * Accept a new log type by TransactionAction and PolygonEdge modules * mix format * Fix merging conflicts * Update CHANGE LOG entry --------- Co-authored-by: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Co-authored-by: POA <33550681+poa@users.noreply.github.com> --- .dialyzer-ignore | 8 +- CHANGELOG.md | 1 + .../channels/websocket_v2_test.exs | 26 ++-- .../api/rpc/eth_controller_test.exs | 68 +++++++--- .../api/rpc/logs_controller_test.exs | 59 ++++++--- .../api/rpc/transaction_controller_test.exs | 18 ++- .../api/v2/address_controller_test.exs | 10 +- .../api/v2/transaction_controller_test.exs | 11 +- apps/explorer/lib/explorer/chain.ex | 26 ---- apps/explorer/lib/explorer/chain/log.ex | 33 +++-- .../lib/explorer/chain/token_transfer.ex | 29 +++- ...213152332_alter_log_topic_columns_type.exs | 18 +++ .../address_log_csv_exporter_test.exs | 18 ++- .../test/explorer/chain/import_test.exs | 22 ++- .../explorer/test/explorer/chain/log_test.exs | 30 +++-- .../explorer/chain/smart_contract_test.exs | 3 +- .../explorer/chain/token_transfer_test.exs | 24 ++++ apps/explorer/test/explorer/chain_test.exs | 62 ++++----- .../test/explorer/etherscan/logs_test.exs | 125 ++++++++++-------- .../fetcher/polygon_edge/deposit_execute.ex | 8 +- .../fetcher/polygon_edge/withdrawal.ex | 8 +- apps/indexer/lib/indexer/helper.ex | 9 ++ .../temporary/uncataloged_token_transfers.ex | 4 +- .../indexer/transform/transaction_actions.ex | 40 ++++-- 24 files changed, 436 insertions(+), 224 deletions(-) create mode 100644 apps/explorer/priv/repo/migrations/20231213152332_alter_log_topic_columns_type.exs diff --git a/.dialyzer-ignore b/.dialyzer-ignore index a9bc3a2c503f..ba3b24756c02 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -14,10 +14,10 @@ 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index dbc203b9dcde..f852bfdf7744 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - [#9009](https://github.com/blockscout/blockscout/pull/9009) - Index for block refetch_needed - [#9006](https://github.com/blockscout/blockscout/pull/9006) - Drop unused indexes on address_current_token_balances table +- [#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 diff --git a/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs b/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs index bfb0424424f0..2e06c53dbdc6 100644 --- a/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs +++ b/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs @@ -6,7 +6,15 @@ defmodule BlockScoutWeb.WebsocketV2Test do alias Explorer.Chain.{Address, Import, Token, TokenTransfer, Transaction} alias Explorer.Repo + @first_topic_hex_string "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + @second_topic_hex_string "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca" + @third_topic_hex_string "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d" + describe "websocket v2" do + {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(@first_topic_hex_string) + {:ok, second_topic} = Explorer.Chain.Hash.Full.cast(@second_topic_hex_string) + {:ok, third_topic} = Explorer.Chain.Hash.Full.cast(@third_topic_hex_string) + @import_data %{ blocks: %{ params: [ @@ -34,9 +42,9 @@ 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", @@ -46,9 +54,9 @@ 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: 1, transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", @@ -58,9 +66,9 @@ 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: 2, transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs index 4b68122fd25b..b77a23a39a30 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs @@ -5,6 +5,12 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do alias Explorer.Repo alias Indexer.Fetcher.CoinBalanceOnDemand + @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" + @first_topic_hex_string_2 "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + + @second_topic_hex_string_1 "0x00000000000000000000000098a9dc37d3650b5b30d6c12789b3881ee0b70c16" + @second_topic_hex_string_2 "0x000000000000000000000000e2680fd7cdbb04e9087a647ad4d023ef6c8fb4e2" + setup do mocked_json_rpc_named_arguments = [ transport: EthereumJSONRPC.Mox, @@ -27,6 +33,11 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do defp params(api_params, params), do: Map.put(api_params, "params", params) + defp topic(topic_hex_string) do + {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string) + topic + end + describe "eth_get_logs" do setup do %{ @@ -108,10 +119,10 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do address: address, transaction: transaction, data: "0x010101", - first_topic: "0x01" + first_topic: topic(@first_topic_hex_string_1) ) - params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01"]}]) + params = params(api_params, [%{"address" => to_string(address.hash), "topics" => [@first_topic_hex_string_1]}]) assert response = conn @@ -134,7 +145,7 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do block_number: block.number, transaction: transaction, data: "0x010101", - first_topic: "0x01" + first_topic: topic(@first_topic_hex_string_1) ) insert(:log, @@ -143,10 +154,13 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do block_number: block.number, transaction: transaction, data: "0x020202", - first_topic: "0x00" + first_topic: topic(@first_topic_hex_string_2) ) - params = params(api_params, [%{"address" => to_string(address.hash), "topics" => [["0x01", "0x00"]]}]) + params = + params(api_params, [ + %{"address" => to_string(address.hash), "topics" => [[@first_topic_hex_string_1, @first_topic_hex_string_2]]} + ]) assert response = conn @@ -171,10 +185,11 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do block_number: block.number, address: contract_address, transaction: transaction, - first_topic: "0x01" + 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 @@ -192,7 +207,11 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do 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 = @@ -227,15 +246,24 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do address: address, transaction: transaction, data: "0x010101", - first_topic: "0x01", - second_topic: "0x02", + 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 @@ -257,8 +285,8 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do address: address, transaction: transaction, data: "0x010101", - first_topic: "0x01", - second_topic: "0x02", + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), block: block, block_number: block.number ) @@ -267,13 +295,19 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do address: address, transaction: transaction, data: "0x020202", - first_topic: "0x01", - second_topic: "0x03", + 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 diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs index 66fc27dc71db..2691f1e7e825 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs @@ -4,6 +4,22 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do alias BlockScoutWeb.API.RPC.LogsController alias Explorer.Chain.{Log, Transaction} + @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" + @first_topic_hex_string_2 "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + + @second_topic_hex_string_1 "0x00000000000000000000000098a9dc37d3650b5b30d6c12789b3881ee0b70c16" + @second_topic_hex_string_2 "0x000000000000000000000000e2680fd7cdbb04e9087a647ad4d023ef6c8fb4e2" + + @third_topic_hex_string_1 "0x0000000000000000000000005079fc00f00f30000e0c8c083801cfde000008b6" + + @fourth_topic_hex_string_1 "0x8c9b7729443a4444242342b2ca385a239a5c1d76a88473e1cd2ab0c70dd1b9c7" + @fourth_topic_hex_string_2 "0x232b688786cc0d24a11e07563c1bfa129537cec9385dc5b1fb8f86462977239b" + + defp topic(topic_hex_string) do + {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string) + topic + end + describe "getLogs" do test "without fromBlock, toBlock, address, and topic{x}", %{conn: conn} do params = %{ @@ -434,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) @@ -492,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) @@ -541,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) @@ -589,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) @@ -791,7 +807,12 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do third_topic: third_topic, fourth_topic: fourth_topic }) do - [first_topic, second_topic, third_topic, fourth_topic] + [ + first_topic && Explorer.Chain.Hash.to_string(first_topic), + second_topic && Explorer.Chain.Hash.to_string(second_topic), + third_topic && Explorer.Chain.Hash.to_string(third_topic), + fourth_topic && Explorer.Chain.Hash.to_string(fourth_topic) + ] end defp integer_to_hex(integer), do: "0x" <> String.downcase(Integer.to_string(integer, 16)) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs index 4a828099ce6e..8e30a3e68046 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs @@ -5,8 +5,16 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do @moduletag capture_log: true + @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" + @second_topic_hex_string_1 "0x00000000000000000000000098a9dc37d3650b5b30d6c12789b3881ee0b70c16" + setup :verify_on_exit! + defp topic(topic_hex_string) do + {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string) + topic + end + describe "gettxreceiptstatus" do test "with missing txhash", %{conn: conn} do params = %{ @@ -414,8 +422,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do insert(:log, address: address, transaction: transaction, - first_topic: "first topic", - second_topic: "second topic", + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), block: block, block_number: block.number ) @@ -491,8 +499,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do insert(:log, address: address, transaction: transaction, - first_topic: "first topic", - second_topic: "second topic", + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), block: block, block_number: block.number ) @@ -520,7 +528,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do %{ "address" => "#{address.hash}", "data" => "#{log.data}", - "topics" => ["first topic", "second topic", nil, nil], + "topics" => [@first_topic_hex_string_1, @second_topic_hex_string_1, nil, nil], "index" => "#{log.index}" } ], diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index d3749c1628af..34829785f55e 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -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 diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs index ede7f16822d1..9fdad1782da8 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs @@ -8,6 +8,13 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do alias Explorer.Chain.{Address, InternalTransaction, Log, Token, TokenTransfer, Transaction} alias Explorer.Repo + @first_topic_hex_string_1 "0x99e7b0ba56da2819c37c047f0511fd2bf6c9b4e27b4a979a19d6da0f74be8155" + + defp topic(topic_hex_string) do + {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string) + topic + end + setup do Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.TransactionsApiV2.child_id()) Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.TransactionsApiV2.child_id()) @@ -976,7 +983,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do index: 1, block: tx.block, block_number: tx.block_number, - first_topic: "0x99e7b0ba56da2819c37c047f0511fd2bf6c9b4e27b4a979a19d6da0f74be8155", + first_topic: topic(@first_topic_hex_string_1), data: "0x000000000000000000000000dc2b93f3291030f3f7a6d9363ac37757f7ad5c4300000000000000000000000000000000000000000000000000002824369a100000000000000000000000000046b555cb3962bf9533c437cbd04a2f702dfdb999000000000000000000000000000000000000000000000000000014121b4d0800000000000000000000000000faf7a981360c2fab3a5ab7b3d6d8d0cf97a91eb9000000000000000000000000000000000000000000000000000014121b4d0800" ) @@ -1039,7 +1046,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do index: 1, block: tx.block, block_number: tx.block_number, - first_topic: "0x99e7b0ba56da2819c37c047f0511fd2bf6c9b4e27b4a979a19d6da0f74be8155", + first_topic: topic(@first_topic_hex_string_1), data: "0x000000000000000000000000dc2b93f3291030f3f7a6d9363ac37757f7ad5c4300000000000000000000000000000000000000000000000000002824369a100000000000000000000000000046b555cb3962bf9533c437cbd04a2f702dfdb999000000000000000000000000000000000000000000000000000014121b4d0800000000000000000000000000faf7a981360c2fab3a5ab7b3d6d8d0cf97a91eb9000000000000000000000000000000000000000000000000000014121b4d0800" ) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 19983d00456a..f6a4e3615fa4 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -3590,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 -> diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 2eb2c2ce9275..e1c87a8a1e60 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -35,10 +35,10 @@ 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(), @@ -48,10 +48,10 @@ defmodule Explorer.Chain.Log do @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) @@ -190,9 +190,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 - <> = :binary.encode_unsigned(number) + if is_nil(log.first_topic) do + {{:error, :could_not_decode}, events_acc} + else + <> = log.first_topic.bytes if Map.has_key?(events_acc, method_id) do {events_acc[method_id], events_acc} @@ -200,8 +201,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 +242,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} diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index 8b9b878914df..c53414e209a2 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -28,7 +28,7 @@ defmodule Explorer.Chain.TokenTransfer do import Ecto.Query, only: [from: 2, limit: 2, where: 3, join: 5, order_by: 3, preload: 3] alias Explorer.Chain - alias Explorer.Chain.{Address, Block, DenormalizationHelper, Hash, TokenTransfer, Transaction} + alias Explorer.Chain.{Address, Block, DenormalizationHelper, Hash, Log, TokenTransfer, Transaction} alias Explorer.Chain.Token.Instance alias Explorer.{PagingOptions, Repo} @@ -370,4 +370,31 @@ defmodule Explorer.Chain.TokenTransfer do where: block.consensus == true ) end + + @doc """ + Returns a list of block numbers token transfer `t:Log.t/0`s that don't have an + associated `t:TokenTransfer.t/0` record. + """ + @spec uncataloged_token_transfer_block_numbers :: {:ok, [non_neg_integer()]} + def uncataloged_token_transfer_block_numbers do + query = + from(l in Log, + as: :log, + where: + l.first_topic == ^@constant or + l.first_topic == ^@erc1155_single_transfer_signature or + l.first_topic == ^@erc1155_batch_transfer_signature, + where: + not exists( + from(tf in TokenTransfer, + where: tf.transaction_hash == parent_as(:log).transaction_hash, + where: tf.log_index == parent_as(:log).index + ) + ), + select: l.block_number, + distinct: l.block_number + ) + + Repo.stream_reduce(query, [], &[&1 | &2]) + end end diff --git a/apps/explorer/priv/repo/migrations/20231213152332_alter_log_topic_columns_type.exs b/apps/explorer/priv/repo/migrations/20231213152332_alter_log_topic_columns_type.exs new file mode 100644 index 000000000000..649c95d0dbf1 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231213152332_alter_log_topic_columns_type.exs @@ -0,0 +1,18 @@ +defmodule Explorer.Repo.Migrations.AlterLogTopicColumnsType do + use Ecto.Migration + + def change do + execute(""" + ALTER TABLE logs + ALTER COLUMN first_topic TYPE bytea + USING CAST(REPLACE(first_topic, '0x', '\\x') as bytea), + ALTER COLUMN second_topic TYPE bytea + USING CAST(REPLACE(second_topic, '0x', '\\x') as bytea), + ALTER COLUMN third_topic TYPE bytea + USING CAST(REPLACE(third_topic, '0x', '\\x') as bytea), + ALTER COLUMN fourth_topic TYPE bytea + USING CAST(REPLACE(fourth_topic, '0x', '\\x') as bytea) + ; + """) + end +end diff --git a/apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs index bff1ca818d33..e70b2eb9cb25 100644 --- a/apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs @@ -4,6 +4,16 @@ defmodule Explorer.Chain.AddressLogCsvExporterTest do alias Explorer.Chain.Address alias Explorer.Chain.CSVExport.AddressLogCsvExporter + @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" + @second_topic_hex_string_1 "0x00000000000000000000000098a9dc37d3650b5b30d6c12789b3881ee0b70c16" + @third_topic_hex_string_1 "0x0000000000000000000000005079fc00f00f30000e0c8c083801cfde000008b6" + @fourth_topic_hex_string_1 "0x8c9b7729443a4444242342b2ca385a239a5c1d76a88473e1cd2ab0c70dd1b9c7" + + defp topic(topic_hex_string) do + {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string) + topic + end + describe "export/3" do test "exports address logs to csv" do address = insert(:address) @@ -21,10 +31,10 @@ defmodule Explorer.Chain.AddressLogCsvExporterTest do block: transaction.block, block_number: transaction.block_number, data: "0x12", - first_topic: "0x13", - second_topic: "0x14", - third_topic: "0x15", - fourth_topic: "0x16" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + third_topic: topic(@third_topic_hex_string_1), + fourth_topic: topic(@fourth_topic_hex_string_1) ) from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime) diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs index 322ca7a7ff80..a8d62bfcad6c 100644 --- a/apps/explorer/test/explorer/chain/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -22,9 +22,16 @@ defmodule Explorer.Chain.ImportTest do @moduletag :capturelog + @first_topic_hex_string "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + @second_topic_hex_string "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca" + @third_topic_hex_string "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d" + doctest Import describe "all/1" do + {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(@first_topic_hex_string) + {:ok, second_topic} = Explorer.Chain.Hash.Full.cast(@second_topic_hex_string) + {:ok, third_topic} = Explorer.Chain.Hash.Full.cast(@third_topic_hex_string) # set :timeout options to cover lines that use the timeout override when available @import_data %{ blocks: %{ @@ -91,9 +98,9 @@ defmodule Explorer.Chain.ImportTest do block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", - first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", - third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", + first_topic: first_topic, + second_topic: second_topic, + third_topic: third_topic, fourth_topic: nil, index: 0, transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", @@ -165,6 +172,9 @@ defmodule Explorer.Chain.ImportTest do } test "with valid data" do + {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(@first_topic_hex_string) + {:ok, second_topic} = Explorer.Chain.Hash.Full.cast(@second_topic_hex_string) + {:ok, third_topic} = Explorer.Chain.Hash.Full.cast(@third_topic_hex_string) difficulty = Decimal.new(340_282_366_920_938_463_463_374_607_431_768_211_454) total_difficulty = Decimal.new(12_590_447_576_074_723_148_144_860_474_975_121_280_509) token_transfer_amount = Decimal.new(1_000_000_000_000_000_000) @@ -276,9 +286,9 @@ defmodule Explorer.Chain.ImportTest do 167, 100, 0, 0>> }, index: 0, - first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", - third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", + first_topic: ^first_topic, + second_topic: ^second_topic, + third_topic: ^third_topic, fourth_topic: nil, transaction_hash: %Hash{ byte_count: 32, diff --git a/apps/explorer/test/explorer/chain/log_test.exs b/apps/explorer/test/explorer/chain/log_test.exs index 6842a6b979fb..87881776de29 100644 --- a/apps/explorer/test/explorer/chain/log_test.exs +++ b/apps/explorer/test/explorer/chain/log_test.exs @@ -7,6 +7,13 @@ defmodule Explorer.Chain.LogTest do alias Explorer.Chain.{Log, SmartContract} alias Explorer.Repo + @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" + + defp topic(topic_hex_string) do + {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string) + topic + end + setup :set_mox_from_context doctest Log @@ -36,18 +43,21 @@ defmodule Explorer.Chain.LogTest do params_for( :log, address_hash: build(:address).hash, - first_topic: "ham", + first_topic: @first_topic_hex_string_1, transaction_hash: build(:transaction).hash, block_hash: build(:block).hash ) - assert %Changeset{changes: %{first_topic: "ham"}, valid?: true} = Log.changeset(%Log{}, params) + result = Log.changeset(%Log{}, params) + + assert result.valid? == true + assert result.changes.first_topic == topic(@first_topic_hex_string_1) end test "assigns optional attributes" do - params = Map.put(params_for(:log), :first_topic, "ham") + params = Map.put(params_for(:log), :first_topic, topic(@first_topic_hex_string_1)) changeset = Log.changeset(%Log{}, params) - assert changeset.changes.first_topic === "ham" + assert changeset.changes.first_topic === topic(@first_topic_hex_string_1) end end @@ -99,9 +109,9 @@ defmodule Explorer.Chain.LogTest do insert(:log, address: to_address, transaction: transaction, - first_topic: topic1, - second_topic: topic2, - third_topic: topic3, + first_topic: topic(topic1), + second_topic: topic(topic2), + third_topic: topic(topic3), fourth_topic: nil, data: data ) @@ -153,9 +163,9 @@ defmodule Explorer.Chain.LogTest do log = insert(:log, transaction: transaction, - first_topic: topic1, - second_topic: topic2, - third_topic: topic3, + first_topic: topic(topic1), + second_topic: topic(topic2), + third_topic: topic(topic3), fourth_topic: nil, data: data ) diff --git a/apps/explorer/test/explorer/chain/smart_contract_test.exs b/apps/explorer/test/explorer/chain/smart_contract_test.exs index 8bd0b1e5818a..f7c45251b0e4 100644 --- a/apps/explorer/test/explorer/chain/smart_contract_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract_test.exs @@ -2,9 +2,8 @@ defmodule Explorer.Chain.SmartContractTest do use Explorer.DataCase, async: false import Mox - alias Explorer.{Chain, PagingOptions} + alias Explorer.Chain alias Explorer.Chain.{Address, SmartContract} - alias Explorer.Chain.Hash alias Explorer.Chain.SmartContract.Proxy doctest Explorer.Chain.SmartContract diff --git a/apps/explorer/test/explorer/chain/token_transfer_test.exs b/apps/explorer/test/explorer/chain/token_transfer_test.exs index 29165457b773..3139f318cb39 100644 --- a/apps/explorer/test/explorer/chain/token_transfer_test.exs +++ b/apps/explorer/test/explorer/chain/token_transfer_test.exs @@ -325,4 +325,28 @@ defmodule Explorer.Chain.TokenTransferTest do assert Enum.member?(page_two, transaction_one_bytes) == true end end + + describe "uncataloged_token_transfer_block_numbers/0" do + test "returns a list of block numbers" do + block = insert(:block) + address = insert(:address) + + log = + insert(:token_transfer_log, + transaction: + insert(:transaction, + block_number: block.number, + block_hash: block.hash, + cumulative_gas_used: 0, + gas_used: 0, + index: 0 + ), + block: block, + address_hash: address.hash + ) + + block_number = log.block_number + assert {:ok, [^block_number]} = TokenTransfer.uncataloged_token_transfer_block_numbers() + end + end end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 2bbbaebcd2d0..083d50d0f6dc 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -23,7 +23,6 @@ defmodule Explorer.ChainTest do Token, TokenTransfer, Transaction, - SmartContract, Wei } @@ -38,6 +37,10 @@ defmodule Explorer.ChainTest do alias Explorer.Counters.AddressesWithBalanceCounter alias Explorer.Counters.AddressesCounter + @first_topic_hex_string "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + @second_topic_hex_string "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca" + @third_topic_hex_string "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d" + doctest Explorer.Chain setup :set_mox_global @@ -316,6 +319,9 @@ defmodule Explorer.ChainTest do block_number: transaction1.block_number ) + first_topic_hex_string = "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" + {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(first_topic_hex_string) + transaction2 = :transaction |> insert(from_address: address) @@ -326,11 +332,11 @@ defmodule Explorer.ChainTest do transaction: transaction2, index: 2, address: address, - first_topic: "test", + first_topic: first_topic, block_number: transaction2.block_number ) - [found_log] = Chain.address_to_logs(address_hash, false, topic: "test") + [found_log] = Chain.address_to_logs(address_hash, false, topic: first_topic_hex_string) assert found_log.transaction.hash == transaction2.hash end @@ -343,13 +349,16 @@ defmodule Explorer.ChainTest do |> insert(to_address: address) |> with_block() + fourth_topic_hex_string = "0x927abf391899d10d331079a63caffa905efa7075a44a7bbd52b190db4c4308fb" + {:ok, fourth_topic} = Explorer.Chain.Hash.Full.cast(fourth_topic_hex_string) + insert(:log, block: transaction1.block, block_number: transaction1.block_number, transaction: transaction1, index: 1, address: address, - fourth_topic: "test" + fourth_topic: fourth_topic ) transaction2 = @@ -365,7 +374,7 @@ defmodule Explorer.ChainTest do address: address ) - [found_log] = Chain.address_to_logs(address_hash, false, topic: "test") + [found_log] = Chain.address_to_logs(address_hash, false, topic: fourth_topic_hex_string) assert found_log.transaction.hash == transaction1.hash end @@ -1238,6 +1247,10 @@ defmodule Explorer.ChainTest do # Full tests in `test/explorer/import_test.exs` describe "import/1" do + {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(@first_topic_hex_string) + {:ok, second_topic} = Explorer.Chain.Hash.Full.cast(@second_topic_hex_string) + {:ok, third_topic} = Explorer.Chain.Hash.Full.cast(@third_topic_hex_string) + @import_data %{ blocks: %{ params: [ @@ -1293,9 +1306,9 @@ defmodule Explorer.ChainTest do block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", - first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", - third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", + first_topic: first_topic, + second_topic: second_topic, + third_topic: third_topic, fourth_topic: nil, index: 0, transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", @@ -1362,6 +1375,9 @@ defmodule Explorer.ChainTest do } test "with valid data" do + {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(@first_topic_hex_string) + {:ok, second_topic} = Explorer.Chain.Hash.Full.cast(@second_topic_hex_string) + {:ok, third_topic} = Explorer.Chain.Hash.Full.cast(@third_topic_hex_string) difficulty = Decimal.new(340_282_366_920_938_463_463_374_607_431_768_211_454) total_difficulty = Decimal.new(12_590_447_576_074_723_148_144_860_474_975_121_280_509) token_transfer_amount = Decimal.new(1_000_000_000_000_000_000) @@ -1464,9 +1480,9 @@ defmodule Explorer.ChainTest do 167, 100, 0, 0>> }, index: 0, - first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", - third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", + first_topic: ^first_topic, + second_topic: ^second_topic, + third_topic: ^third_topic, fourth_topic: nil, transaction_hash: %Hash{ byte_count: 32, @@ -4367,30 +4383,6 @@ defmodule Explorer.ChainTest do end end - describe "uncataloged_token_transfer_block_numbers/0" do - test "returns a list of block numbers" do - block = insert(:block) - address = insert(:address) - - log = - insert(:token_transfer_log, - transaction: - insert(:transaction, - block_number: block.number, - block_hash: block.hash, - cumulative_gas_used: 0, - gas_used: 0, - index: 0 - ), - block: block, - address_hash: address.hash - ) - - block_number = log.block_number - assert {:ok, [^block_number]} = Chain.uncataloged_token_transfer_block_numbers() - end - end - describe "address_to_balances_by_day/1" do test "return a list of balances by day" do address = insert(:address) diff --git a/apps/explorer/test/explorer/etherscan/logs_test.exs b/apps/explorer/test/explorer/etherscan/logs_test.exs index 5da949541c45..b5953407f83c 100644 --- a/apps/explorer/test/explorer/etherscan/logs_test.exs +++ b/apps/explorer/test/explorer/etherscan/logs_test.exs @@ -6,6 +6,25 @@ defmodule Explorer.Etherscan.LogsTest do alias Explorer.Etherscan.Logs alias Explorer.Chain.Transaction + @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" + @first_topic_hex_string_2 "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + @first_topic_hex_string_3 "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c" + + @second_topic_hex_string_1 "0x00000000000000000000000098a9dc37d3650b5b30d6c12789b3881ee0b70c16" + @second_topic_hex_string_2 "0x000000000000000000000000e2680fd7cdbb04e9087a647ad4d023ef6c8fb4e2" + @second_topic_hex_string_3 "0x0000000000000000000000005777d92f208679db4b9778590fa3cab3ac9e2168" + + @third_topic_hex_string_1 "0x0000000000000000000000005079fc00f00f30000e0c8c083801cfde000008b6" + @third_topic_hex_string_2 "0x000000000000000000000000e2680fd7cdbb04e9087a647ad4d023ef6c8fb4e2" + @third_topic_hex_string_3 "0x0000000000000000000000000f6d9bd6fc315bbf95b5c44f4eba2b2762f8c372" + + @fourth_topic_hex_string_1 "0x8c9b7729443a4444242342b2ca385a239a5c1d76a88473e1cd2ab0c70dd1b9c7" + + defp topic(topic_hex_string) do + {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string) + topic + end + describe "list_logs/1" do test "with empty db" do contract_address = build(:contract_address) @@ -211,13 +230,13 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some topic" + first_topic: topic(@first_topic_hex_string_1) ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some other topic" + first_topic: topic(@first_topic_hex_string_2) ] log1 = insert(:log, log1_details) @@ -247,15 +266,15 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some first topic", - second_topic: "some second topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1) ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some first topic", - second_topic: "some OTHER second topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_2) ] _log1 = insert(:log, log1_details) @@ -288,15 +307,15 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some first topic", - second_topic: "some second topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1) ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some OTHER first topic", - second_topic: "some OTHER second topic" + first_topic: topic(@first_topic_hex_string_2), + second_topic: topic(@second_topic_hex_string_2) ] log1 = insert(:log, log1_details) @@ -327,14 +346,14 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some first topic", + first_topic: topic(@first_topic_hex_string_1), block_number: block.number ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some OTHER first topic", + first_topic: topic(@first_topic_hex_string_2), block_number: block.number ] @@ -366,16 +385,16 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some first topic", - second_topic: "some second topic", + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), block_number: block.number ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some OTHER first topic", - second_topic: "some OTHER second topic", + first_topic: topic(@first_topic_hex_string_2), + second_topic: topic(@second_topic_hex_string_2), block_number: block.number ] @@ -409,27 +428,27 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some first topic", - second_topic: "some second topic", - third_topic: "some third topic", + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + third_topic: topic(@third_topic_hex_string_1), block_number: block.number ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some OTHER first topic", - second_topic: "some OTHER second topic", - third_topic: "some OTHER third topic", + first_topic: topic(@first_topic_hex_string_2), + second_topic: topic(@second_topic_hex_string_2), + third_topic: topic(@third_topic_hex_string_2), block_number: block.number ] log3_details = [ address: contract_address, transaction: transaction, - first_topic: "some ALT first topic", - second_topic: "some ALT second topic", - third_topic: "some ALT third topic", + first_topic: topic(@first_topic_hex_string_3), + second_topic: topic(@second_topic_hex_string_3), + third_topic: topic(@third_topic_hex_string_3), block_number: block.number ] @@ -469,27 +488,27 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some first topic", - second_topic: "some second topic", - third_topic: "some third topic", + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + third_topic: topic(@third_topic_hex_string_1), block_number: block.number ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some OTHER first topic", - second_topic: "some OTHER second topic", - third_topic: "some OTHER third topic", + first_topic: topic(@first_topic_hex_string_2), + second_topic: topic(@second_topic_hex_string_2), + third_topic: topic(@third_topic_hex_string_2), block_number: block.number ] log3_details = [ address: contract_address, transaction: transaction, - first_topic: "some ALT first topic", - second_topic: "some ALT second topic", - third_topic: "some ALT third topic", + first_topic: topic(@first_topic_hex_string_3), + second_topic: topic(@second_topic_hex_string_3), + third_topic: topic(@third_topic_hex_string_3), block_number: block.number ] @@ -529,27 +548,27 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some topic", - second_topic: "some second topic", - third_topic: "some third topic", + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + third_topic: topic(@third_topic_hex_string_1), block_number: block.number ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some topic", - second_topic: "some OTHER second topic", - third_topic: "some third topic", + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_2), + third_topic: topic(@third_topic_hex_string_1), block_number: block.number ] log3_details = [ address: contract_address, transaction: transaction, - first_topic: "some topic", - second_topic: "some second topic", - third_topic: "some third topic", + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + third_topic: topic(@third_topic_hex_string_1), block_number: block.number ] @@ -589,28 +608,28 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some topic", - second_topic: "some second topic", + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), block_number: block.number ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some OTHER topic", - second_topic: "some OTHER second topic", - third_topic: "some OTHER third topic", - fourth_topic: "some fourth topic", + first_topic: topic(@first_topic_hex_string_2), + second_topic: topic(@second_topic_hex_string_2), + third_topic: topic(@third_topic_hex_string_2), + fourth_topic: topic(@fourth_topic_hex_string_1), block_number: block.number ] log3_details = [ address: contract_address, transaction: transaction, - first_topic: "some topic", - second_topic: "some second topic", - third_topic: "some third topic", - fourth_topic: "some fourth topic", + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + third_topic: topic(@third_topic_hex_string_1), + fourth_topic: topic(@fourth_topic_hex_string_1), block_number: block.number ] diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex index 4e2dd57d09eb..ef1ad37e4a4c 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex @@ -12,6 +12,7 @@ defmodule Indexer.Fetcher.PolygonEdge.DepositExecute do import EthereumJSONRPC, only: [quantity_to_integer: 1] import Indexer.Fetcher.PolygonEdge, only: [fill_block_range: 5, get_block_number_by_tag: 3] + import Indexer.Helper, only: [log_topic_to_string: 1] alias Explorer.{Chain, Repo} alias Explorer.Chain.Log @@ -128,8 +129,13 @@ defmodule Indexer.Fetcher.PolygonEdge.DepositExecute do @spec event_to_deposit_execute(binary(), binary(), binary(), binary()) :: map() def event_to_deposit_execute(second_topic, third_topic, l2_transaction_hash, l2_block_number) do + msg_id = + second_topic + |> log_topic_to_string() + |> quantity_to_integer() + %{ - msg_id: quantity_to_integer(second_topic), + msg_id: msg_id, l2_transaction_hash: l2_transaction_hash, l2_block_number: quantity_to_integer(l2_block_number), success: quantity_to_integer(third_topic) != 0 diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex index 80fb20568c99..098545140ac0 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex @@ -13,6 +13,7 @@ defmodule Indexer.Fetcher.PolygonEdge.Withdrawal do import EthereumJSONRPC, only: [quantity_to_integer: 1] import Explorer.Helper, only: [decode_data: 2] import Indexer.Fetcher.PolygonEdge, only: [fill_block_range: 5, get_block_number_by_tag: 3] + import Indexer.Helper, only: [log_topic_to_string: 1] alias ABI.TypeDecoder alias Explorer.{Chain, Repo} @@ -133,6 +134,11 @@ defmodule Indexer.Fetcher.PolygonEdge.Withdrawal do @spec event_to_withdrawal(binary(), map(), binary(), binary()) :: map() def event_to_withdrawal(second_topic, data, l2_transaction_hash, l2_block_number) do + msg_id = + second_topic + |> log_topic_to_string() + |> quantity_to_integer() + [data_bytes] = decode_data(data, [:bytes]) sig = binary_part(data_bytes, 0, 32) @@ -148,7 +154,7 @@ defmodule Indexer.Fetcher.PolygonEdge.Withdrawal do end %{ - msg_id: quantity_to_integer(second_topic), + msg_id: msg_id, from: from, to: to, l2_transaction_hash: l2_transaction_hash, diff --git a/apps/indexer/lib/indexer/helper.ex b/apps/indexer/lib/indexer/helper.ex index 540606fe222f..21b69831f26e 100644 --- a/apps/indexer/lib/indexer/helper.ex +++ b/apps/indexer/lib/indexer/helper.ex @@ -32,4 +32,13 @@ defmodule Indexer.Helper do def is_address_correct?(_address) do false end + + @spec log_topic_to_string(any()) :: binary() | nil + def log_topic_to_string(topic) do + if is_binary(topic) or is_nil(topic) do + topic + else + Hash.to_string(topic) + end + end end diff --git a/apps/indexer/lib/indexer/temporary/uncataloged_token_transfers.ex b/apps/indexer/lib/indexer/temporary/uncataloged_token_transfers.ex index 17f676f136d5..d048a3331172 100644 --- a/apps/indexer/lib/indexer/temporary/uncataloged_token_transfers.ex +++ b/apps/indexer/lib/indexer/temporary/uncataloged_token_transfers.ex @@ -12,7 +12,7 @@ defmodule Indexer.Temporary.UncatalogedTokenTransfers do require Logger - alias Explorer.Chain + alias Explorer.Chain.TokenTransfer alias Indexer.Block.Catchup.Fetcher alias Indexer.Temporary.UncatalogedTokenTransfers @@ -52,7 +52,7 @@ defmodule Indexer.Temporary.UncatalogedTokenTransfers do end def handle_info(:scan, state) do - {:ok, block_numbers} = Chain.uncataloged_token_transfer_block_numbers() + {:ok, block_numbers} = TokenTransfer.uncataloged_token_transfer_block_numbers() case block_numbers do [] -> diff --git a/apps/indexer/lib/indexer/transform/transaction_actions.ex b/apps/indexer/lib/indexer/transform/transaction_actions.ex index 1620fd724dd2..618c311f2a95 100644 --- a/apps/indexer/lib/indexer/transform/transaction_actions.ex +++ b/apps/indexer/lib/indexer/transform/transaction_actions.ex @@ -285,8 +285,15 @@ defmodule Indexer.Transform.TransactionActions do [debt_amount, collateral_amount, _liquidator, _receive_a_token] = decode_data(log.data, [{:uint, 256}, {:uint, 256}, :address, :bool]) - debt_address = truncate_address_hash(log.third_topic) - collateral_address = truncate_address_hash(log.second_topic) + debt_address = + log.third_topic + |> Helper.log_topic_to_string() + |> truncate_address_hash() + + collateral_address = + log.second_topic + |> Helper.log_topic_to_string() + |> truncate_address_hash() case get_token_data([debt_address, collateral_address]) do false -> @@ -318,7 +325,10 @@ defmodule Indexer.Transform.TransactionActions do defp aave_handle_event(type, amount, log, address_topic, chain_id) when type in ["borrow", "supply", "withdraw", "repay", "flash_loan"] do - address = truncate_address_hash(address_topic) + address = + address_topic + |> Helper.log_topic_to_string() + |> truncate_address_hash() case get_token_data([address]) do false -> @@ -345,7 +355,10 @@ defmodule Indexer.Transform.TransactionActions do end defp aave_handle_event(type, log, address_topic, chain_id) when type in ["enable_collateral", "disable_collateral"] do - address = truncate_address_hash(address_topic) + address = + address_topic + |> Helper.log_topic_to_string() + |> truncate_address_hash() case get_token_data([address]) do false -> @@ -448,12 +461,23 @@ defmodule Indexer.Transform.TransactionActions do |> Enum.reduce(%{}, fn log, acc -> if sanitize_first_topic(log.first_topic) == @uniswap_v3_transfer_nft_event do # This is Transfer event for NFT - from = truncate_address_hash(log.second_topic) + from = + log.second_topic + |> Helper.log_topic_to_string() + |> truncate_address_hash() # credo:disable-for-next-line if from == burn_address_hash_string() do - to = truncate_address_hash(log.third_topic) - [token_id] = decode_data(log.fourth_topic, [{:uint, 256}]) + to = + log.third_topic + |> Helper.log_topic_to_string() + |> truncate_address_hash() + + [token_id] = + log.fourth_topic + |> Helper.log_topic_to_string() + |> decode_data([{:uint, 256}]) + mint_nft_ids = Map.put_new(acc, to, %{ids: [], log_index: log.index}) Map.put(mint_nft_ids, to, %{ @@ -970,7 +994,7 @@ defmodule Indexer.Transform.TransactionActions do end defp sanitize_first_topic(first_topic) do - if is_nil(first_topic), do: "", else: String.downcase(first_topic) + if is_nil(first_topic), do: "", else: String.downcase(Helper.log_topic_to_string(first_topic)) end defp truncate_address_hash(nil), do: burn_address_hash_string() From 83978978f9d565e49dcfc4f6a6cc5f39d153b397 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 21 Dec 2023 18:56:28 +0300 Subject: [PATCH 239/607] Drop logs type column and related index (#9007) * Drop logs type index * Remove log type column * Update CHANGE LOG entry --- CHANGELOG.md | 1 + .../channels/websocket_v2_test.exs | 9 ++-- .../lib/ethereum_jsonrpc/log.ex | 43 ++++++------------- .../lib/ethereum_jsonrpc/receipts.ex | 15 +++---- .../test/ethereum_jsonrpc/receipts_test.exs | 6 +-- .../lib/explorer/chain/import/runner/logs.ex | 6 +-- apps/explorer/lib/explorer/chain/log.ex | 15 ++----- apps/explorer/lib/explorer/eth_rpc.ex | 3 +- apps/explorer/lib/explorer/etherscan/logs.ex | 3 +- ...1215115638_drop_unused_logs_type_index.exs | 11 +++++ .../test/explorer/chain/import_test.exs | 4 +- apps/explorer/test/explorer/chain_test.exs | 4 +- apps/explorer/test/support/factory.ex | 3 +- .../lib/indexer/transform/mint_transfers.ex | 3 +- .../indexer/block/fetcher/receipts_test.exs | 6 +-- .../test/indexer/block/fetcher_test.exs | 3 +- .../indexer/transform/mint_transfers_test.exs | 6 +-- .../transform/token_transfers_test.exs | 39 ++++++----------- 18 files changed, 65 insertions(+), 115 deletions(-) create mode 100644 apps/explorer/priv/repo/migrations/20231215115638_drop_unused_logs_type_index.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index f852bfdf7744..48ef1d03c82c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Chore - [#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 - [#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 diff --git a/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs b/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs index 2e06c53dbdc6..6d7aad632f90 100644 --- a/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs +++ b/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs @@ -47,8 +47,7 @@ defmodule BlockScoutWeb.WebsocketV2Test do third_topic: third_topic, fourth_topic: nil, index: 0, - transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - type: "mined" + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" }, %{ block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", @@ -59,8 +58,7 @@ defmodule BlockScoutWeb.WebsocketV2Test do third_topic: third_topic, fourth_topic: nil, index: 1, - transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - type: "mined" + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" }, %{ block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", @@ -71,8 +69,7 @@ defmodule BlockScoutWeb.WebsocketV2Test do third_topic: third_topic, fourth_topic: nil, index: 2, - transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - type: "mined" + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" } ], timeout: 5 diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex index ecda58364425..f3c6a88662c5 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex @@ -33,8 +33,7 @@ defmodule EthereumJSONRPC.Log do ...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", ...> "transactionIndex" => 0, - ...> "transactionLogIndex" => 0, - ...> "type" => "mined" + ...> "transactionLogIndex" => 0 ...> } ...> ) %{ @@ -47,12 +46,9 @@ defmodule EthereumJSONRPC.Log do index: 0, second_topic: nil, third_topic: nil, - transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - type: "mined" + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" } - Geth does not supply a `"type"` - iex> EthereumJSONRPC.Log.elixir_to_params( ...> %{ ...> "address" => "0xda8b3276cde6d768a44b9dac659faa339a41ac55", @@ -82,17 +78,15 @@ defmodule EthereumJSONRPC.Log do } """ - def elixir_to_params( - %{ - "address" => address_hash, - "blockNumber" => block_number, - "blockHash" => block_hash, - "data" => data, - "logIndex" => index, - "topics" => topics, - "transactionHash" => transaction_hash - } = elixir - ) do + def elixir_to_params(%{ + "address" => address_hash, + "blockNumber" => block_number, + "blockHash" => block_hash, + "data" => data, + "logIndex" => index, + "topics" => topics, + "transactionHash" => transaction_hash + }) do %{ address_hash: address_hash, block_number: block_number, @@ -102,7 +96,6 @@ defmodule EthereumJSONRPC.Log do transaction_hash: transaction_hash } |> put_topics(topics) - |> put_type(elixir) end @doc """ @@ -118,8 +111,7 @@ defmodule EthereumJSONRPC.Log do ...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", ...> "transactionIndex" => "0x0", - ...> "transactionLogIndex" => "0x0", - ...> "type" => "mined" + ...> "transactionLogIndex" => "0x0" ...> } ...> ) %{ @@ -131,8 +123,7 @@ defmodule EthereumJSONRPC.Log do "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", "transactionIndex" => 0, - "transactionLogIndex" => 0, - "type" => "mined" + "transactionLogIndex" => 0 } Geth includes a `"removed"` key @@ -172,7 +163,7 @@ defmodule EthereumJSONRPC.Log do end defp entry_to_elixir({key, _} = entry) - when key in ~w(address blockHash data removed topics transactionHash type timestamp), + when key in ~w(address blockHash data removed topics transactionHash timestamp), do: entry defp entry_to_elixir({key, quantity}) when key in ~w(blockNumber logIndex transactionIndex transactionLogIndex) do @@ -190,10 +181,4 @@ defmodule EthereumJSONRPC.Log do |> Map.put(:third_topic, Enum.at(topics, 2)) |> Map.put(:fourth_topic, Enum.at(topics, 3)) end - - defp put_type(params, %{"type" => type}) do - Map.put(params, :type, type) - end - - defp put_type(params, _), do: params end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex index 8e0cffa7f2b1..37cc0a963c38 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex @@ -32,8 +32,7 @@ defmodule EthereumJSONRPC.Receipts do ...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", ...> "transactionIndex" => 0, - ...> "transactionLogIndex" => 0, - ...> "type" => "mined" + ...> "transactionLogIndex" => 0 ...> } ...> ], ...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -53,8 +52,7 @@ defmodule EthereumJSONRPC.Receipts do "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", "transactionIndex" => 0, - "transactionLogIndex" => 0, - "type" => "mined" + "transactionLogIndex" => 0 } ] @@ -84,8 +82,7 @@ defmodule EthereumJSONRPC.Receipts do ...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", ...> "transactionIndex" => 0, - ...> "transactionLogIndex" => 0, - ...> "type" => "mined" + ...> "transactionLogIndex" => 0 ...> } ...> ], ...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -165,8 +162,7 @@ defmodule EthereumJSONRPC.Receipts do ...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", ...> "transactionIndex" => "0x0", - ...> "transactionLogIndex" => "0x0", - ...> "type" => "mined" + ...> "transactionLogIndex" => "0x0" ...> } ...> ], ...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -193,8 +189,7 @@ defmodule EthereumJSONRPC.Receipts do "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", "transactionIndex" => 0, - "transactionLogIndex" => 0, - "type" => "mined" + "transactionLogIndex" => 0 } ], "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs index f554c31ce572..945f3f78bc94 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs @@ -23,7 +23,6 @@ defmodule EthereumJSONRPC.ReceiptsTest do index: index, first_topic: first_topic, status: status, - type: type, transaction_hash: transaction_hash, transaction_index: transaction_index } = @@ -41,7 +40,6 @@ defmodule EthereumJSONRPC.ReceiptsTest do first_topic: "0xf6db2bace4ac8277384553ad9603d045220a91fb2448ab6130d7a6f044f9a8cf", gas_used: 106_025, status: nil, - type: nil, transaction_hash: "0xd3efddbbeb6ad8d8bb3f6b8c8fb6165567e9dd868013146bdbeb60953c82822a", transaction_index: 17 } @@ -58,7 +56,6 @@ defmodule EthereumJSONRPC.ReceiptsTest do index: 0, first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22", status: :ok, - type: "mined", transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", transaction_index: 0 } @@ -89,8 +86,7 @@ defmodule EthereumJSONRPC.ReceiptsTest do "data" => data, "logIndex" => integer_to_quantity(index), "topics" => [first_topic], - "transactionHash" => transaction_hash, - "type" => type + "transactionHash" => transaction_hash } ], "status" => native_status, diff --git a/apps/explorer/lib/explorer/chain/import/runner/logs.ex b/apps/explorer/lib/explorer/chain/import/runner/logs.ex index c90adaa04a41..7c0e591a0f92 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/logs.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/logs.ex @@ -92,7 +92,6 @@ defmodule Explorer.Chain.Import.Runner.Logs do third_topic: fragment("EXCLUDED.third_topic"), fourth_topic: fragment("EXCLUDED.fourth_topic"), # Don't update `index` as it is part of the composite primary key and used for the conflict target - type: fragment("EXCLUDED.type"), # Don't update `transaction_hash` as it is part of the composite primary key and used for the conflict target inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", log.inserted_at), updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", log.updated_at) @@ -100,14 +99,13 @@ defmodule Explorer.Chain.Import.Runner.Logs do ], where: fragment( - "(EXCLUDED.address_hash, EXCLUDED.data, EXCLUDED.first_topic, EXCLUDED.second_topic, EXCLUDED.third_topic, EXCLUDED.fourth_topic, EXCLUDED.type) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?)", + "(EXCLUDED.address_hash, EXCLUDED.data, EXCLUDED.first_topic, EXCLUDED.second_topic, EXCLUDED.third_topic, EXCLUDED.fourth_topic) IS DISTINCT FROM (?, ?, ?, ?, ?, ?)", log.address_hash, log.data, log.first_topic, log.second_topic, log.third_topic, - log.fourth_topic, - log.type + log.fourth_topic ) ) end diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index e1c87a8a1e60..be27eead7641 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -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(), @@ -41,8 +40,7 @@ defmodule Explorer.Chain.Log do 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 @@ -53,7 +51,6 @@ defmodule Explorer.Chain.Log do 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 diff --git a/apps/explorer/lib/explorer/eth_rpc.ex b/apps/explorer/lib/explorer/eth_rpc.ex index 0e5b493a9aef..d7260cf3987c 100644 --- a/apps/explorer/lib/explorer/eth_rpc.ex +++ b/apps/explorer/lib/explorer/eth_rpc.ex @@ -180,8 +180,7 @@ defmodule Explorer.EthRPC do "topics" => topics, "transactionHash" => to_string(log.transaction_hash), "transactionIndex" => log.transaction_index, - "transactionLogIndex" => log.index, - "type" => "mined" + "transactionLogIndex" => log.index } end diff --git a/apps/explorer/lib/explorer/etherscan/logs.ex b/apps/explorer/lib/explorer/etherscan/logs.ex index e83587869d6d..a0c432ce4432 100644 --- a/apps/explorer/lib/explorer/etherscan/logs.ex +++ b/apps/explorer/lib/explorer/etherscan/logs.ex @@ -34,8 +34,7 @@ defmodule Explorer.Etherscan.Logs do :fourth_topic, :index, :address_hash, - :transaction_hash, - :type + :transaction_hash ] @default_paging_options %{block_number: nil, log_index: nil} diff --git a/apps/explorer/priv/repo/migrations/20231215115638_drop_unused_logs_type_index.exs b/apps/explorer/priv/repo/migrations/20231215115638_drop_unused_logs_type_index.exs new file mode 100644 index 000000000000..fc7df4ddce8c --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231215115638_drop_unused_logs_type_index.exs @@ -0,0 +1,11 @@ +defmodule Explorer.Repo.Migrations.DropUnusedLogsTypeIndex do + use Ecto.Migration + + def change do + drop(index(:logs, [:type], name: :logs_type_index)) + + alter table(:logs) do + remove(:type) + end + end +end diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs index a8d62bfcad6c..0d2fbfa02de3 100644 --- a/apps/explorer/test/explorer/chain/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -103,8 +103,7 @@ defmodule Explorer.Chain.ImportTest do third_topic: third_topic, fourth_topic: nil, index: 0, - transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - type: "mined" + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" } ], timeout: 5 @@ -296,7 +295,6 @@ defmodule Explorer.Chain.ImportTest do <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> }, - type: "mined", inserted_at: %{}, updated_at: %{} } diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 083d50d0f6dc..fb529bb074c6 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -1311,8 +1311,7 @@ defmodule Explorer.ChainTest do third_topic: third_topic, fourth_topic: nil, index: 0, - transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - type: "mined" + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" } ] }, @@ -1490,7 +1489,6 @@ defmodule Explorer.ChainTest do <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> }, - type: "mined", inserted_at: %{}, updated_at: %{} } diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 4c218afb3809..5fdd286ee056 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -679,8 +679,7 @@ defmodule Explorer.Factory do index: sequence("log_index", & &1), second_topic: nil, third_topic: nil, - transaction: build(:transaction), - type: sequence("0x") + transaction: build(:transaction) } end diff --git a/apps/indexer/lib/indexer/transform/mint_transfers.ex b/apps/indexer/lib/indexer/transform/mint_transfers.ex index e57a9841a7fb..c53dde7a5387 100644 --- a/apps/indexer/lib/indexer/transform/mint_transfers.ex +++ b/apps/indexer/lib/indexer/transform/mint_transfers.ex @@ -24,8 +24,7 @@ defmodule Indexer.Transform.MintTransfers do ...> index: 1, ...> second_topic: "0x0000000000000000000000009a4a90e2732f3fa4087b0bb4bf85c76d14833df1", ...> third_topic: "0x0000000000000000000000007301cfa0e1756b71869e93d4e4dca5c7d0eb0aa6", - ...> transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee", - ...> type: "mined" + ...> transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee" ...> } ...> ]) %{ diff --git a/apps/indexer/test/indexer/block/fetcher/receipts_test.exs b/apps/indexer/test/indexer/block/fetcher/receipts_test.exs index c70d10761a01..a7c45106551e 100644 --- a/apps/indexer/test/indexer/block/fetcher/receipts_test.exs +++ b/apps/indexer/test/indexer/block/fetcher/receipts_test.exs @@ -51,8 +51,7 @@ defmodule Indexer.Block.Fetcher.ReceiptsTest do "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], "transactionHash" => "0x43bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", "transactionIndex" => "0x0", - "transactionLogIndex" => "0x0", - "type" => "mined" + "transactionLogIndex" => "0x0" } ], "logsBloom" => @@ -82,8 +81,7 @@ defmodule Indexer.Block.Fetcher.ReceiptsTest do "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", "transactionIndex" => "0x0", - "transactionLogIndex" => "0x0", - "type" => "mined" + "transactionLogIndex" => "0x0" } ], "logsBloom" => diff --git a/apps/indexer/test/indexer/block/fetcher_test.exs b/apps/indexer/test/indexer/block/fetcher_test.exs index 24be0dfa8803..0498cfd37ecb 100644 --- a/apps/indexer/test/indexer/block/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/fetcher_test.exs @@ -375,8 +375,7 @@ defmodule Indexer.Block.FetcherTest do "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", "transactionIndex" => "0x0", - "transactionLogIndex" => "0x0", - "type" => "mined" + "transactionLogIndex" => "0x0" } ], "logsBloom" => diff --git a/apps/indexer/test/indexer/transform/mint_transfers_test.exs b/apps/indexer/test/indexer/transform/mint_transfers_test.exs index 04499a7af454..4670e983a272 100644 --- a/apps/indexer/test/indexer/transform/mint_transfers_test.exs +++ b/apps/indexer/test/indexer/transform/mint_transfers_test.exs @@ -17,8 +17,7 @@ defmodule Indexer.Transform.MintTransfersTest do index: 1, second_topic: "0x0000000000000000000000009a4a90e2732f3fa4087b0bb4bf85c76d14833df1", third_topic: "0x0000000000000000000000007301cfa0e1756b71869e93d4e4dca5c7d0eb0aa6", - transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee", - type: "mined" + transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee" } ] @@ -47,8 +46,7 @@ defmodule Indexer.Transform.MintTransfersTest do index: 1, second_topic: "0x0000000000000000000000009a4a90e2732f3fa4087b0bb4bf85c76d14833df1", third_topic: "0x0000000000000000000000007301cfa0e1756b71869e93d4e4dca5c7d0eb0aa6", - transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee", - type: "mined" + transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee" } ] diff --git a/apps/indexer/test/indexer/transform/token_transfers_test.exs b/apps/indexer/test/indexer/transform/token_transfers_test.exs index 8833474acccc..0c670035cc35 100644 --- a/apps/indexer/test/indexer/transform/token_transfers_test.exs +++ b/apps/indexer/test/indexer/transform/token_transfers_test.exs @@ -19,8 +19,7 @@ defmodule Indexer.Transform.TokenTransfersTest do index: 8, second_topic: "0x000000000000000000000000556813d9cc20acfe8388af029a679d34a63388db", third_topic: "0x00000000000000000000000092148dd870fa1b7c4700f2bd7f44238821c26f73", - transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5", - type: "mined" + transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5" }, %{ address_hash: "0x6ea5ec9cb832e60b6b1654f5826e9be638f276a5", @@ -32,8 +31,7 @@ defmodule Indexer.Transform.TokenTransfersTest do index: 0, second_topic: "0x00000000000000000000000063b0595bb7a0b7edd0549c9557a0c8aee6da667b", third_topic: "0x000000000000000000000000f3089e15d0c23c181d7f98b0878b560bfe193a1d", - transaction_hash: "0x8425a9b81a9bd1c64861110c1a453b84719cb0361d6fa0db68abf7611b9a890e", - type: "mined" + transaction_hash: "0x8425a9b81a9bd1c64861110c1a453b84719cb0361d6fa0db68abf7611b9a890e" }, %{ address_hash: "0x91932e8c6776fb2b04abb71874a7988747728bb2", @@ -45,8 +43,7 @@ defmodule Indexer.Transform.TokenTransfersTest do index: 1, second_topic: "0x0000000000000000000000009851ba177554eb07271ac230a137551e6dd0aa84", third_topic: "0x000000000000000000000000dccb72afee70e60b0c1226288fe86c01b953e8ac", - transaction_hash: "0x4011d9a930a3da620321589a54dc0ca3b88216b4886c7a7c3aaad1fb17702d35", - type: "mined" + transaction_hash: "0x4011d9a930a3da620321589a54dc0ca3b88216b4886c7a7c3aaad1fb17702d35" }, %{ address_hash: "0x0BE9e53fd7EDaC9F859882AfdDa116645287C629", @@ -58,8 +55,7 @@ defmodule Indexer.Transform.TokenTransfersTest do third_topic: nil, fourth_topic: nil, index: 1, - transaction_hash: "0x185889bc91372106ecf114a4e23f4ee615e131ae3e698078bd5d2ed7e3f55a49", - type: "mined" + transaction_hash: "0x185889bc91372106ecf114a4e23f4ee615e131ae3e698078bd5d2ed7e3f55a49" }, %{ address_hash: "0x0BE9e53fd7EDaC9F859882AfdDa116645287C629", @@ -71,8 +67,7 @@ defmodule Indexer.Transform.TokenTransfersTest do third_topic: nil, fourth_topic: nil, index: 1, - transaction_hash: "0x07510dbfddbac9064f7d607c2d9a14aa26fa19cdfcd578c0b585ff2395df543f", - type: "mined" + transaction_hash: "0x07510dbfddbac9064f7d607c2d9a14aa26fa19cdfcd578c0b585ff2395df543f" } ] @@ -157,8 +152,7 @@ defmodule Indexer.Transform.TokenTransfersTest do second_topic: nil, third_topic: nil, transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8", - block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca", - type: "mined" + block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca" } expected = %{ @@ -198,8 +192,7 @@ defmodule Indexer.Transform.TokenTransfersTest do fourth_topic: "0x0000000000000000000000009c978f4cfa1fe13406bcc05baf26a35716f881dd", index: 2, transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8", - block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca", - type: "mined" + block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca" } assert TokenTransfers.parse([log]) == %{ @@ -240,8 +233,7 @@ defmodule Indexer.Transform.TokenTransfersTest do fourth_topic: "0x0000000000000000000000006c943470780461b00783ad530a53913bd2c104d3", index: 2, transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8", - block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca", - type: "mined" + block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca" } assert TokenTransfers.parse([log]) == %{ @@ -275,8 +267,7 @@ defmodule Indexer.Transform.TokenTransfersTest do fourth_topic: "0x0000000000000000000000000000000000000000", index: 6, transaction_hash: "0xa6ad6588edb4abd8ca45f30d2f026ba20b68a3002a5870dbd30cc3752568483b", - block_hash: "0x61b720e40f8c521edd77a52cabce556c18b18b198f78e361f310003386ff1f02", - type: "mined" + block_hash: "0x61b720e40f8c521edd77a52cabce556c18b18b198f78e361f310003386ff1f02" } assert TokenTransfers.parse([log]) == %{ @@ -296,8 +287,7 @@ defmodule Indexer.Transform.TokenTransfersTest do index: 2, second_topic: nil, third_topic: nil, - transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8", - type: "mined" + transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8" } error = capture_log(fn -> %{tokens: [], token_transfers: []} = TokenTransfers.parse([log]) end) @@ -319,8 +309,7 @@ defmodule Indexer.Transform.TokenTransfersTest do index: 8, second_topic: "0x000000000000000000000000556813d9cc20acfe8388af029a679d34a63388db", third_topic: "0x00000000000000000000000092148dd870fa1b7c4700f2bd7f44238821c26f73", - transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5", - type: "mined" + transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5" } assert %{ @@ -343,8 +332,7 @@ defmodule Indexer.Transform.TokenTransfersTest do index: 8, second_topic: "0x000000000000000000000000556813d9cc20acfe8388af029a679d34a63388db", third_topic: "0x00000000000000000000000092148dd870fa1b7c4700f2bd7f44238821c26f73", - transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5", - type: "mined" + transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5" }, %{ address_hash: contract_address_hash, @@ -357,8 +345,7 @@ defmodule Indexer.Transform.TokenTransfersTest do fourth_topic: "0x0000000000000000000000009c978f4cfa1fe13406bcc05baf26a35716f881dd", index: 2, transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5", - block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca", - type: "mined" + block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca" } ] From a4799d35aa532461ebb8f05bfb81ae5a5f811b3e Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 21 Dec 2023 19:07:03 +0300 Subject: [PATCH 240/607] Drop unused token_id column from token_transfers table and indexes based on this column (#9005) * Drop unused token_id column from token_transfers table and indexes on this column * Remove token ids migration * Refactor DB migration * Update CHANGE LOG entry --- CHANGELOG.md | 1 + .../models/transaction_state_helper.ex | 2 +- .../lib/block_scout_web/schema/types.ex | 1 - .../tokens/inventory_controller_test.exs | 2 +- .../schema/query/token_transfers_test.exs | 3 - .../views/tokens/helper_test.exs | 2 +- apps/explorer/config/config.exs | 2 - apps/explorer/config/runtime/test.exs | 2 - apps/explorer/lib/explorer/application.ex | 3 +- .../lib/explorer/chain/token_transfer.ex | 5 +- .../chain/transaction/state_change.ex | 2 +- .../lowest_block_number_updater.ex | 65 -------------- .../supervisor.ex | 70 ---------------- .../worker.ex | 84 ------------------- ...ken_transfer_token_id_migrator_progress.ex | 67 --------------- ...5_drop_token_transfers_token_id_column.exs | 12 +++ .../lowest_block_number_updater_test.exs | 37 -------- .../worker_test.exs | 31 ------- 18 files changed, 19 insertions(+), 372 deletions(-) delete mode 100644 apps/explorer/lib/explorer/token_transfer_token_id_migration/lowest_block_number_updater.ex delete mode 100644 apps/explorer/lib/explorer/token_transfer_token_id_migration/supervisor.ex delete mode 100644 apps/explorer/lib/explorer/token_transfer_token_id_migration/worker.ex delete mode 100644 apps/explorer/lib/explorer/utility/token_transfer_token_id_migrator_progress.ex create mode 100644 apps/explorer/priv/repo/migrations/20231215094615_drop_token_transfers_token_id_column.exs delete mode 100644 apps/explorer/test/explorer/token_transfer_token_id_migration/lowest_block_number_updater_test.exs delete mode 100644 apps/explorer/test/explorer/token_transfer_token_id_migration/worker_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 48ef1d03c82c..7d3662a542e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - [#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 diff --git a/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex b/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex index 3971a5463a64..41b5bd1cfe7b 100644 --- a/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex @@ -112,7 +112,7 @@ defmodule BlockScoutWeb.Models.TransactionStateHelper do token_ids = if token.type == "ERC-1155" do - token_transfer.token_ids || [token_transfer.token_id] + token_transfer.token_ids else [nil] end diff --git a/apps/block_scout_web/lib/block_scout_web/schema/types.ex b/apps/block_scout_web/lib/block_scout_web/schema/types.ex index 99f47a29163b..d81f6eca5137 100644 --- a/apps/block_scout_web/lib/block_scout_web/schema/types.ex +++ b/apps/block_scout_web/lib/block_scout_web/schema/types.ex @@ -130,7 +130,6 @@ defmodule BlockScoutWeb.Schema.Types do field(:amounts, list_of(:decimal)) field(:block_number, :integer) field(:log_index, :integer) - field(:token_id, :decimal) field(:token_ids, list_of(:decimal)) field(:from_address_hash, :address_hash) field(:to_address_hash, :address_hash) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs index 799b54da797c..e15f85569214 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs @@ -126,7 +126,7 @@ defmodule BlockScoutWeb.Tokens.InventoryControllerTest do transaction: transaction, token_contract_address: token.contract_address, token: token, - token_id: 1000 + token_ids: [1000] ) conn = get(conn, token_inventory_path(conn, :index, token.contract_address_hash), %{type: "JSON"}) diff --git a/apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs b/apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs index ea20aa4f4ede..d10a3fdcc639 100644 --- a/apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs +++ b/apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs @@ -16,7 +16,6 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do amounts block_number log_index - token_id token_ids from_address_hash to_address_hash @@ -45,7 +44,6 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do "amounts" => Enum.map(token_transfer.amounts, &to_string/1), "block_number" => token_transfer.block_number, "log_index" => token_transfer.log_index, - "token_id" => token_transfer.token_id, "token_ids" => Enum.map(token_transfer.token_ids, &to_string/1), "from_address_hash" => to_string(token_transfer.from_address_hash), "to_address_hash" => to_string(token_transfer.to_address_hash), @@ -70,7 +68,6 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do amount block_number log_index - token_id from_address_hash to_address_hash token_contract_address_hash diff --git a/apps/block_scout_web/test/block_scout_web/views/tokens/helper_test.exs b/apps/block_scout_web/test/block_scout_web/views/tokens/helper_test.exs index e59a85fb76c4..a8815379cedb 100644 --- a/apps/block_scout_web/test/block_scout_web/views/tokens/helper_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/tokens/helper_test.exs @@ -27,7 +27,7 @@ defmodule BlockScoutWeb.Tokens.HelperTest do test "returns a string with the token_id with ERC-721 token" do token = build(:token, type: "ERC-721", decimals: nil) - token_transfer = build(:token_transfer, token: token, amount: nil, token_id: 1) + token_transfer = build(:token_transfer, token: token, amount: nil, token_ids: [1]) assert Helper.token_transfer_amount(token_transfer) == {:ok, :erc721_instance} end diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 1dac8ff0963b..cc22af6be938 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -109,8 +109,6 @@ 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 diff --git a/apps/explorer/config/runtime/test.exs b/apps/explorer/config/runtime/test.exs index 367f4793f27e..d496caa48a78 100644 --- a/apps/explorer/config/runtime/test.exs +++ b/apps/explorer/config/runtime/test.exs @@ -33,8 +33,6 @@ 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 diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index dfed4625c879..1749cb69fd59 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -5,7 +5,7 @@ defmodule Explorer.Application do use Application - alias Explorer.{Admin, TokenTransferTokenIdMigration} + alias Explorer.Admin alias Explorer.Chain.Cache.{ Accounts, @@ -122,7 +122,6 @@ 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), diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index c53414e209a2..38dad6123521 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -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 diff --git a/apps/explorer/lib/explorer/chain/transaction/state_change.ex b/apps/explorer/lib/explorer/chain/transaction/state_change.ex index 3a399aa019f1..c0b70f23c493 100644 --- a/apps/explorer/lib/explorer/chain/transaction/state_change.ex +++ b/apps/explorer/lib/explorer/chain/transaction/state_change.ex @@ -120,7 +120,7 @@ defmodule Explorer.Chain.Transaction.StateChange do end defp do_update_balance(old_val, type, transfer, _) do - token_ids = if transfer.token.type == "ERC-1155", do: transfer.token_ids || [transfer.token_id], else: [nil] + token_ids = if transfer.token.type == "ERC-1155", do: transfer.token_ids, else: [nil] transfer_amounts = transfer.amounts || [transfer.amount || 1] sub_or_add = diff --git a/apps/explorer/lib/explorer/token_transfer_token_id_migration/lowest_block_number_updater.ex b/apps/explorer/lib/explorer/token_transfer_token_id_migration/lowest_block_number_updater.ex deleted file mode 100644 index 5794e524ccfc..000000000000 --- a/apps/explorer/lib/explorer/token_transfer_token_id_migration/lowest_block_number_updater.ex +++ /dev/null @@ -1,65 +0,0 @@ -defmodule Explorer.TokenTransferTokenIdMigration.LowestBlockNumberUpdater do - @moduledoc """ - Collects processed block numbers from token id migration workers - and updates last_processed_block_number according to them. - Full algorithm is in the 'Indexer.Fetcher.TokenTransferTokenIdMigration.Supervisor' module doc. - """ - use GenServer - - alias Explorer.Utility.TokenTransferTokenIdMigratorProgress - - def start_link(_) do - GenServer.start_link(__MODULE__, :ok, name: __MODULE__) - end - - @impl true - def init(_) do - last_processed_block_number = TokenTransferTokenIdMigratorProgress.get_last_processed_block_number() - - {:ok, %{last_processed_block_number: last_processed_block_number, processed_ranges: []}} - end - - def add_range(from, to) do - GenServer.cast(__MODULE__, {:add_range, from..to}) - end - - @impl true - def handle_cast({:add_range, range}, %{processed_ranges: processed_ranges} = state) do - ranges = - [range | processed_ranges] - |> Enum.sort_by(& &1.last, &>=/2) - |> normalize_ranges() - - {new_last_number, new_ranges} = maybe_update_last_processed_number(state.last_processed_block_number, ranges) - - {:noreply, %{last_processed_block_number: new_last_number, processed_ranges: new_ranges}} - end - - defp normalize_ranges(ranges) do - %{prev_range: prev, result: result} = - Enum.reduce(ranges, %{prev_range: nil, result: []}, fn range, %{prev_range: prev_range, result: result} -> - case {prev_range, range} do - {nil, _} -> - %{prev_range: range, result: result} - - {%{last: l1} = r1, %{first: f2} = r2} when l1 - 1 > f2 -> - %{prev_range: r2, result: [r1 | result]} - - {%{first: f1}, %{last: l2}} -> - %{prev_range: f1..l2, result: result} - end - end) - - Enum.reverse([prev | result]) - end - - # since ranges are normalized, we need to check only the first range to determine the new last_processed_number - defp maybe_update_last_processed_number(current_last, [from..to | rest] = ranges) when current_last - 1 <= from do - case TokenTransferTokenIdMigratorProgress.update_last_processed_block_number(to) do - {:ok, _} -> {to, rest} - _ -> {current_last, ranges} - end - end - - defp maybe_update_last_processed_number(current_last, ranges), do: {current_last, ranges} -end diff --git a/apps/explorer/lib/explorer/token_transfer_token_id_migration/supervisor.ex b/apps/explorer/lib/explorer/token_transfer_token_id_migration/supervisor.ex deleted file mode 100644 index 2121158fee35..000000000000 --- a/apps/explorer/lib/explorer/token_transfer_token_id_migration/supervisor.ex +++ /dev/null @@ -1,70 +0,0 @@ -defmodule Explorer.TokenTransferTokenIdMigration.Supervisor do - @moduledoc """ - Supervises parts of token id migration process. - - Migration process algorithm: - - Defining the bounds of migration (by the first and the last block number of TokenTransfer). - Supervisor starts the workers in amount equal to 'TOKEN_ID_MIGRATION_CONCURRENCY' env value (defaults to 1) - and the 'LowestBlockNumberUpdater'. - - Each worker goes through the token transfers by batches ('TOKEN_ID_MIGRATION_BATCH_SIZE', defaults to 500) - and updates the token_ids to be equal of [token_id] for transfers that has any token_id. - Worker goes from the newest blocks to latest. - After worker is done with current batch, it sends the information about processed batch to 'LowestBlockNumberUpdater' - and takes the next by defining its bounds based on amount of all workers. - - For example, if batch size is 10, we have 5 workers and 100 items to be processed, - the distribution will be like this: - 1 worker - 99..90, 49..40 - 2 worker - 89..80, 39..30 - 3 worker - 79..70, 29..20 - 4 worker - 69..60, 19..10 - 5 worker - 59..50, 9..0 - - 'LowestBlockNumberUpdater' keeps the information about the last processed block number - (which is stored in the 'token_transfer_token_id_migrator_progress' db entity) - and block ranges that has already been processed by the workers but couldn't be committed - to last processed block number yet (because of the possible gap between the current last block - and upper bound of the last processed batch). Uncommitted block numbers are stored in normalize ranges. - When there is no gap between the last processed block number and the upper bound of the upper range, - 'LowestBlockNumberUpdater' updates the last processed block number in db and drops this range from its state. - - This supervisor won't start if the migration is completed - (last processed block number in db == 'TOKEN_ID_MIGRATION_FIRST_BLOCK' (defaults to 0)). - """ - use Supervisor - - alias Explorer.TokenTransferTokenIdMigration.{LowestBlockNumberUpdater, Worker} - alias Explorer.Utility.TokenTransferTokenIdMigratorProgress - - @default_first_block 0 - @default_workers_count 1 - - def start_link(_) do - Supervisor.start_link(__MODULE__, :ok, name: __MODULE__) - end - - @impl true - def init(_) do - first_block = Application.get_env(:explorer, :token_id_migration)[:first_block] || @default_first_block - last_block = TokenTransferTokenIdMigratorProgress.get_last_processed_block_number() - - if last_block > first_block do - workers_count = Application.get_env(:explorer, :token_id_migration)[:concurrency] || @default_workers_count - - workers = - Enum.map(1..workers_count, fn id -> - Supervisor.child_spec( - {Worker, idx: id, first_block: first_block, last_block: last_block, step: workers_count - 1}, - id: {Worker, id}, - restart: :transient - ) - end) - - Supervisor.init([LowestBlockNumberUpdater | workers], strategy: :one_for_one) - else - :ignore - end - end -end diff --git a/apps/explorer/lib/explorer/token_transfer_token_id_migration/worker.ex b/apps/explorer/lib/explorer/token_transfer_token_id_migration/worker.ex deleted file mode 100644 index f7916ed0582a..000000000000 --- a/apps/explorer/lib/explorer/token_transfer_token_id_migration/worker.ex +++ /dev/null @@ -1,84 +0,0 @@ -defmodule Explorer.TokenTransferTokenIdMigration.Worker do - @moduledoc """ - Performs the migration of TokenTransfer token_id to token_ids by batches. - Full algorithm is in the 'Explorer.TokenTransferTokenIdMigration.Supervisor' module doc. - """ - use GenServer - - import Ecto.Query - - alias Explorer.Chain.TokenTransfer - alias Explorer.Repo - alias Explorer.TokenTransferTokenIdMigration.LowestBlockNumberUpdater - - @default_batch_size 500 - @interval 10 - - def start_link(idx: idx, first_block: first, last_block: last, step: step) do - GenServer.start_link(__MODULE__, %{idx: idx, bottom_block: first, last_block: last, step: step}) - end - - @impl true - def init(%{idx: idx, bottom_block: bottom_block, last_block: last_block, step: step}) do - batch_size = Application.get_env(:explorer, :token_id_migration)[:batch_size] || @default_batch_size - range = calculate_new_range(last_block, bottom_block, batch_size, idx - 1) - - schedule_next_update() - - {:ok, %{batch_size: batch_size, bottom_block: bottom_block, step: step, current_range: range}} - end - - @impl true - def handle_info(:update, %{current_range: :out_of_bound} = state) do - {:stop, :normal, state} - end - - @impl true - def handle_info(:update, %{current_range: {lower_bound, upper_bound}} = state) do - case do_update(lower_bound, upper_bound) do - true -> - LowestBlockNumberUpdater.add_range(upper_bound, lower_bound) - new_range = calculate_new_range(lower_bound, state.bottom_block, state.batch_size, state.step) - schedule_next_update() - {:noreply, %{state | current_range: new_range}} - - _ -> - schedule_next_update() - {:noreply, state} - end - end - - defp calculate_new_range(last_processed_block, bottom_block, batch_size, step) do - upper_bound = last_processed_block - step * batch_size - 1 - lower_bound = max(upper_bound - batch_size + 1, bottom_block) - - if upper_bound >= bottom_block do - {lower_bound, upper_bound} - else - :out_of_bound - end - end - - defp do_update(lower_bound, upper_bound) do - token_transfers_batch_query = - from( - tt in TokenTransfer, - where: tt.block_number >= ^lower_bound, - where: tt.block_number <= ^upper_bound - ) - - token_transfers_batch_query - |> Repo.all() - |> Enum.filter(fn %{token_id: token_id} -> not is_nil(token_id) end) - |> Enum.map(fn token_transfer -> - token_transfer - |> TokenTransfer.changeset(%{token_ids: [token_transfer.token_id], token_id: nil}) - |> Repo.update() - end) - |> Enum.all?(&match?({:ok, _}, &1)) - end - - defp schedule_next_update do - Process.send_after(self(), :update, @interval) - end -end diff --git a/apps/explorer/lib/explorer/utility/token_transfer_token_id_migrator_progress.ex b/apps/explorer/lib/explorer/utility/token_transfer_token_id_migrator_progress.ex deleted file mode 100644 index 3a28181016e5..000000000000 --- a/apps/explorer/lib/explorer/utility/token_transfer_token_id_migrator_progress.ex +++ /dev/null @@ -1,67 +0,0 @@ -defmodule Explorer.Utility.TokenTransferTokenIdMigratorProgress do - @moduledoc """ - Module is responsible for keeping the current progress of TokenTransfer token_id migration. - Full algorithm is in the 'Indexer.Fetcher.TokenTransferTokenIdMigration.Supervisor' module doc. - """ - use Explorer.Schema - - require Logger - - alias Explorer.Chain.Cache.BlockNumber - alias Explorer.Repo - - schema "token_transfer_token_id_migrator_progress" do - field(:last_processed_block_number, :integer) - - timestamps() - end - - @doc false - def changeset(progress \\ %__MODULE__{}, params) do - cast(progress, params, [:last_processed_block_number]) - end - - def get_current_progress do - Repo.one( - from( - p in __MODULE__, - order_by: [desc: p.updated_at], - limit: 1 - ) - ) - end - - def get_last_processed_block_number do - case get_current_progress() do - nil -> - latest_processed_block_number = BlockNumber.get_max() + 1 - update_last_processed_block_number(latest_processed_block_number) - latest_processed_block_number - - %{last_processed_block_number: block_number} -> - block_number - end - end - - def update_last_processed_block_number(block_number, force \\ false) do - case get_current_progress() do - nil -> - %{last_processed_block_number: block_number} - |> changeset() - |> Repo.insert() - - progress -> - if not force and progress.last_processed_block_number < block_number do - Logger.error( - "TokenTransferTokenIdMigratorProgress new block_number is above the last one. Last: #{progress.last_processed_block_number}, new: #{block_number}" - ) - - {:error, :invalid_block_number} - else - progress - |> changeset(%{last_processed_block_number: block_number}) - |> Repo.update() - end - end - end -end diff --git a/apps/explorer/priv/repo/migrations/20231215094615_drop_token_transfers_token_id_column.exs b/apps/explorer/priv/repo/migrations/20231215094615_drop_token_transfers_token_id_column.exs new file mode 100644 index 000000000000..df8637071b31 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231215094615_drop_token_transfers_token_id_column.exs @@ -0,0 +1,12 @@ +defmodule Explorer.Repo.Migrations.DropTokenTransfersTokenIdColumn do + use Ecto.Migration + + def change do + drop(index(:token_transfers, [:token_id])) + drop(index(:token_transfers, [:token_contract_address_hash, "token_id DESC", "block_number DESC"])) + + alter table(:token_transfers) do + remove(:token_id) + end + end +end diff --git a/apps/explorer/test/explorer/token_transfer_token_id_migration/lowest_block_number_updater_test.exs b/apps/explorer/test/explorer/token_transfer_token_id_migration/lowest_block_number_updater_test.exs deleted file mode 100644 index bdd94920db2c..000000000000 --- a/apps/explorer/test/explorer/token_transfer_token_id_migration/lowest_block_number_updater_test.exs +++ /dev/null @@ -1,37 +0,0 @@ -defmodule Explorer.TokenTransferTokenIdMigration.LowestBlockNumberUpdaterTest do - use Explorer.DataCase, async: false - - alias Explorer.Repo - alias Explorer.TokenTransferTokenIdMigration.LowestBlockNumberUpdater - alias Explorer.Utility.TokenTransferTokenIdMigratorProgress - - describe "Add range and update last processed block number" do - test "add_range/2" do - TokenTransferTokenIdMigratorProgress.update_last_processed_block_number(2000, true) - LowestBlockNumberUpdater.start_link([]) - - LowestBlockNumberUpdater.add_range(1000, 500) - LowestBlockNumberUpdater.add_range(1500, 1001) - Process.sleep(10) - - assert %{last_processed_block_number: 2000, processed_ranges: [1500..500//-1]} = - :sys.get_state(LowestBlockNumberUpdater) - - assert %{last_processed_block_number: 2000} = Repo.one(TokenTransferTokenIdMigratorProgress) - - LowestBlockNumberUpdater.add_range(499, 300) - LowestBlockNumberUpdater.add_range(299, 0) - Process.sleep(10) - - assert %{last_processed_block_number: 2000, processed_ranges: [1500..0//-1]} = - :sys.get_state(LowestBlockNumberUpdater) - - assert %{last_processed_block_number: 2000} = Repo.one(TokenTransferTokenIdMigratorProgress) - - LowestBlockNumberUpdater.add_range(1999, 1501) - Process.sleep(10) - assert %{last_processed_block_number: 0, processed_ranges: []} = :sys.get_state(LowestBlockNumberUpdater) - assert %{last_processed_block_number: 0} = Repo.one(TokenTransferTokenIdMigratorProgress) - end - end -end diff --git a/apps/explorer/test/explorer/token_transfer_token_id_migration/worker_test.exs b/apps/explorer/test/explorer/token_transfer_token_id_migration/worker_test.exs deleted file mode 100644 index 8797e90130e6..000000000000 --- a/apps/explorer/test/explorer/token_transfer_token_id_migration/worker_test.exs +++ /dev/null @@ -1,31 +0,0 @@ -defmodule Explorer.TokenTransferTokenIdMigration.WorkerTest do - use Explorer.DataCase, async: false - - alias Explorer.Repo - alias Explorer.TokenTransferTokenIdMigration.{LowestBlockNumberUpdater, Worker} - alias Explorer.Utility.TokenTransferTokenIdMigratorProgress - - describe "Move TokenTransfer token_id to token_ids" do - test "Move token_ids and update last processed block number" do - insert(:token_transfer, block_number: 1, token_id: 1, transaction: insert(:transaction)) - insert(:token_transfer, block_number: 500, token_id: 2, transaction: insert(:transaction)) - insert(:token_transfer, block_number: 1000, token_id: 3, transaction: insert(:transaction)) - insert(:token_transfer, block_number: 1500, token_id: 4, transaction: insert(:transaction)) - insert(:token_transfer, block_number: 2000, token_id: 5, transaction: insert(:transaction)) - - TokenTransferTokenIdMigratorProgress.update_last_processed_block_number(3000, true) - LowestBlockNumberUpdater.start_link([]) - - Worker.start_link(idx: 1, first_block: 0, last_block: 3000, step: 2) - Worker.start_link(idx: 2, first_block: 0, last_block: 3000, step: 2) - Worker.start_link(idx: 3, first_block: 0, last_block: 3000, step: 2) - Process.sleep(200) - - token_transfers = Repo.all(Explorer.Chain.TokenTransfer) - assert Enum.all?(token_transfers, fn tt -> is_nil(tt.token_id) end) - - expected_token_ids = [[Decimal.new(1)], [Decimal.new(2)], [Decimal.new(3)], [Decimal.new(4)], [Decimal.new(5)]] - assert ^expected_token_ids = token_transfers |> Enum.map(& &1.token_ids) |> Enum.sort_by(&List.first/1) - end - end -end From a7a3d2827b92d0fe5c416576ab9b79cc0b80a442 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Thu, 21 Dec 2023 14:06:14 +0600 Subject: [PATCH 241/607] Token type filling migrations --- apps/explorer/config/runtime/test.exs | 2 + apps/explorer/lib/explorer/application.ex | 4 +- .../explorer/chain/address/token_balance.ex | 29 +++++-- .../chain/cache/background_migrations.ex | 26 +++++- ...ddress_current_token_balance_token_type.ex | 51 ++++++++++++ .../address_token_balance_token_type.ex | 51 ++++++++++++ .../explorer/migrator/filling_migration.ex | 83 +++++++++++++++++++ .../migrator/transactions_denormalization.ex | 76 ++++------------- .../chain/address/token_balance_test.exs | 1 + ..._current_token_balance_token_type_test.exs | 32 +++++++ .../address_token_balance_token_type_test.exs | 32 +++++++ ...sactions_denormalization_migrator_test.exs | 7 +- 12 files changed, 320 insertions(+), 74 deletions(-) create mode 100644 apps/explorer/lib/explorer/migrator/address_current_token_balance_token_type.ex create mode 100644 apps/explorer/lib/explorer/migrator/address_token_balance_token_type.ex create mode 100644 apps/explorer/lib/explorer/migrator/filling_migration.ex create mode 100644 apps/explorer/test/explorer/migrator/address_current_token_balance_token_type_test.exs create mode 100644 apps/explorer/test/explorer/migrator/address_token_balance_token_type_test.exs diff --git a/apps/explorer/config/runtime/test.exs b/apps/explorer/config/runtime/test.exs index d496caa48a78..ec346c1d3f89 100644 --- a/apps/explorer/config/runtime/test.exs +++ b/apps/explorer/config/runtime/test.exs @@ -36,6 +36,8 @@ config :explorer, Explorer.Tracer, disabled?: false config :explorer, Explorer.TokenInstanceOwnerAddressMigration.Supervisor, enabled: false config :explorer, Explorer.Migrator.TransactionsDenormalization, enabled: false +config :explorer, Explorer.Migrator.AddressCurrentTokenBalanceTokenType, enabled: false +config :explorer, Explorer.Migrator.AddressTokenBalanceTokenType, enabled: false config :explorer, realtime_events_sender: Explorer.Chain.Events.SimpleSender diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 1749cb69fd59..52b196489d07 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -127,7 +127,9 @@ defmodule Explorer.Application do configure(Explorer.TokenInstanceOwnerAddressMigration.Supervisor), sc_microservice_configure(Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand), configure(Explorer.Chain.Cache.RootstockLockedBTC), - configure(Explorer.Migrator.TransactionsDenormalization) + configure(Explorer.Migrator.TransactionsDenormalization), + configure(Explorer.Migrator.AddressCurrentTokenBalanceTokenType), + configure(Explorer.Migrator.AddressTokenBalanceTokenType) ] |> List.flatten() diff --git a/apps/explorer/lib/explorer/chain/address/token_balance.ex b/apps/explorer/lib/explorer/chain/address/token_balance.ex index ab7a75b62486..5b647bae1ed2 100644 --- a/apps/explorer/lib/explorer/chain/address/token_balance.ex +++ b/apps/explorer/lib/explorer/chain/address/token_balance.ex @@ -13,6 +13,7 @@ defmodule Explorer.Chain.Address.TokenBalance do alias Explorer.Chain alias Explorer.Chain.Address.TokenBalance + alias Explorer.Chain.Cache.BackgroundMigrations alias Explorer.Chain.{Address, Block, Hash, Token} @typedoc """ @@ -80,15 +81,27 @@ defmodule Explorer.Chain.Address.TokenBalance do ignores the burn_address for tokens ERC-721 since the most tokens ERC-721 don't allow get the balance for burn_address. """ + # credo:disable-for-next-line /Complexity/ def unfetched_token_balances do - from( - tb in TokenBalance, - join: t in Token, - on: tb.token_contract_address_hash == t.contract_address_hash, - where: - ((tb.address_hash != ^@burn_address_hash and t.type == "ERC-721") or t.type == "ERC-20" or t.type == "ERC-1155") and - (is_nil(tb.value_fetched_at) or is_nil(tb.value)) - ) + if BackgroundMigrations.get_tb_token_type_finished() do + from( + tb in TokenBalance, + where: + ((tb.address_hash != ^@burn_address_hash and tb.token_type == "ERC-721") or tb.token_type == "ERC-20" or + tb.token_type == "ERC-1155") and + (is_nil(tb.value_fetched_at) or is_nil(tb.value)) + ) + else + from( + tb in TokenBalance, + join: t in Token, + on: tb.token_contract_address_hash == t.contract_address_hash, + where: + ((tb.address_hash != ^@burn_address_hash and t.type == "ERC-721") or t.type == "ERC-20" or + t.type == "ERC-1155") and + (is_nil(tb.value_fetched_at) or is_nil(tb.value)) + ) + end end @doc """ diff --git a/apps/explorer/lib/explorer/chain/cache/background_migrations.ex b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex index 5e3b3524573b..3470f48c08b7 100644 --- a/apps/explorer/lib/explorer/chain/cache/background_migrations.ex +++ b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex @@ -7,11 +7,17 @@ defmodule Explorer.Chain.Cache.BackgroundMigrations do use Explorer.Chain.MapCache, name: :background_migrations_status, - key: :denormalization_finished + key: :denormalization_finished, + key: :tb_token_type_finished, + key: :ctb_token_type_finished @dialyzer :no_match - alias Explorer.Migrator.TransactionsDenormalization + alias Explorer.Migrator.{ + AddressCurrentTokenBalanceTokenType, + AddressTokenBalanceTokenType, + TransactionsDenormalization + } defp handle_fallback(:denormalization_finished) do Task.start(fn -> @@ -20,4 +26,20 @@ defmodule Explorer.Chain.Cache.BackgroundMigrations do {:return, false} end + + defp handle_fallback(:tb_token_type_finished) do + Task.start(fn -> + set_tb_token_type_finished(AddressTokenBalanceTokenType.migration_finished?()) + end) + + {:return, false} + end + + defp handle_fallback(:ctb_token_type_finished) do + Task.start(fn -> + set_ctb_token_type_finished(AddressCurrentTokenBalanceTokenType.migration_finished?()) + end) + + {:return, false} + end end diff --git a/apps/explorer/lib/explorer/migrator/address_current_token_balance_token_type.ex b/apps/explorer/lib/explorer/migrator/address_current_token_balance_token_type.ex new file mode 100644 index 000000000000..93226db73fce --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/address_current_token_balance_token_type.ex @@ -0,0 +1,51 @@ +defmodule Explorer.Migrator.AddressCurrentTokenBalanceTokenType do + @moduledoc """ + Fill empty token_type's for address_current_token_balances + """ + + use Explorer.Migrator.FillingMigration + + import Ecto.Query + + alias Explorer.Chain.Address.CurrentTokenBalance + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Migrator.FillingMigration + alias Explorer.Repo + + @migration_name "ctb_token_type" + + @impl FillingMigration + def migration_name, do: @migration_name + + @impl FillingMigration + def last_unprocessed_identifiers do + limit = batch_size() * concurrency() + + unprocessed_data_query() + |> select([ctb], ctb.id) + |> limit(^limit) + |> Repo.all(timeout: :infinity) + end + + @impl FillingMigration + def unprocessed_data_query do + from(ctb in CurrentTokenBalance, where: is_nil(ctb.token_type)) + end + + @impl FillingMigration + def update_batch(token_balance_ids) do + query = + from(current_token_balance in CurrentTokenBalance, + join: token in assoc(current_token_balance, :token), + where: current_token_balance.id in ^token_balance_ids, + update: [set: [token_type: token.type]] + ) + + Repo.update_all(query, [], timeout: :infinity) + end + + @impl FillingMigration + def update_cache do + BackgroundMigrations.set_ctb_token_type_finished(true) + end +end diff --git a/apps/explorer/lib/explorer/migrator/address_token_balance_token_type.ex b/apps/explorer/lib/explorer/migrator/address_token_balance_token_type.ex new file mode 100644 index 000000000000..9427db73ed60 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/address_token_balance_token_type.ex @@ -0,0 +1,51 @@ +defmodule Explorer.Migrator.AddressTokenBalanceTokenType do + @moduledoc """ + Fill empty token_type's for address_token_balances + """ + + use Explorer.Migrator.FillingMigration + + import Ecto.Query + + alias Explorer.Chain.Address.TokenBalance + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Migrator.FillingMigration + alias Explorer.Repo + + @migration_name "tb_token_type" + + @impl FillingMigration + def migration_name, do: @migration_name + + @impl FillingMigration + def last_unprocessed_identifiers do + limit = batch_size() * concurrency() + + unprocessed_data_query() + |> select([tb], tb.id) + |> limit(^limit) + |> Repo.all(timeout: :infinity) + end + + @impl FillingMigration + def unprocessed_data_query do + from(tb in TokenBalance, where: is_nil(tb.token_type)) + end + + @impl FillingMigration + def update_batch(token_balance_ids) do + query = + from(token_balance in TokenBalance, + join: token in assoc(token_balance, :token), + where: token_balance.id in ^token_balance_ids, + update: [set: [token_type: token.type]] + ) + + Repo.update_all(query, [], timeout: :infinity) + end + + @impl FillingMigration + def update_cache do + BackgroundMigrations.set_tb_token_type_finished(true) + end +end diff --git a/apps/explorer/lib/explorer/migrator/filling_migration.ex b/apps/explorer/lib/explorer/migrator/filling_migration.ex new file mode 100644 index 000000000000..05823e951765 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/filling_migration.ex @@ -0,0 +1,83 @@ +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 + not Repo.exists?(unprocessed_data_query()) + end + + @impl true + def init(_) do + case MigrationStatus.get_status(migration_name()) do + "completed" -> + :ignore + + _ -> + MigrationStatus.set_status(migration_name(), "started") + schedule_batch_migration() + {:ok, %{}} + end + end + + @impl true + def handle_info(:migrate_batch, state) do + case last_unprocessed_identifiers() do + [] -> + update_cache() + MigrationStatus.set_status(migration_name(), "completed") + {:stop, :normal, state} + + hashes -> + hashes + |> Enum.chunk_every(batch_size()) + |> Enum.map(&run_task/1) + |> Task.await_many(:infinity) + + schedule_batch_migration() + + {:noreply, state} + end + end + + defp run_task(batch), do: Task.async(fn -> update_batch(batch) end) + + defp schedule_batch_migration do + Process.send(self(), :migrate_batch, []) + end + + defp batch_size do + Application.get_env(:explorer, __MODULE__)[:batch_size] || @default_batch_size + end + + defp concurrency do + default = 4 * System.schedulers_online() + + Application.get_env(:explorer, __MODULE__)[:concurrency] || default + end + end + end +end diff --git a/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex b/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex index 166d7f834001..fe66548aba14 100644 --- a/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex +++ b/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex @@ -3,78 +3,39 @@ defmodule Explorer.Migrator.TransactionsDenormalization do Migrates all transactions to have set block_consensus and block_timestamp """ - use GenServer, restart: :transient + use Explorer.Migrator.FillingMigration import Ecto.Query alias Explorer.Chain.Cache.BackgroundMigrations alias Explorer.Chain.Transaction - alias Explorer.Migrator.MigrationStatus + alias Explorer.Migrator.FillingMigration alias Explorer.Repo - @default_batch_size 500 @migration_name "denormalization" - @spec start_link(term()) :: GenServer.on_start() - def start_link(_) do - GenServer.start_link(__MODULE__, :ok, name: __MODULE__) - end - - def migration_finished? do - not Repo.exists?(unprocessed_transactions_query()) - end - - @impl true - def init(_) do - case MigrationStatus.get_status(@migration_name) do - "completed" -> - :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_transaction_hashes() do - [] -> - BackgroundMigrations.set_denormalization_finished(true) - 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() + @impl FillingMigration + def migration_name, do: @migration_name - {:noreply, state} - end - end - - defp run_task(batch), do: Task.async(fn -> update_batch(batch) end) - - defp last_unprocessed_transaction_hashes do + @impl FillingMigration + def last_unprocessed_identifiers do limit = batch_size() * concurrency() - unprocessed_transactions_query() + unprocessed_data_query() |> select([t], t.hash) |> limit(^limit) |> Repo.all(timeout: :infinity) end - defp unprocessed_transactions_query do + @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 - defp update_batch(transaction_hashes) do + @impl FillingMigration + def update_batch(transaction_hashes) do query = from(transaction in Transaction, join: block in assoc(transaction, :block), @@ -85,17 +46,8 @@ defmodule Explorer.Migrator.TransactionsDenormalization do Repo.update_all(query, [], timeout: :infinity) 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 + @impl FillingMigration + def update_cache do + BackgroundMigrations.set_denormalization_finished(true) end end diff --git a/apps/explorer/test/explorer/chain/address/token_balance_test.exs b/apps/explorer/test/explorer/chain/address/token_balance_test.exs index 9a72837f6a5f..3e1a8018289a 100644 --- a/apps/explorer/test/explorer/chain/address/token_balance_test.exs +++ b/apps/explorer/test/explorer/chain/address/token_balance_test.exs @@ -46,6 +46,7 @@ defmodule Explorer.Chain.Address.TokenBalanceTest do :token_balance, address: burn_address, token_contract_address_hash: token.contract_address_hash, + token_type: "ERC-721", value_fetched_at: nil ) diff --git a/apps/explorer/test/explorer/migrator/address_current_token_balance_token_type_test.exs b/apps/explorer/test/explorer/migrator/address_current_token_balance_token_type_test.exs new file mode 100644 index 000000000000..f9e43e12e6fc --- /dev/null +++ b/apps/explorer/test/explorer/migrator/address_current_token_balance_token_type_test.exs @@ -0,0 +1,32 @@ +defmodule Explorer.Migrator.AddressCurrentTokenBalanceTokenTypeTest do + use Explorer.DataCase, async: false + + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Chain.Address.CurrentTokenBalance + alias Explorer.Migrator.{AddressCurrentTokenBalanceTokenType, MigrationStatus} + alias Explorer.Repo + + describe "Migrate current token balances" do + test "Set token_type for not processed current token balances" do + Enum.each(0..10, fn _x -> + current_token_balance = insert(:address_current_token_balance, token_type: nil) + assert %{token_type: nil} = current_token_balance + end) + + assert MigrationStatus.get_status("ctb_token_type") == nil + + AddressCurrentTokenBalanceTokenType.start_link([]) + Process.sleep(100) + + CurrentTokenBalance + |> Repo.all() + |> Repo.preload(:token) + |> Enum.each(fn ctb -> + assert %{token_type: token_type, token: %{type: token_type}} = ctb + assert not is_nil(token_type) + end) + + assert MigrationStatus.get_status("ctb_token_type") == "completed" + end + end +end diff --git a/apps/explorer/test/explorer/migrator/address_token_balance_token_type_test.exs b/apps/explorer/test/explorer/migrator/address_token_balance_token_type_test.exs new file mode 100644 index 000000000000..e1ac9d997fc1 --- /dev/null +++ b/apps/explorer/test/explorer/migrator/address_token_balance_token_type_test.exs @@ -0,0 +1,32 @@ +defmodule Explorer.Migrator.AddressTokenBalanceTokenTypeTest do + use Explorer.DataCase, async: false + + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Chain.Address.TokenBalance + alias Explorer.Migrator.{AddressTokenBalanceTokenType, MigrationStatus} + alias Explorer.Repo + + describe "Migrate token balances" do + test "Set token_type for not processed token balances" do + Enum.each(0..10, fn _x -> + token_balance = insert(:token_balance, token_type: nil) + assert %{token_type: nil} = token_balance + end) + + assert MigrationStatus.get_status("tb_token_type") == nil + + AddressTokenBalanceTokenType.start_link([]) + Process.sleep(100) + + TokenBalance + |> Repo.all() + |> Repo.preload(:token) + |> Enum.each(fn tb -> + assert %{token_type: token_type, token: %{type: token_type}} = tb + assert not is_nil(token_type) + end) + + assert MigrationStatus.get_status("tb_token_type") == "completed" + end + end +end diff --git a/apps/explorer/test/explorer/migrator/transactions_denormalization_migrator_test.exs b/apps/explorer/test/explorer/migrator/transactions_denormalization_migrator_test.exs index 8505844a7927..e47afe96da4d 100644 --- a/apps/explorer/test/explorer/migrator/transactions_denormalization_migrator_test.exs +++ b/apps/explorer/test/explorer/migrator/transactions_denormalization_migrator_test.exs @@ -1,8 +1,9 @@ defmodule Explorer.Migrator.TransactionsDenormalizationTest do use Explorer.DataCase, async: false + alias Explorer.Chain.Cache.BackgroundMigrations alias Explorer.Chain.Transaction - alias Explorer.Migrator.TransactionsDenormalization + alias Explorer.Migrator.{MigrationStatus, TransactionsDenormalization} alias Explorer.Repo describe "Migrate transactions" do @@ -20,6 +21,8 @@ defmodule Explorer.Migrator.TransactionsDenormalizationTest do assert not is_nil(timestamp) end) + assert MigrationStatus.get_status("denormalization") == nil + TransactionsDenormalization.start_link([]) Process.sleep(100) @@ -33,6 +36,8 @@ defmodule Explorer.Migrator.TransactionsDenormalizationTest do block: %{consensus: consensus, timestamp: timestamp} } = t end) + + assert MigrationStatus.get_status("denormalization") == "completed" end end end From 7b5838c091bb70ccf9259fae97830c3cc1df46e4 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Mon, 25 Dec 2023 15:16:51 +0600 Subject: [PATCH 242/607] Enable migrators in config --- apps/explorer/config/config.exs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index cc22af6be938..eafec8dec772 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -112,6 +112,8 @@ config :explorer, Explorer.Counters.BlockPriorityFeeCounter, 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 From a67da9a7e73de7d97d99f5b1ac1e2f3016f0c780 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Wed, 27 Dec 2023 16:49:42 +0600 Subject: [PATCH 243/607] Improve filling migrations tests --- .../migrator/address_current_token_balance_token_type_test.exs | 1 + .../explorer/migrator/address_token_balance_token_type_test.exs | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/explorer/test/explorer/migrator/address_current_token_balance_token_type_test.exs b/apps/explorer/test/explorer/migrator/address_current_token_balance_token_type_test.exs index f9e43e12e6fc..27591d9c52c1 100644 --- a/apps/explorer/test/explorer/migrator/address_current_token_balance_token_type_test.exs +++ b/apps/explorer/test/explorer/migrator/address_current_token_balance_token_type_test.exs @@ -27,6 +27,7 @@ defmodule Explorer.Migrator.AddressCurrentTokenBalanceTokenTypeTest do end) assert MigrationStatus.get_status("ctb_token_type") == "completed" + assert BackgroundMigrations.get_ctb_token_type_finished() == true end end end diff --git a/apps/explorer/test/explorer/migrator/address_token_balance_token_type_test.exs b/apps/explorer/test/explorer/migrator/address_token_balance_token_type_test.exs index e1ac9d997fc1..4bf09c57122c 100644 --- a/apps/explorer/test/explorer/migrator/address_token_balance_token_type_test.exs +++ b/apps/explorer/test/explorer/migrator/address_token_balance_token_type_test.exs @@ -27,6 +27,7 @@ defmodule Explorer.Migrator.AddressTokenBalanceTokenTypeTest do end) assert MigrationStatus.get_status("tb_token_type") == "completed" + assert BackgroundMigrations.get_tb_token_type_finished() == true end end end From 4f81bd8174c1132b41e3be6c150e30eb1e9d96db Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 27 Dec 2023 13:58:38 +0300 Subject: [PATCH 244/607] Add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d3662a542e0..abed60060102 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Chore +- [#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 From c7f566156ee0376f562a12b48e7934564e7bbd6f Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 25 Dec 2023 15:56:04 +0300 Subject: [PATCH 245/607] Add ASC indices for logs, token transfers, transactions --- CHANGELOG.md | 1 + ...0231225113850_transactions_asc_indices.exs | 47 +++++++++++++++++++ .../20231225115026_logs_asc_index.exs | 7 +++ ...231225115100_token_transfers_asc_index.exs | 7 +++ 4 files changed, 62 insertions(+) create mode 100644 apps/explorer/priv/repo/migrations/20231225113850_transactions_asc_indices.exs create mode 100644 apps/explorer/priv/repo/migrations/20231225115026_logs_asc_index.exs create mode 100644 apps/explorer/priv/repo/migrations/20231225115100_token_transfers_asc_index.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index abed60060102..c1df85bc68ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### 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 diff --git a/apps/explorer/priv/repo/migrations/20231225113850_transactions_asc_indices.exs b/apps/explorer/priv/repo/migrations/20231225113850_transactions_asc_indices.exs new file mode 100644 index 000000000000..b0f668e89cd5 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231225113850_transactions_asc_indices.exs @@ -0,0 +1,47 @@ +defmodule Explorer.Repo.Migrations.TransactionsAscIndices do + use Ecto.Migration + + def change do + create( + index( + :transactions, + [ + :from_address_hash, + "block_number ASC NULLS LAST", + "index ASC NULLS LAST", + "inserted_at ASC", + "hash DESC" + ], + name: "transactions_from_address_hash_with_pending_index_asc" + ) + ) + + create( + index( + :transactions, + [ + :to_address_hash, + "block_number ASC NULLS LAST", + "index ASC NULLS LAST", + "inserted_at ASC", + "hash DESC" + ], + name: "transactions_to_address_hash_with_pending_index_asc" + ) + ) + + create( + index( + :transactions, + [ + :created_contract_address_hash, + "block_number ASC NULLS LAST", + "index ASC NULLS LAST", + "inserted_at ASC", + "hash DESC" + ], + name: "transactions_created_contract_address_hash_with_pending_index_a" + ) + ) + end +end diff --git a/apps/explorer/priv/repo/migrations/20231225115026_logs_asc_index.exs b/apps/explorer/priv/repo/migrations/20231225115026_logs_asc_index.exs new file mode 100644 index 000000000000..7e7fc0963d6a --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231225115026_logs_asc_index.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Migrations.LogsAscIndex do + use Ecto.Migration + + def change do + create(index(:logs, ["block_number ASC, index ASC"])) + end +end diff --git a/apps/explorer/priv/repo/migrations/20231225115100_token_transfers_asc_index.exs b/apps/explorer/priv/repo/migrations/20231225115100_token_transfers_asc_index.exs new file mode 100644 index 000000000000..084ce83adb35 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231225115100_token_transfers_asc_index.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Migrations.TokenTransfersAscIndex do + use Ecto.Migration + + def change do + create(index(:token_transfers, ["block_number ASC", "log_index ASC"])) + end +end From 1cc481c98c2ecc33a4fb85a73143386092408efd Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 27 Dec 2023 17:52:26 +0300 Subject: [PATCH 246/607] Invalidate GA cache --- .github/workflows/config.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index bd487d8c2aa5..82c11d6d85ec 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -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-" From 286ddbc6f3cbc2dc2650aff4f78331fde54632d0 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 27 Dec 2023 18:22:09 +0300 Subject: [PATCH 247/607] Update version to 6.0.0 --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/workflows/prerelease.yml | 2 +- .../workflows/publish-docker-image-every-push.yml | 2 +- .../workflows/publish-docker-image-for-core.yml | 2 +- .../publish-docker-image-for-eth-goerli.yml | 2 +- .../publish-docker-image-for-eth-sepolia.yml | 2 +- .github/workflows/publish-docker-image-for-eth.yml | 2 +- .../workflows/publish-docker-image-for-fuse.yml | 2 +- .../publish-docker-image-for-immutable.yml | 2 +- .../publish-docker-image-for-l2-staging.yml | 2 +- .../workflows/publish-docker-image-for-lukso.yml | 2 +- .../publish-docker-image-for-optimism.yml | 2 +- .../publish-docker-image-for-polygon-edge.yml | 2 +- .github/workflows/publish-docker-image-for-rsk.yml | 2 +- .../publish-docker-image-for-stability.yml | 2 +- .../workflows/publish-docker-image-for-suave.yml | 2 +- .../workflows/publish-docker-image-for-xdai.yml | 2 +- .../workflows/publish-docker-image-for-zkevm.yml | 2 +- .../workflows/publish-docker-image-for-zksync.yml | 2 +- .../publish-docker-image-staging-on-demand.yml | 2 +- .github/workflows/release-additional.yml | 2 +- .github/workflows/release.yml | 2 +- CHANGELOG.md | 14 ++++++++++++++ apps/block_scout_web/mix.exs | 2 +- apps/ethereum_jsonrpc/mix.exs | 2 +- apps/explorer/mix.exs | 2 +- apps/indexer/mix.exs | 2 +- docker-compose/docker-compose.yml | 2 +- docker/Makefile | 2 +- mix.exs | 2 +- rel/config.exs | 2 +- 31 files changed, 44 insertions(+), 30 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 4f35712cae63..dcd9fc29d2b7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -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 diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 070650c676f4..2d09956875c9 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -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 diff --git a/.github/workflows/publish-docker-image-every-push.yml b/.github/workflows/publish-docker-image-every-push.yml index 10d2cfabc9ff..25d9c3fa9960 100644 --- a/.github/workflows/publish-docker-image-every-push.yml +++ b/.github/workflows/publish-docker-image-every-push.yml @@ -11,7 +11,7 @@ on: env: OTP_VERSION: '25.2.1' ELIXIR_VERSION: '1.14.5' - RELEASE_VERSION: 5.4.0 + RELEASE_VERSION: 6.0.0 jobs: push_to_registry: diff --git a/.github/workflows/publish-docker-image-for-core.yml b/.github/workflows/publish-docker-image-for-core.yml index 0c76d126208f..61cb6f8189a2 100644 --- a/.github/workflows/publish-docker-image-for-core.yml +++ b/.github/workflows/publish-docker-image-for-core.yml @@ -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 diff --git a/.github/workflows/publish-docker-image-for-eth-goerli.yml b/.github/workflows/publish-docker-image-for-eth-goerli.yml index 1911bbb35795..f381283cf359 100644 --- a/.github/workflows/publish-docker-image-for-eth-goerli.yml +++ b/.github/workflows/publish-docker-image-for-eth-goerli.yml @@ -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 diff --git a/.github/workflows/publish-docker-image-for-eth-sepolia.yml b/.github/workflows/publish-docker-image-for-eth-sepolia.yml index 7c09456adcb7..b24c2571676e 100644 --- a/.github/workflows/publish-docker-image-for-eth-sepolia.yml +++ b/.github/workflows/publish-docker-image-for-eth-sepolia.yml @@ -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 diff --git a/.github/workflows/publish-docker-image-for-eth.yml b/.github/workflows/publish-docker-image-for-eth.yml index f7a0ad5fd2a2..a663380306f2 100644 --- a/.github/workflows/publish-docker-image-for-eth.yml +++ b/.github/workflows/publish-docker-image-for-eth.yml @@ -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 diff --git a/.github/workflows/publish-docker-image-for-fuse.yml b/.github/workflows/publish-docker-image-for-fuse.yml index 286664969fb6..a9ec8713e782 100644 --- a/.github/workflows/publish-docker-image-for-fuse.yml +++ b/.github/workflows/publish-docker-image-for-fuse.yml @@ -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 diff --git a/.github/workflows/publish-docker-image-for-immutable.yml b/.github/workflows/publish-docker-image-for-immutable.yml index 1c9e5cc85af0..8bd44e3d1ccd 100644 --- a/.github/workflows/publish-docker-image-for-immutable.yml +++ b/.github/workflows/publish-docker-image-for-immutable.yml @@ -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 diff --git a/.github/workflows/publish-docker-image-for-l2-staging.yml b/.github/workflows/publish-docker-image-for-l2-staging.yml index 637b880a2ba5..1c5f290907a6 100644 --- a/.github/workflows/publish-docker-image-for-l2-staging.yml +++ b/.github/workflows/publish-docker-image-for-l2-staging.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.4.0 + RELEASE_VERSION: 6.0.0 DOCKER_CHAIN_NAME: optimism-l2-advanced steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-lukso.yml b/.github/workflows/publish-docker-image-for-lukso.yml index 4cd324babd30..10efc69fe25a 100644 --- a/.github/workflows/publish-docker-image-for-lukso.yml +++ b/.github/workflows/publish-docker-image-for-lukso.yml @@ -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 diff --git a/.github/workflows/publish-docker-image-for-optimism.yml b/.github/workflows/publish-docker-image-for-optimism.yml index 320ed18ba421..f0c24fa58221 100644 --- a/.github/workflows/publish-docker-image-for-optimism.yml +++ b/.github/workflows/publish-docker-image-for-optimism.yml @@ -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 diff --git a/.github/workflows/publish-docker-image-for-polygon-edge.yml b/.github/workflows/publish-docker-image-for-polygon-edge.yml index d2153e90725a..a7384c4f29fc 100644 --- a/.github/workflows/publish-docker-image-for-polygon-edge.yml +++ b/.github/workflows/publish-docker-image-for-polygon-edge.yml @@ -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 diff --git a/.github/workflows/publish-docker-image-for-rsk.yml b/.github/workflows/publish-docker-image-for-rsk.yml index 3f77608f0c3d..615ad8820bca 100644 --- a/.github/workflows/publish-docker-image-for-rsk.yml +++ b/.github/workflows/publish-docker-image-for-rsk.yml @@ -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 diff --git a/.github/workflows/publish-docker-image-for-stability.yml b/.github/workflows/publish-docker-image-for-stability.yml index b81cbc13344c..c14e5a6a6ac9 100644 --- a/.github/workflows/publish-docker-image-for-stability.yml +++ b/.github/workflows/publish-docker-image-for-stability.yml @@ -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 diff --git a/.github/workflows/publish-docker-image-for-suave.yml b/.github/workflows/publish-docker-image-for-suave.yml index 8ba4ec4975ed..0f3c4ad43982 100644 --- a/.github/workflows/publish-docker-image-for-suave.yml +++ b/.github/workflows/publish-docker-image-for-suave.yml @@ -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 diff --git a/.github/workflows/publish-docker-image-for-xdai.yml b/.github/workflows/publish-docker-image-for-xdai.yml index 7af9b838a315..b53fe18fc50f 100644 --- a/.github/workflows/publish-docker-image-for-xdai.yml +++ b/.github/workflows/publish-docker-image-for-xdai.yml @@ -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 diff --git a/.github/workflows/publish-docker-image-for-zkevm.yml b/.github/workflows/publish-docker-image-for-zkevm.yml index 603719db11aa..0e7072a3940e 100644 --- a/.github/workflows/publish-docker-image-for-zkevm.yml +++ b/.github/workflows/publish-docker-image-for-zkevm.yml @@ -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 diff --git a/.github/workflows/publish-docker-image-for-zksync.yml b/.github/workflows/publish-docker-image-for-zksync.yml index aa6317647923..04cdf569a63d 100644 --- a/.github/workflows/publish-docker-image-for-zksync.yml +++ b/.github/workflows/publish-docker-image-for-zksync.yml @@ -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 diff --git a/.github/workflows/publish-docker-image-staging-on-demand.yml b/.github/workflows/publish-docker-image-staging-on-demand.yml index e33a3ee7c37d..090a6be9d782 100644 --- a/.github/workflows/publish-docker-image-staging-on-demand.yml +++ b/.github/workflows/publish-docker-image-staging-on-demand.yml @@ -12,7 +12,7 @@ on: env: OTP_VERSION: '25.2.1' ELIXIR_VERSION: '1.14.5' - RELEASE_VERSION: 5.4.0 + RELEASE_VERSION: 6.0.0 jobs: push_to_registry: diff --git a/.github/workflows/release-additional.yml b/.github/workflows/release-additional.yml index 36ff36c1aa79..f0912540dc69 100644 --- a/.github/workflows/release-additional.yml +++ b/.github/workflows/release-additional.yml @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.4.0 + RELEASE_VERSION: 6.0.0 steps: - name: Check out the repo uses: actions/checkout@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cd7599014882..ded48ce497f3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index c1df85bc68ef..5dc5c4caff1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # ChangeLog +## Current + +### Features + +### Fixes + +### Chore + +
+ Dependencies version bumps + +
+ ## 6.0.0-dev ### Features @@ -65,6 +78,7 @@ - [#9094](https://github.com/blockscout/blockscout/pull/9094) - Improve exchange rates logging - [#9014](https://github.com/blockscout/blockscout/pull/9014) - Decrease amount of NFT in address collection: 15 -> 9 - [#8994](https://github.com/blockscout/blockscout/pull/8994) - Refactor transactions event preloads +- [#8991](https://github.com/blockscout/blockscout/pull/8991) - Manage DB queue target via runtime env var
Dependencies version bumps diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index d155e0c572b8..e90612b58202 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -23,7 +23,7 @@ defmodule BlockScoutWeb.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "5.4.0", + version: "6.0.0", xref: [exclude: [Explorer.Chain.Zkevm.Reader]] ] end diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index 1da7647be613..3545774c33a3 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -23,7 +23,7 @@ defmodule EthereumJsonrpc.MixProject do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "5.4.0" + version: "6.0.0" ] end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index a4195751ef8d..ae066dfdc75f 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -24,7 +24,7 @@ defmodule Explorer.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "5.4.0", + version: "6.0.0", xref: [exclude: [BlockScoutWeb.WebRouter.Helpers]] ] end diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs index 58b20d24aa4c..7e349261ccbd 100644 --- a/apps/indexer/mix.exs +++ b/apps/indexer/mix.exs @@ -14,7 +14,7 @@ defmodule Indexer.MixProject do elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", start_permanent: Mix.env() == :prod, - version: "5.4.0" + version: "6.0.0" ] end diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 2bbda2c94236..b00502a5a187 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -34,7 +34,7 @@ services: CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED: "" CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL: "" ADMIN_PANEL_ENABLED: "" - RELEASE_VERSION: 5.4.0 + RELEASE_VERSION: 6.0.0 links: - db:database environment: diff --git a/docker/Makefile b/docker/Makefile index 09e923956423..63bcd32de63b 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -10,7 +10,7 @@ STATS_CONTAINER_NAME := stats STATS_DB_CONTAINER_NAME := stats-postgres PROXY_CONTAINER_NAME := proxy PG_CONTAINER_NAME := postgres -RELEASE_VERSION ?= '5.4.0' +RELEASE_VERSION ?= '6.0.0' TAG := $(RELEASE_VERSION)-commit-$(shell git log -1 --pretty=format:"%h") STABLE_TAG := $(RELEASE_VERSION) diff --git a/mix.exs b/mix.exs index f144b85ac3e7..06f570b70e79 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule BlockScout.Mixfile do [ # app: :block_scout, # aliases: aliases(config_env()), - version: "5.4.0", + version: "6.0.0", apps_path: "apps", deps: deps(), dialyzer: dialyzer(), diff --git a/rel/config.exs b/rel/config.exs index aef4c2f2d18e..b7c3f38132d3 100644 --- a/rel/config.exs +++ b/rel/config.exs @@ -71,7 +71,7 @@ end # will be used by default release :blockscout do - set version: "5.4.0-beta" + set version: "6.0.0-beta" set applications: [ :runtime_tools, block_scout_web: :permanent, From d4e03a8e7fc2399d90e51b54cee7c0b6af9e25dc Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Fri, 5 Jan 2024 15:54:28 +0400 Subject: [PATCH 248/607] Fix migration_finished? logic --- CHANGELOG.md | 2 ++ apps/explorer/lib/explorer/etherscan.ex | 3 ++- apps/explorer/lib/explorer/migrator/filling_migration.ex | 2 +- .../test/explorer/chain/cache/gas_price_oracle_test.exs | 3 +++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dc5c4caff1d..80a642db0cea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ ### Fixes +- [#9101](https://github.com/blockscout/blockscout/pull/9101) - Fix migration_finished? logic + ### Chore - [#9055](https://github.com/blockscout/blockscout/pull/9055) - Add ASC indices for logs, token transfers, transactions diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index dba8be73ebcc..023f74e9dadb 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -556,6 +556,7 @@ defmodule Explorer.Etherscan do 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, @@ -567,7 +568,7 @@ defmodule Explorer.Etherscan do block_number: b.number, block_timestamp: b.timestamp, confirmations: fragment("? - ?", ^block_height, t.block_number), - token_id: tt.token_id, + token_ids: tt.token_ids, token_name: tt.token_name, token_symbol: tt.token_symbol, token_decimals: tt.token_decimals, diff --git a/apps/explorer/lib/explorer/migrator/filling_migration.ex b/apps/explorer/lib/explorer/migrator/filling_migration.ex index 05823e951765..7ca5b26989f4 100644 --- a/apps/explorer/lib/explorer/migrator/filling_migration.ex +++ b/apps/explorer/lib/explorer/migrator/filling_migration.ex @@ -27,7 +27,7 @@ defmodule Explorer.Migrator.FillingMigration do end def migration_finished? do - not Repo.exists?(unprocessed_data_query()) + MigrationStatus.get_status(migration_name()) == "completed" end @impl true diff --git a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs index 20472f79ee32..a30d4070f66f 100644 --- a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs +++ b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs @@ -351,6 +351,7 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do status: :ok, block_hash: block1.hash, block_number: block1.number, + block_timestamp: block1.timestamp, cumulative_gas_used: 884_322, gas_used: 106_025, index: 0, @@ -366,6 +367,7 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do status: :ok, block_hash: block2.hash, block_number: block2.number, + block_timestamp: block2.timestamp, cumulative_gas_used: 884_322, gas_used: 106_025, index: 0, @@ -381,6 +383,7 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do status: :ok, block_hash: block2.hash, block_number: block2.number, + block_timestamp: block2.timestamp, cumulative_gas_used: 884_322, gas_used: 106_025, index: 1, From a0e5f11fca9ec9d5cc7143f6b38f38b9e11b77df Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Mon, 8 Jan 2024 14:08:56 +0400 Subject: [PATCH 249/607] Update migrator's cache if migration is already finished --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/migrator/filling_migration.ex | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80a642db0cea..3ff34b5b4031 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ ### 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 ### Chore diff --git a/apps/explorer/lib/explorer/migrator/filling_migration.ex b/apps/explorer/lib/explorer/migrator/filling_migration.ex index 7ca5b26989f4..507dfcb6e5f7 100644 --- a/apps/explorer/lib/explorer/migrator/filling_migration.ex +++ b/apps/explorer/lib/explorer/migrator/filling_migration.ex @@ -34,6 +34,7 @@ defmodule Explorer.Migrator.FillingMigration do def init(_) do case MigrationStatus.get_status(migration_name()) do "completed" -> + update_cache() :ignore _ -> From 4bf97cd0f1ba5031f242737b2d9611e0faec201a Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 8 Jan 2024 16:58:09 +0300 Subject: [PATCH 250/607] Update CHANGELOG --- CHANGELOG.md | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ff34b5b4031..701e81828fb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,14 +13,20 @@
-## 6.0.0-dev +## 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 @@ -34,24 +40,21 @@ - [#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 -## Current - -### 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 - -- [#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 -
Dependencies version bumps +- [#9059](https://github.com/blockscout/blockscout/pull/9059) - Bump redux from 5.0.0 to 5.0.1 in /apps/block_scout_web/assets +- [#9057](https://github.com/blockscout/blockscout/pull/9057) - Bump benchee from 1.2.0 to 1.3.0 +- [#9060](https://github.com/blockscout/blockscout/pull/9060) - Bump @amplitude/analytics-browser from 2.3.7 to 2.3.8 in /apps/block_scout_web/assets +- [#9084](https://github.com/blockscout/blockscout/pull/9084) - Bump @babel/preset-env from 7.23.6 to 7.23.7 in /apps/block_scout_web/assets +- [#9083](https://github.com/blockscout/blockscout/pull/9083) - Bump @babel/core from 7.23.6 to 7.23.7 in /apps/block_scout_web/assets +- [#9086](https://github.com/blockscout/blockscout/pull/9086) - Bump core-js from 3.34.0 to 3.35.0 in /apps/block_scout_web/assets +- [#9081](https://github.com/blockscout/blockscout/pull/9081) - Bump sweetalert2 from 11.10.1 to 11.10.2 in /apps/block_scout_web/assets +- [#9085](https://github.com/blockscout/blockscout/pull/9085) - Bump moment from 2.29.4 to 2.30.1 in /apps/block_scout_web/assets +- [#9087](https://github.com/blockscout/blockscout/pull/9087) - Bump postcss-loader from 7.3.3 to 7.3.4 in /apps/block_scout_web/assets +- [#9082](https://github.com/blockscout/blockscout/pull/9082) - Bump sass-loader from 13.3.2 to 13.3.3 in /apps/block_scout_web/assets +- [#9088](https://github.com/blockscout/blockscout/pull/9088) - Bump sass from 1.69.5 to 1.69.6 in /apps/block_scout_web/assets +
## 5.4.0-beta From 2098e5cc70f533b94662f050d2c989e635d3a675 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 18:08:20 +0000 Subject: [PATCH 251/607] Bump ueberauth from 0.10.5 to 0.10.7 Bumps [ueberauth](https://github.com/ueberauth/ueberauth) from 0.10.5 to 0.10.7. - [Release notes](https://github.com/ueberauth/ueberauth/releases) - [Changelog](https://github.com/ueberauth/ueberauth/blob/master/CHANGELOG.md) - [Commits](https://github.com/ueberauth/ueberauth/compare/v0.10.5...v0.10.7) --- updated-dependencies: - dependency-name: ueberauth dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index be7161e17e3d..619743cf93a8 100644 --- a/mix.lock +++ b/mix.lock @@ -136,7 +136,7 @@ "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, "toml": {:hex, :toml, "0.6.2", "38f445df384a17e5d382befe30e3489112a48d3ba4c459e543f748c2f25dd4d1", [:mix], [], "hexpm", "d013e45126d74c0c26a38d31f5e8e9b83ea19fc752470feb9a86071ca5a672fa"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, - "ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"}, + "ueberauth": {:hex, :ueberauth, "0.10.7", "5a31cbe11e7ce5c7484d745dc9e1f11948e89662f8510d03c616de03df581ebd", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "0bccf73e2ffd6337971340832947ba232877aa8122dba4c95be9f729c8987377"}, "ueberauth_auth0": {:hex, :ueberauth_auth0, "2.1.0", "0632d5844049fa2f26823f15e1120aa32f27df6f27ce515a4b04641736594bf4", [:mix], [{:oauth2, "~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "8d3b30fa27c95c9e82c30c4afb016251405706d2e9627e603c3c9787fd1314fc"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "wallaby": {:hex, :wallaby, "0.30.6", "7dc4c1213f3b52c4152581d126632bc7e06892336d3a0f582853efeeabd45a71", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "50950c1d968549b54c20e16175c68c7fc0824138e2bb93feb11ef6add8eb23d4"}, From 5eac5a40bd2d51c359d533aad334030b49f4dc8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 18:31:55 +0000 Subject: [PATCH 252/607] Bump sass from 1.69.6 to 1.69.7 in /apps/block_scout_web/assets Bumps [sass](https://github.com/sass/dart-sass) from 1.69.6 to 1.69.7. - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.69.6...1.69.7) --- updated-dependencies: - dependency-name: sass dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 77192dd8a924..3e26e42e9e28 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -89,7 +89,7 @@ "mini-css-extract-plugin": "^2.7.6", "postcss": "^8.4.32", "postcss-loader": "^7.3.4", - "sass": "^1.69.6", + "sass": "^1.69.7", "sass-loader": "^13.3.3", "style-loader": "^3.3.3", "webpack": "^5.89.0", @@ -15193,9 +15193,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.69.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.6.tgz", - "integrity": "sha512-qbRr3k9JGHWXCvZU77SD2OTwUlC+gNT+61JOLcmLm+XqH4h/5D+p4IIsxvpkB89S9AwJOyb5+rWNpIucaFxSFQ==", + "version": "1.69.7", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz", + "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -29243,9 +29243,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass": { - "version": "1.69.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.6.tgz", - "integrity": "sha512-qbRr3k9JGHWXCvZU77SD2OTwUlC+gNT+61JOLcmLm+XqH4h/5D+p4IIsxvpkB89S9AwJOyb5+rWNpIucaFxSFQ==", + "version": "1.69.7", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz", + "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 9d8513f7037a..c7ebe9a98806 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -101,7 +101,7 @@ "mini-css-extract-plugin": "^2.7.6", "postcss": "^8.4.32", "postcss-loader": "^7.3.4", - "sass": "^1.69.6", + "sass": "^1.69.7", "sass-loader": "^13.3.3", "style-loader": "^3.3.3", "webpack": "^5.89.0", From d2683964f8d3ef806c8a277c5c65e6343fd86ac5 Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Sat, 30 Dec 2023 21:54:55 +0800 Subject: [PATCH 253/607] add status in stream_transactions_with_unfetched_created_contract_codes --- apps/explorer/lib/explorer/chain.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index f6a4e3615fa4..e61f9cf6ca22 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1988,7 +1988,7 @@ defmodule Explorer.Chain do from(t in Transaction, where: not is_nil(t.block_hash) and not is_nil(t.created_contract_address_hash) and - is_nil(t.created_contract_code_indexed_at), + is_nil(t.created_contract_code_indexed_at) and t.status == ^1, select: ^fields ) From faadefd58c47270e4ede32aa6311998bdbd8863b Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Tue, 9 Jan 2024 13:33:54 +0800 Subject: [PATCH 254/607] rebase master --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 701e81828fb7..cf23936d0a38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Fixes +- [#9075](https://github.com/blockscout/blockscout/pull/9075) - Fix fetching contract codes + ### Chore
From a530bf666160c5d430773729bd5ae24671d8a990 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Wed, 3 Jan 2024 21:03:00 +0300 Subject: [PATCH 255/607] Send only unique requests in ContractCode fetcher --- apps/indexer/lib/indexer/fetcher/contract_code.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/indexer/lib/indexer/fetcher/contract_code.ex b/apps/indexer/lib/indexer/fetcher/contract_code.ex index c9f8f0f7b6e8..716b8f25e204 100644 --- a/apps/indexer/lib/indexer/fetcher/contract_code.ex +++ b/apps/indexer/lib/indexer/fetcher/contract_code.ex @@ -98,6 +98,7 @@ defmodule Indexer.Fetcher.ContractCode do Logger.debug("fetching created_contract_code for transactions") entries + |> Enum.uniq() |> Enum.map(¶ms/1) |> EthereumJSONRPC.fetch_codes(json_rpc_named_arguments) |> case do From abcfe1656381009dab1e3eec98ef7a8d65504a57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:59:06 +0000 Subject: [PATCH 256/607] Bump follow-redirects from 1.14.8 to 1.15.4 in /apps/explorer Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.8 to 1.15.4. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.8...v1.15.4) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] --- apps/explorer/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/explorer/package-lock.json b/apps/explorer/package-lock.json index 86af32e1175b..88f5dd663846 100644 --- a/apps/explorer/package-lock.json +++ b/apps/explorer/package-lock.json @@ -28,9 +28,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "funding": [ { "type": "individual", @@ -134,9 +134,9 @@ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" }, "follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==" + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==" }, "js-sha3": { "version": "0.8.0", From c9d57051f835916671a2b52b83af2f26f7e3e1ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jan 2024 12:00:17 +0000 Subject: [PATCH 257/607] Bump postcss from 8.4.32 to 8.4.33 in /apps/block_scout_web/assets Bumps [postcss](https://github.com/postcss/postcss) from 8.4.32 to 8.4.33. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.32...8.4.33) --- updated-dependencies: - dependency-name: postcss dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 3e26e42e9e28..1ad62aa64380 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -87,7 +87,7 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "mini-css-extract-plugin": "^2.7.6", - "postcss": "^8.4.32", + "postcss": "^8.4.33", "postcss-loader": "^7.3.4", "sass": "^1.69.7", "sass-loader": "^13.3.3", @@ -13743,9 +13743,9 @@ } }, "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", "dev": true, "funding": [ { @@ -28217,9 +28217,9 @@ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" }, "postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", "dev": true, "requires": { "nanoid": "^3.3.7", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index c7ebe9a98806..e47b66d1bc48 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -99,7 +99,7 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "mini-css-extract-plugin": "^2.7.6", - "postcss": "^8.4.32", + "postcss": "^8.4.33", "postcss-loader": "^7.3.4", "sass": "^1.69.7", "sass-loader": "^13.3.3", From f21b2672f716cbfe6ebd29babfcaa2e9f98b7776 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Fri, 5 Jan 2024 15:54:39 +0300 Subject: [PATCH 258/607] Fix some log topics for Suave and Polygon Edge --- .dialyzer-ignore | 4 ++-- .../views/api/v2/transaction_view.ex | 13 ++++++++++++- .../indexer/fetcher/polygon_edge/deposit_execute.ex | 7 ++++++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/.dialyzer-ignore b/.dialyzer-ignore index ba3b24756c02..15662b23302e 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -14,8 +14,8 @@ 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:146 -lib/indexer/fetcher/polygon_edge/deposit_execute.ex:190 +lib/indexer/fetcher/polygon_edge/deposit_execute.ex:151 +lib/indexer/fetcher/polygon_edge/deposit_execute.ex:195 lib/indexer/fetcher/polygon_edge/withdrawal.ex:166 lib/indexer/fetcher/polygon_edge/withdrawal.ex:210 lib/indexer/fetcher/zkevm/transaction_batch.ex:116 diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index 44832a51274b..a3ac1335ea45 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -561,7 +561,18 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end defp sanitize_log_first_topic(first_topic) do - if is_nil(first_topic), do: "", else: String.downcase(first_topic) + if is_nil(first_topic) do + "" + else + sanitized = + if is_binary(first_topic) do + first_topic + else + Hash.to_string(first_topic) + end + + String.downcase(sanitized) + end end def token_transfers(_, _conn, false), do: nil diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex index ef1ad37e4a4c..950cb026ffb4 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex @@ -134,11 +134,16 @@ defmodule Indexer.Fetcher.PolygonEdge.DepositExecute do |> log_topic_to_string() |> quantity_to_integer() + status = + third_topic + |> log_topic_to_string() + |> quantity_to_integer() + %{ msg_id: msg_id, l2_transaction_hash: l2_transaction_hash, l2_block_number: quantity_to_integer(l2_block_number), - success: quantity_to_integer(third_topic) != 0 + success: status != 0 } end From 21934db1fc424741b5db2b00ffdcbbe5fd63cc7c Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Fri, 5 Jan 2024 15:59:04 +0300 Subject: [PATCH 259/607] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf23936d0a38..71241a944879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ ### Fixes - [#9113](https://github.com/blockscout/blockscout/pull/9113) - Fix migrators cache updating +- [#9102](https://github.com/blockscout/blockscout/pull/9102) - Fix some log topics for Suave and Polygon Edge - [#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 From 01bd827a20b770235e49788909be25c30671bfa6 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Tue, 9 Jan 2024 12:04:01 +0300 Subject: [PATCH 260/607] Add interpolation operator to some queries --- .../indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex | 2 +- apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex index 950cb026ffb4..0807c1bc8dfd 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex @@ -161,7 +161,7 @@ defmodule Indexer.Fetcher.PolygonEdge.DepositExecute do from(log in Log, select: {log.second_topic, log.third_topic, log.transaction_hash, log.block_number}, where: - log.first_topic == @state_sync_result_event and log.address_hash == ^state_receiver and + log.first_topic == ^@state_sync_result_event and log.address_hash == ^state_receiver and log.block_number >= ^block_start and log.block_number <= ^block_end ) diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex index 098545140ac0..8520cce2c155 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex @@ -176,7 +176,7 @@ defmodule Indexer.Fetcher.PolygonEdge.Withdrawal do from(log in Log, select: {log.second_topic, log.data, log.transaction_hash, log.block_number}, where: - log.first_topic == @l2_state_synced_event and log.address_hash == ^state_sender and + log.first_topic == ^@l2_state_synced_event and log.address_hash == ^state_sender and log.block_number >= ^block_start and log.block_number <= ^block_end ) From e9a9dfd28e1a4adbc44fa47b655cec4e86934426 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Wed, 10 Jan 2024 08:26:28 +0300 Subject: [PATCH 261/607] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71241a944879..ec01d7fa4972 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Fixes +- [#9102](https://github.com/blockscout/blockscout/pull/9102) - Fix some log topics for Suave and Polygon Edge - [#9075](https://github.com/blockscout/blockscout/pull/9075) - Fix fetching contract codes ### Chore @@ -25,7 +26,6 @@ ### Fixes - [#9113](https://github.com/blockscout/blockscout/pull/9113) - Fix migrators cache updating -- [#9102](https://github.com/blockscout/blockscout/pull/9102) - Fix some log topics for Suave and Polygon Edge - [#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 From 05242dbb66ba7829dfb841b4f306b612dfd66593 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 29 Dec 2023 16:21:19 +0300 Subject: [PATCH 262/607] Allow payable function with output appear in the Read tab --- CHANGELOG.md | 1 + .../lib/explorer/smart_contract/helper.ex | 2 +- .../explorer/smart_contract/helper_test.exs | 26 +++++++++++++++++++ cspell.json | 1 + 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf23936d0a38..73ee77784bd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Fixes - [#9075](https://github.com/blockscout/blockscout/pull/9075) - Fix fetching contract codes +- [#9073](https://github.com/blockscout/blockscout/pull/9073) - Allow payable function with output appear in the Read tab ### Chore diff --git a/apps/explorer/lib/explorer/smart_contract/helper.ex b/apps/explorer/lib/explorer/smart_contract/helper.ex index 544cb7c19e77..40ffcaa69866 100644 --- a/apps/explorer/lib/explorer/smart_contract/helper.ex +++ b/apps/explorer/lib/explorer/smart_contract/helper.ex @@ -23,7 +23,7 @@ defmodule Explorer.SmartContract.Helper do @spec read_with_wallet_method?(%{}) :: true | false def read_with_wallet_method?(function), do: - !error?(function) && !event?(function) && !constructor?(function) && nonpayable?(function) && + !error?(function) && !event?(function) && !constructor?(function) && !empty_outputs?(function) def empty_outputs?(function), do: is_nil(function["outputs"]) || function["outputs"] == [] diff --git a/apps/explorer/test/explorer/smart_contract/helper_test.exs b/apps/explorer/test/explorer/smart_contract/helper_test.exs index 4cd3832434c9..202c0b7edc1f 100644 --- a/apps/explorer/test/explorer/smart_contract/helper_test.exs +++ b/apps/explorer/test/explorer/smart_contract/helper_test.exs @@ -124,4 +124,30 @@ defmodule Explorer.SmartContract.HelperTest do refute Helper.nonpayable?(function) end end + + describe "read_with_wallet_method?" do + test "returns payable method with output in the read tab" do + function = %{ + "type" => "function", + "stateMutability" => "payable", + "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}], + "name" => "returnaddress", + "inputs" => [] + } + + assert Helper.read_with_wallet_method?(function) + end + + test "doesn't return payable method with no output in the read tab" do + function = %{ + "type" => "function", + "stateMutability" => "payable", + "outputs" => [], + "name" => "returnaddress", + "inputs" => [] + } + + refute Helper.read_with_wallet_method?(function) + end + end end diff --git a/cspell.json b/cspell.json index d19d333d4403..ddad179c8d1b 100644 --- a/cspell.json +++ b/cspell.json @@ -389,6 +389,7 @@ "rerequest", "reshows", "retryable", + "returnaddress", "reuseaddr", "RPC's", "RPCs", From 8a311514605277d235b4afee2a76867aadcae4e6 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Fri, 5 Jan 2024 18:31:19 +0300 Subject: [PATCH 263/607] Return current exchange rate in api/v2/stats --- CHANGELOG.md | 1 + .../controllers/api/v2/stats_controller.ex | 2 +- .../explorer/exchange_rates/exchange_rates.ex | 22 ++++++++++++++++++- .../lib/explorer/market/market_history.ex | 2 +- .../exchange_rates/exchange_rates_test.exs | 8 ++++++- 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50cade811d01..8672e81278ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Fixes +- [#9109](https://github.com/blockscout/blockscout/pull/9109) - Return current exchange rate in api/v2/stats - [#9102](https://github.com/blockscout/blockscout/pull/9102) - Fix some log topics for Suave and Polygon Edge - [#9075](https://github.com/blockscout/blockscout/pull/9075) - Fix fetching contract codes - [#9073](https://github.com/blockscout/blockscout/pull/9073) - Allow payable function with output appear in the Read tab diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex index 34ea7b3eae34..7e78533fba9a 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex @@ -27,7 +27,7 @@ defmodule BlockScoutWeb.API.V2.StatsController do :standard end - exchange_rate_from_db = Market.get_native_coin_exchange_rate_from_db() + exchange_rate_from_db = Market.get_coin_exchange_rate() transaction_stats = Helper.get_transaction_stats() diff --git a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex index 7ffe82536881..b3497d8b4c75 100644 --- a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex +++ b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex @@ -10,6 +10,7 @@ defmodule Explorer.ExchangeRates do require Logger alias Explorer.Chain.Events.Publisher + alias Explorer.Market alias Explorer.ExchangeRates.{Source, Token} @interval Application.compile_env(:explorer, __MODULE__)[:cache_period] @@ -123,10 +124,29 @@ defmodule Explorer.ExchangeRates do @spec fetch_rates :: Task.t() defp fetch_rates do Task.Supervisor.async_nolink(Explorer.MarketTaskSupervisor, fn -> - Source.fetch_exchange_rates() + case Source.fetch_exchange_rates() do + {:ok, tokens} -> {:ok, add_coin_info_from_db(tokens)} + err -> err + end end) end + defp add_coin_info_from_db(tokens) do + case Market.fetch_recent_history() do + [today | _the_rest] -> + tvl_from_history = Map.get(today, :tvl) + + tokens + |> Enum.map(fn + %Token{tvl_usd: nil} = token -> %{token | tvl_usd: tvl_from_history} + token -> token + end) + + _ -> + tokens + end + end + defp list_from_store(:ets) do table_name() |> :ets.tab2list() diff --git a/apps/explorer/lib/explorer/market/market_history.ex b/apps/explorer/lib/explorer/market/market_history.ex index 8c1411879ee3..ea27a083aa48 100644 --- a/apps/explorer/lib/explorer/market/market_history.ex +++ b/apps/explorer/lib/explorer/market/market_history.ex @@ -20,7 +20,7 @@ defmodule Explorer.Market.MarketHistory do * `:date` - The date in UTC. * `:opening_price` - Opening price in USD. * `:market_cap` - Market cap in USD. - * `:market_cap` - TVL in USD. + * `:tvl` - TVL in USD. """ @type t :: %__MODULE__{ closing_price: Decimal.t() | nil, diff --git a/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs b/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs index 953a5b5a1640..25faf397b422 100644 --- a/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs +++ b/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs @@ -1,5 +1,5 @@ defmodule Explorer.ExchangeRatesTest do - use ExUnit.Case, async: false + use Explorer.DataCase import Mox @@ -7,12 +7,16 @@ defmodule Explorer.ExchangeRatesTest do alias Explorer.ExchangeRates alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Source.TestSource + alias Explorer.Market.MarketHistoryCache @moduletag :capture_log setup :verify_on_exit! setup do + Supervisor.terminate_child(Explorer.Supervisor, {ConCache, MarketHistoryCache.cache_name()}) + Supervisor.restart_child(Explorer.Supervisor, {ConCache, MarketHistoryCache.cache_name()}) + # Use TestSource mock and ets table for this test set source_configuration = Application.get_env(:explorer, Explorer.ExchangeRates.Source) rates_configuration = Application.get_env(:explorer, Explorer.ExchangeRates) @@ -24,6 +28,8 @@ defmodule Explorer.ExchangeRatesTest do on_exit(fn -> Application.put_env(:explorer, Explorer.ExchangeRates.Source, source_configuration) Application.put_env(:explorer, Explorer.ExchangeRates, rates_configuration) + Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id()) + Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id()) end) end From 4c2c9e32bed801a98a53bc4bfa0851ad761515cf Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Tue, 26 Dec 2023 16:43:26 +0600 Subject: [PATCH 264/607] Add tracing by block logic for geth --- CHANGELOG.md | 2 + .../lib/ethereum_jsonrpc/geth.ex | 110 +++++++++--- .../test/ethereum_jsonrpc/geth_test.exs | 168 +++++++++++++++++- .../indexer/fetcher/internal_transaction.ex | 41 +++-- config/runtime.exs | 1 + docker-compose/envs/common-blockscout.env | 1 + 6 files changed, 286 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8672e81278ff..78b3e4a19cfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Features +- [#9072](https://github.com/blockscout/blockscout/pull/9072) - Add tracing by block logic for geth + ### Fixes - [#9109](https://github.com/blockscout/blockscout/pull/9109) - Return current exchange rate in api/v2/stats diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex index d38fb0716a72..96379d75bb5c 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex @@ -63,12 +63,65 @@ defmodule EthereumJSONRPC.Geth do def fetch_first_trace(_transactions_params, _json_rpc_named_arguments), do: :ignore @doc """ - Internal transaction fetching for entire blocks is not currently supported for Geth. - - To signal to the caller that fetching is not supported, `:ignore` is returned. + Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Geth trace URL. """ @impl EthereumJSONRPC.Variant - def fetch_block_internal_transactions(_block_range, _json_rpc_named_arguments), do: :ignore + def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) do + id_to_params = id_to_params(block_numbers) + + with {:ok, blocks_responses} <- + id_to_params + |> debug_trace_block_by_number_requests() + |> json_rpc(json_rpc_named_arguments), + :ok <- check_errors_exist(blocks_responses, id_to_params) do + transactions_params = to_transactions_params(blocks_responses, id_to_params) + + {transactions_id_to_params, transactions_responses} = + Enum.reduce(transactions_params, {%{}, []}, fn {params, calls}, {id_to_params_acc, calls_acc} -> + {Map.put(id_to_params_acc, params[:id], params), [calls | calls_acc]} + end) + + debug_trace_transaction_responses_to_internal_transactions_params( + transactions_responses, + transactions_id_to_params, + json_rpc_named_arguments + ) + end + end + + defp check_errors_exist(blocks_responses, id_to_params) do + blocks_responses + |> EthereumJSONRPC.sanitize_responses(id_to_params) + |> Enum.reduce([], fn + %{result: _result}, acc -> acc + %{error: error}, acc -> [error | acc] + end) + |> case do + [] -> :ok + errors -> {:error, errors} + end + end + + defp to_transactions_params(blocks_responses, id_to_params) do + Enum.reduce(blocks_responses, [], fn %{id: id, result: tx_result}, blocks_acc -> + extract_transactions_params(Map.fetch!(id_to_params, id), tx_result) ++ blocks_acc + end) + end + + defp extract_transactions_params(block_number, tx_result) do + tx_result + |> Enum.reduce({[], 0}, fn %{"txHash" => tx_hash, "result" => calls_result}, {tx_acc, counter} -> + { + [ + {%{block_number: block_number, hash_data: tx_hash, transaction_index: counter, id: counter}, + %{id: counter, result: calls_result}} + | tx_acc + ], + counter + 1 + } + end) + |> elem(0) + end @doc """ Fetches the pending transactions from the Geth node. @@ -84,6 +137,10 @@ defmodule EthereumJSONRPC.Geth do end) end + defp debug_trace_block_by_number_requests(id_to_params) do + Enum.map(id_to_params, &debug_trace_block_by_number_request/1) + end + @tracer_path "priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js" @external_resource @tracer_path @tracer File.read!(@tracer_path) @@ -92,30 +149,39 @@ defmodule EthereumJSONRPC.Geth do debug_trace_transaction_timeout = Application.get_env(:ethereum_jsonrpc, __MODULE__)[:debug_trace_transaction_timeout] - tracer = - cond do - tracer_type() == "js" -> - %{"tracer" => @tracer} - - tracer_type() in ~w(opcode polygon_edge) -> - %{ - "enableMemory" => true, - "disableStack" => false, - "disableStorage" => true, - "enableReturnData" => false - } - - true -> - %{"tracer" => "callTracer"} - end - request(%{ id: id, method: "debug_traceTransaction", - params: [hash_data, %{timeout: debug_trace_transaction_timeout} |> Map.merge(tracer)] + params: [hash_data, %{timeout: debug_trace_transaction_timeout} |> Map.merge(tracer_params())] + }) + end + + defp debug_trace_block_by_number_request({id, block_number}) do + request(%{ + id: id, + method: "debug_traceBlockByNumber", + params: [integer_to_quantity(block_number), tracer_params()] }) end + defp tracer_params do + cond do + tracer_type() == "js" -> + %{"tracer" => @tracer} + + tracer_type() in ~w(opcode polygon_edge) -> + %{ + "enableMemory" => true, + "disableStack" => false, + "disableStorage" => true, + "enableReturnData" => false + } + + true -> + %{"tracer" => "callTracer"} + end + end + defp debug_trace_transaction_responses_to_internal_transactions_params( [%{result: %{"structLogs" => _}} | _] = responses, id_to_params, diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs index a379ac57a816..cc22f8baf483 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs @@ -555,8 +555,172 @@ defmodule EthereumJSONRPC.GethTest do end describe "fetch_block_internal_transactions/1" do - test "is not supported", %{json_rpc_named_arguments: json_rpc_named_arguments} do - EthereumJSONRPC.Geth.fetch_block_internal_transactions([], json_rpc_named_arguments) + setup do + EthereumJSONRPC.Case.Geth.Mox.setup() + end + + test "is supported", %{json_rpc_named_arguments: json_rpc_named_arguments} do + block_number = 3_287_375 + block_quantity = EthereumJSONRPC.integer_to_quantity(block_number) + transaction_hash = "0x32b17f27ddb546eab3c4c33f31eb22c1cb992d4ccc50dae26922805b717efe5c" + + expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id, params: [^block_quantity, %{"tracer" => "callTracer"}]}], + _ -> + {:ok, + [ + %{ + id: id, + result: [ + %{ + "result" => %{ + "calls" => [ + %{ + "from" => "0x4200000000000000000000000000000000000015", + "gas" => "0xe9a3c", + "gasUsed" => "0x4a28", + "input" => + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + "to" => "0x6df83a19647a398d48e77a6835f4a28eb7e2f7c0", + "type" => "DELEGATECALL", + "value" => "0x0" + } + ], + "from" => "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001", + "gas" => "0xf4240", + "gasUsed" => "0xb6f9", + "input" => + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + "to" => "0x4200000000000000000000000000000000000015", + "type" => "CALL", + "value" => "0x0" + }, + "txHash" => transaction_hash + } + ] + } + ]} + end) + + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + + assert {:ok, + [ + %{ + block_number: 3_287_375, + call_type: "call", + from_address_hash: "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001", + gas: 1_000_000, + gas_used: 46841, + index: 0, + input: + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + output: "0x", + to_address_hash: "0x4200000000000000000000000000000000000015", + trace_address: [], + transaction_hash: ^transaction_hash, + transaction_index: 0, + type: "call", + value: 0 + }, + %{ + block_number: 3_287_375, + call_type: "delegatecall", + from_address_hash: "0x4200000000000000000000000000000000000015", + gas: 956_988, + gas_used: 18984, + index: 1, + input: + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + output: "0x", + to_address_hash: "0x6df83a19647a398d48e77a6835f4a28eb7e2f7c0", + trace_address: [0], + transaction_hash: ^transaction_hash, + transaction_index: 0, + type: "call", + value: 0 + } + ]} = Geth.fetch_block_internal_transactions([block_number], json_rpc_named_arguments) + end + + test "result is the same as fetch_internal_transactions/2", %{json_rpc_named_arguments: json_rpc_named_arguments} do + block_number = 3_287_375 + block_quantity = EthereumJSONRPC.integer_to_quantity(block_number) + transaction_hash = "0x32b17f27ddb546eab3c4c33f31eb22c1cb992d4ccc50dae26922805b717efe5c" + + expect(EthereumJSONRPC.Mox, :json_rpc, 2, fn + [%{id: id, params: [^block_quantity, %{"tracer" => "callTracer"}]}], _ -> + {:ok, + [ + %{ + id: id, + result: [ + %{ + "result" => %{ + "calls" => [ + %{ + "from" => "0x4200000000000000000000000000000000000015", + "gas" => "0xe9a3c", + "gasUsed" => "0x4a28", + "input" => + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + "to" => "0x6df83a19647a398d48e77a6835f4a28eb7e2f7c0", + "type" => "DELEGATECALL", + "value" => "0x0" + } + ], + "from" => "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001", + "gas" => "0xf4240", + "gasUsed" => "0xb6f9", + "input" => + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + "to" => "0x4200000000000000000000000000000000000015", + "type" => "CALL", + "value" => "0x0" + }, + "txHash" => transaction_hash + } + ] + } + ]} + + [%{id: id, params: [^transaction_hash, %{"tracer" => "callTracer"}]}], _ -> + {:ok, + [ + %{ + id: id, + result: %{ + "calls" => [ + %{ + "from" => "0x4200000000000000000000000000000000000015", + "gas" => "0xe9a3c", + "gasUsed" => "0x4a28", + "input" => + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + "to" => "0x6df83a19647a398d48e77a6835f4a28eb7e2f7c0", + "type" => "DELEGATECALL", + "value" => "0x0" + } + ], + "from" => "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001", + "gas" => "0xf4240", + "gasUsed" => "0xb6f9", + "input" => + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + "to" => "0x4200000000000000000000000000000000000015", + "type" => "CALL", + "value" => "0x0" + } + } + ]} + end) + + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + + assert Geth.fetch_block_internal_transactions([block_number], json_rpc_named_arguments) == + Geth.fetch_internal_transactions( + [%{block_number: block_number, transaction_index: 0, hash_data: transaction_hash}], + json_rpc_named_arguments + ) end end diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex index 1861018b3324..d5d1af69436a 100644 --- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex @@ -111,19 +111,7 @@ defmodule Indexer.Fetcher.InternalTransaction do json_rpc_named_arguments |> Keyword.fetch!(:variant) - |> case do - variant - when variant in [EthereumJSONRPC.Nethermind, EthereumJSONRPC.Erigon, EthereumJSONRPC.Besu, EthereumJSONRPC.RSK] -> - EthereumJSONRPC.fetch_block_internal_transactions(filtered_unique_numbers, json_rpc_named_arguments) - - _ -> - try do - fetch_block_internal_transactions_by_transactions(filtered_unique_numbers, json_rpc_named_arguments) - rescue - error -> - {:error, error, __STACKTRACE__} - end - end + |> fetch_internal_transactions(filtered_unique_numbers, json_rpc_named_arguments) |> case do {:ok, internal_transactions_params} -> safe_import_internal_transaction(internal_transactions_params, filtered_unique_numbers) @@ -159,6 +147,33 @@ defmodule Indexer.Fetcher.InternalTransaction do end end + defp fetch_internal_transactions(variant, block_numbers, json_rpc_named_arguments) do + if variant in block_traceable_variants() do + EthereumJSONRPC.fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) + else + try do + fetch_block_internal_transactions_by_transactions(block_numbers, json_rpc_named_arguments) + rescue + error -> + {:error, error, __STACKTRACE__} + end + end + end + + @default_block_traceable_variants [ + EthereumJSONRPC.Nethermind, + EthereumJSONRPC.Erigon, + EthereumJSONRPC.Besu, + EthereumJSONRPC.RSK + ] + defp block_traceable_variants do + if Application.get_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth)[:block_traceable?] do + [EthereumJSONRPC.Geth | @default_block_traceable_variants] + else + @default_block_traceable_variants + end + end + defp drop_genesis(block_numbers, json_rpc_named_arguments) do first_block = Application.get_env(:indexer, :trace_first_block) diff --git a/config/runtime.exs b/config/runtime.exs index e8f4de449691..2cd105ac72e2 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -162,6 +162,7 @@ config :ethereum_jsonrpc, EthereumJSONRPC.HTTP, |> Map.to_list() config :ethereum_jsonrpc, EthereumJSONRPC.Geth, + block_traceable?: ConfigHelper.parse_bool_env_var("ETHEREUM_JSONRPC_GETH_TRACE_BY_BLOCK"), debug_trace_transaction_timeout: System.get_env("ETHEREUM_JSONRPC_DEBUG_TRACE_TRANSACTION_TIMEOUT", "5s"), tracer: if(ConfigHelper.chain_type() == "polygon_edge", diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index f1743f72265e..511cd2ebfcec 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -18,6 +18,7 @@ ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES=false #ETHEREUM_JSONRPC_ARCHIVE_BALANCES_WINDOW=200 # ETHEREUM_JSONRPC_HTTP_HEADERS= # ETHEREUM_JSONRPC_WAIT_PER_TIMEOUT= +# ETHEREUM_JSONRPC_GETH_TRACE_BY_BLOCK= IPC_PATH= NETWORK_PATH=/ BLOCKSCOUT_HOST= From d8cc916821415caed2614471ce5a062773356d2b Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Tue, 9 Jan 2024 14:17:15 +0300 Subject: [PATCH 265/607] Fix Explorer.Chain.Cache.GasPriceOracle.merge_fees --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8672e81278ff..5591355e0b04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Fixes +- [#9125](https://github.com/blockscout/blockscout/pull/9125) - Fix Explorer.Chain.Cache.GasPriceOracle.merge_fees - [#9109](https://github.com/blockscout/blockscout/pull/9109) - Return current exchange rate in api/v2/stats - [#9102](https://github.com/blockscout/blockscout/pull/9102) - Fix some log topics for Suave and Polygon Edge - [#9075](https://github.com/blockscout/blockscout/pull/9075) - Fix fetching contract codes diff --git a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex index d6d85c2b42f2..a37eec21dd25 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex @@ -287,9 +287,9 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do |> Enum.reduce( &Map.merge(&1, &2, fn _, nil, nil -> nil - _, val, acc when nil not in [val, acc] and is_list(acc) -> [val | acc] - _, val, acc when nil not in [val, acc] -> [val, acc] - _, val, acc -> [val || acc] + _, val, nil -> [val] + _, nil, acc -> if is_list(acc), do: acc, else: [acc] + _, val, acc -> if is_list(acc), do: [val | acc], else: [val, acc] end) ) |> Map.new(fn From 0784151ba6b79346ed3db7077b2bbcd78edffce3 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Mon, 8 Jan 2024 00:48:41 +0300 Subject: [PATCH 266/607] Improve update_in in gas tracker --- CHANGELOG.md | 1 + .../controllers/api/v2/stats_controller.ex | 2 +- .../explorer/chain/cache/gas_price_oracle.ex | 25 ++++++++++++++++--- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5591355e0b04..caec35e1027c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Fixes - [#9125](https://github.com/blockscout/blockscout/pull/9125) - Fix Explorer.Chain.Cache.GasPriceOracle.merge_fees +- [#9110](https://github.com/blockscout/blockscout/pull/9110) - Improve update_in in gas tracker - [#9109](https://github.com/blockscout/blockscout/pull/9109) - Return current exchange rate in api/v2/stats - [#9102](https://github.com/blockscout/blockscout/pull/9102) - Fix some log topics for Suave and Polygon Edge - [#9075](https://github.com/blockscout/blockscout/pull/9075) - Fix fetching contract codes diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex index 7e78533fba9a..451275586b28 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex @@ -70,7 +70,7 @@ defmodule BlockScoutWeb.API.V2.StatsController do "transactions_today" => Enum.at(transaction_stats, 0).number_of_transactions |> to_string(), "gas_used_today" => Enum.at(transaction_stats, 0).gas_used, "gas_prices" => gas_prices, - "gas_prices_update_in" => GasPriceOracle.global_ttl(), + "gas_prices_update_in" => GasPriceOracle.update_in(), "gas_price_updated_at" => GasPriceOracle.get_updated_at(), "static_gas_price" => gas_price, "market_cap" => Helper.market_cap(market_cap_type, exchange_rate_from_db), diff --git a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex index a37eec21dd25..d09ce6d41cb0 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex @@ -29,11 +29,29 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do key: :gas_prices_acc, key: :updated_at, key: :old_gas_prices, + key: :old_updated_at, key: :async_task, - global_ttl: global_ttl(), + global_ttl: :infinity, ttl_check_interval: :timer.seconds(1), callback: &async_task_on_deletion(&1) + @doc """ + Calculates how much time left till the next gas prices updated taking into account estimated query running time. + """ + @spec update_in :: non_neg_integer() + def update_in do + case {get_old_updated_at(), get_updated_at()} do + {%DateTime{} = old_updated_at, %DateTime{} = updated_at} -> + time_to_update = DateTime.diff(updated_at, old_updated_at, :millisecond) + 500 + time_since_last_update = DateTime.diff(DateTime.utc_now(), updated_at, :millisecond) + next_update_in = time_to_update - time_since_last_update + if next_update_in <= 0, do: global_ttl(), else: next_update_in + + _ -> + global_ttl() + :timer.seconds(2) + end + end + @doc """ Calculates the `slow`, `average`, and `fast` gas price and time percentiles from the last `num_of_blocks` blocks and estimates the fiat price for each percentile. These percentiles correspond to the likelihood of a transaction being picked up by miners depending on the fee offered. @@ -330,7 +348,7 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do defp format_wei(wei), do: wei |> Wei.to(:gwei) |> Decimal.to_float() |> Float.ceil(2) - def global_ttl, do: Application.get_env(:explorer, __MODULE__)[:global_ttl] + defp global_ttl, do: Application.get_env(:explorer, __MODULE__)[:global_ttl] defp simple_transaction_gas, do: Application.get_env(:explorer, __MODULE__)[:simple_transaction_gas] @@ -359,7 +377,8 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do {result, acc} = get_average_gas_price(num_of_blocks(), safelow(), average(), fast()) set_gas_prices_acc(acc) - set_gas_prices(result) + set_gas_prices(%ConCache.Item{ttl: global_ttl(), value: result}) + set_old_updated_at(get_updated_at()) set_updated_at(DateTime.utc_now()) rescue e -> From d3d7435df14fd9ed7c8c0d7bc39e7b1e1872ff6e Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Sat, 30 Dec 2023 22:24:26 +0300 Subject: [PATCH 267/607] Return additional sources in case of bytecode twin of EIP-1167 pattern --- CHANGELOG.md | 1 + .../address_contract_controller.ex | 2 +- .../api/v2/smart_contract_controller.ex | 2 +- .../controllers/smart_contract_controller.ex | 2 +- .../visualize_sol2uml_controller.ex | 2 +- .../templates/address_contract/index.html.eex | 3 +- .../views/api/rpc/contract_view.ex | 2 +- .../views/api/v2/smart_contract_view.ex | 29 ++++++-- apps/block_scout_web/priv/gettext/default.pot | 68 +++++++++---------- .../priv/gettext/en/LC_MESSAGES/default.po | 68 +++++++++---------- apps/explorer/lib/explorer/chain.ex | 3 +- apps/explorer/lib/explorer/chain/address.ex | 8 +-- .../lib/explorer/chain/smart_contract.ex | 5 ++ .../chain/smart_contract/proxy/eip_1167.ex | 8 ++- .../smart_contract_additional_sources.ex | 4 +- .../lib/explorer/etherscan/contracts.ex | 5 +- .../explorer/chain/smart_contract_test.exs | 10 +-- apps/explorer/test/explorer/chain_test.exs | 2 +- 18 files changed, 127 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index caec35e1027c..302784ccb521 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Fixes - [#9125](https://github.com/blockscout/blockscout/pull/9125) - Fix Explorer.Chain.Cache.GasPriceOracle.merge_fees +- [#9124](https://github.com/blockscout/blockscout/pull/9124) - EIP-1167 display multiple sources of bytecode twin - [#9110](https://github.com/blockscout/blockscout/pull/9110) - Improve update_in in gas tracker - [#9109](https://github.com/blockscout/blockscout/pull/9109) - Return current exchange rate in api/v2/stats - [#9102](https://github.com/blockscout/blockscout/pull/9102) - Fix some log topics for Suave and Polygon Edge diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex index 445b7ca48679..a43c5c7b2b1c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex @@ -15,7 +15,7 @@ defmodule BlockScoutWeb.AddressContractController do necessity_by_association: %{ :contracts_creation_internal_transaction => :optional, :names => :optional, - :smart_contract => :optional, + [smart_contract: :smart_contract_additional_sources] => :optional, :token => :optional, :contracts_creation_transaction => :optional } diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex index 4986c3a19ad5..0407d0160289 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex @@ -20,7 +20,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do @smart_contract_address_options [ necessity_by_association: %{ :contracts_creation_internal_transaction => :optional, - :smart_contract => :optional, + [smart_contract: :smart_contract_additional_sources] => :optional, :contracts_creation_transaction => :optional }, api?: true diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex index acaee3f7bd4d..398a90f022fb 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex @@ -12,7 +12,7 @@ defmodule BlockScoutWeb.SmartContractController do def index(conn, %{"hash" => address_hash_string, "type" => contract_type, "action" => action} = params) do address_options = [ necessity_by_association: %{ - :smart_contract => :optional + [smart_contract: :smart_contract_additional_sources] => :optional } ] diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/visualize_sol2uml_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/visualize_sol2uml_controller.ex index 3863fb31a8c6..78c4d225f381 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/visualize_sol2uml_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/visualize_sol2uml_controller.ex @@ -16,7 +16,7 @@ defmodule BlockScoutWeb.VisualizeSol2umlController do # check that contract is verified. partial and twin verification is ok for this case false <- is_nil(address.smart_contract) do sources = - address.smart_contract_additional_sources + address.smart_contract.smart_contract_additional_sources |> Enum.map(fn additional_source -> {additional_source.file_name, additional_source.contract_source_code} end) |> Enum.into(%{}) |> Map.merge(%{ diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex index 1d6776ecf04d..4a052b2fa539 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex @@ -2,9 +2,8 @@ <% minimal_proxy_template = EIP1167.get_implementation_address(@address.hash) %> <% metadata_for_verification = minimal_proxy_template || SmartContract.get_address_verified_twin_contract(@address.hash).verified_contract %> <% smart_contract_verified = BlockScoutWeb.AddressView.smart_contract_verified?(@address) %> -<% additional_sources_from_twin = SmartContract.get_address_verified_twin_contract(@address.hash).additional_sources %> <% fully_verified = SmartContract.verified_with_full_match?(@address.hash)%> -<% additional_sources = if smart_contract_verified, do: @address.smart_contract_additional_sources, else: additional_sources_from_twin %> +<% additional_sources = BlockScoutWeb.API.V2.SmartContractView.additional_sources(@address.smart_contract, smart_contract_verified, minimal_proxy_template, SmartContract.get_address_verified_twin_contract(@address.hash)) %> <% visualize_sol2uml_enabled = Explorer.Visualize.Sol2uml.enabled?() %>
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex index bc47f2bd85cc..5297bf50d1aa 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex @@ -171,7 +171,7 @@ defmodule BlockScoutWeb.API.RPC.ContractView do additional_sources = if AddressView.smart_contract_verified?(address), - do: address.smart_contract_additional_sources, + do: address.smart_contract.smart_contract_additional_sources, else: additional_sources_from_twin additional_sources_array = diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex index 868536acfbbe..549ca3249708 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex @@ -10,7 +10,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do alias BlockScoutWeb.{ABIEncodedValueView, AddressContractView, AddressView} alias Ecto.Changeset alias Explorer.Chain - alias Explorer.Chain.{Address, SmartContract} + alias Explorer.Chain.{Address, SmartContract, SmartContractAdditionalSource} alias Explorer.Chain.SmartContract.Proxy.EIP1167 alias Explorer.Visualize.Sol2uml @@ -136,14 +136,13 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do # credo:disable-for-next-line def prepare_smart_contract(%Address{smart_contract: %SmartContract{} = smart_contract} = address) do minimal_proxy_template = EIP1167.get_implementation_address(address.hash, @api_true) - twin = SmartContract.get_address_verified_twin_contract(address.hash, @api_true) - metadata_for_verification = minimal_proxy_template || twin.verified_contract + bytecode_twin = SmartContract.get_address_verified_twin_contract(address.hash, @api_true) + metadata_for_verification = minimal_proxy_template || bytecode_twin.verified_contract smart_contract_verified = AddressView.smart_contract_verified?(address) - additional_sources_from_twin = twin.additional_sources fully_verified = SmartContract.verified_with_full_match?(address.hash, @api_true) additional_sources = - if smart_contract_verified, do: address.smart_contract_additional_sources, else: additional_sources_from_twin + additional_sources(smart_contract, smart_contract_verified, minimal_proxy_template, bytecode_twin) visualize_sol2uml_enabled = Sol2uml.enabled?() target_contract = if smart_contract_verified, do: address.smart_contract, else: metadata_for_verification @@ -192,6 +191,26 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do bytecode_info(address) end + @doc """ + Returns additional sources of the smart-contract or from bytecode twin or from implementation, if it fits minimal proxy pattern (EIP-1167) + """ + @spec additional_sources(SmartContract.t(), boolean, SmartContract.t() | nil, %{ + :verified_contract => any(), + :additional_sources => SmartContractAdditionalSource.t() | nil + }) :: [SmartContractAdditionalSource.t()] + def additional_sources(smart_contract, smart_contract_verified, minimal_proxy_template, bytecode_twin) do + cond do + !is_nil(minimal_proxy_template) -> + minimal_proxy_template.smart_contract_additional_sources + + smart_contract_verified -> + smart_contract.smart_contract_additional_sources + + true -> + bytecode_twin.additional_sources + end + end + defp bytecode_info(address) do case AddressContractView.contract_creation_code(address) do {:selfdestructed, init} -> diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 224aa1df7789..38e44c6d5c9d 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -336,7 +336,7 @@ msgstr "" msgid "All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:28 +#: lib/block_scout_web/templates/address_contract/index.html.eex:27 #, elixir-autogen, elixir-format msgid "All metadata displayed below is from that contract. In order to verify current contract, click" msgstr "" @@ -688,12 +688,12 @@ msgstr "" msgid "Compiler" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:143 +#: lib/block_scout_web/templates/address_contract/index.html.eex:142 #, elixir-autogen, elixir-format msgid "Compiler Settings" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:72 +#: lib/block_scout_web/templates/address_contract/index.html.eex:71 #, elixir-autogen, elixir-format msgid "Compiler version" msgstr "" @@ -747,7 +747,7 @@ msgstr "" msgid "Connection Lost, click to load newer validations" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:97 +#: lib/block_scout_web/templates/address_contract/index.html.eex:96 #, elixir-autogen, elixir-format msgid "Constructor Arguments" msgstr "" @@ -763,7 +763,7 @@ msgstr "" msgid "Contract" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:158 +#: lib/block_scout_web/templates/address_contract/index.html.eex:157 #, elixir-autogen, elixir-format msgid "Contract ABI" msgstr "" @@ -793,8 +793,8 @@ msgstr "" msgid "Contract Creation" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:175 -#: lib/block_scout_web/templates/address_contract/index.html.eex:190 +#: lib/block_scout_web/templates/address_contract/index.html.eex:174 +#: lib/block_scout_web/templates/address_contract/index.html.eex:189 #, elixir-autogen, elixir-format msgid "Contract Creation Code" msgstr "" @@ -811,7 +811,7 @@ msgstr "" msgid "Contract Name" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:26 +#: lib/block_scout_web/templates/address_contract/index.html.eex:25 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:11 #, elixir-autogen, elixir-format msgid "Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB" @@ -822,12 +822,12 @@ msgstr "" msgid "Contract name or address" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:64 +#: lib/block_scout_web/templates/address_contract/index.html.eex:63 #, elixir-autogen, elixir-format msgid "Contract name:" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:107 +#: lib/block_scout_web/templates/address_contract/index.html.eex:106 #, elixir-autogen, elixir-format msgid "Contract source code" msgstr "" @@ -842,7 +842,7 @@ msgstr "" msgid "Contracts" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:181 +#: lib/block_scout_web/templates/address_contract/index.html.eex:180 #, elixir-autogen, elixir-format msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified." msgstr "" @@ -852,7 +852,7 @@ msgstr "" msgid "Contribute" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:160 +#: lib/block_scout_web/templates/address_contract/index.html.eex:159 #, elixir-autogen, elixir-format msgid "Copy ABI" msgstr "" @@ -878,7 +878,7 @@ msgstr "" msgid "Copy Address" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:145 +#: lib/block_scout_web/templates/address_contract/index.html.eex:144 #, elixir-autogen, elixir-format msgid "Copy Compiler Settings" msgstr "" @@ -889,8 +889,8 @@ msgstr "" msgid "Copy Contract Address" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:177 -#: lib/block_scout_web/templates/address_contract/index.html.eex:193 +#: lib/block_scout_web/templates/address_contract/index.html.eex:176 +#: lib/block_scout_web/templates/address_contract/index.html.eex:192 #, elixir-autogen, elixir-format msgid "Copy Contract Creation Code" msgstr "" @@ -900,8 +900,8 @@ msgstr "" msgid "Copy Decompiled Contract Code" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:214 -#: lib/block_scout_web/templates/address_contract/index.html.eex:224 +#: lib/block_scout_web/templates/address_contract/index.html.eex:213 +#: lib/block_scout_web/templates/address_contract/index.html.eex:223 #, elixir-autogen, elixir-format msgid "Copy Deployed ByteCode" msgstr "" @@ -937,8 +937,8 @@ msgstr "" msgid "Copy Raw Trace" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:121 -#: lib/block_scout_web/templates/address_contract/index.html.eex:133 +#: lib/block_scout_web/templates/address_contract/index.html.eex:120 +#: lib/block_scout_web/templates/address_contract/index.html.eex:132 #, elixir-autogen, elixir-format msgid "Copy Source Code" msgstr "" @@ -1109,8 +1109,8 @@ msgstr "" msgid "Delegate Call" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:212 -#: lib/block_scout_web/templates/address_contract/index.html.eex:220 +#: lib/block_scout_web/templates/address_contract/index.html.eex:211 +#: lib/block_scout_web/templates/address_contract/index.html.eex:219 #, elixir-autogen, elixir-format msgid "Deployed ByteCode" msgstr "" @@ -1140,7 +1140,7 @@ msgstr "" msgid "Difficulty" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:182 +#: lib/block_scout_web/templates/address_contract/index.html.eex:181 #, elixir-autogen, elixir-format msgid "Displaying the init data provided of the creating transaction." msgstr "" @@ -1216,7 +1216,7 @@ msgstr "" msgid "ETH RPC API Documentation" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:83 +#: lib/block_scout_web/templates/address_contract/index.html.eex:82 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:30 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:22 #, elixir-autogen, elixir-format @@ -1334,7 +1334,7 @@ msgstr "" msgid "Export Data" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:249 +#: lib/block_scout_web/templates/address_contract/index.html.eex:248 #, elixir-autogen, elixir-format msgid "External libraries" msgstr "" @@ -2002,12 +2002,12 @@ msgstr "" msgid "Optimization" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:68 +#: lib/block_scout_web/templates/address_contract/index.html.eex:67 #, elixir-autogen, elixir-format msgid "Optimization enabled" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:77 +#: lib/block_scout_web/templates/address_contract/index.html.eex:76 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:62 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:54 #, elixir-autogen, elixir-format @@ -2776,12 +2776,12 @@ msgstr "" msgid "This block has not been processed yet." msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:48 +#: lib/block_scout_web/templates/address_contract/index.html.eex:47 #, elixir-autogen, elixir-format msgid "This contract has been partially verified via Sourcify." msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:52 +#: lib/block_scout_web/templates/address_contract/index.html.eex:51 #, elixir-autogen, elixir-format msgid "This contract has been verified via Sourcify." msgstr "" @@ -3268,7 +3268,7 @@ msgstr "" msgid "Verified Contracts" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:89 +#: lib/block_scout_web/templates/address_contract/index.html.eex:88 #, elixir-autogen, elixir-format msgid "Verified at" msgstr "" @@ -3288,10 +3288,10 @@ msgstr "" msgid "Verified contracts, %{subnetwork}, %{coin}" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:28 -#: lib/block_scout_web/templates/address_contract/index.html.eex:30 -#: lib/block_scout_web/templates/address_contract/index.html.eex:198 -#: lib/block_scout_web/templates/address_contract/index.html.eex:229 +#: lib/block_scout_web/templates/address_contract/index.html.eex:27 +#: lib/block_scout_web/templates/address_contract/index.html.eex:29 +#: lib/block_scout_web/templates/address_contract/index.html.eex:197 +#: lib/block_scout_web/templates/address_contract/index.html.eex:228 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:14 #, elixir-autogen, elixir-format msgid "Verify & Publish" @@ -3541,7 +3541,7 @@ msgstr "" msgid "balance of the address" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:28 +#: lib/block_scout_web/templates/address_contract/index.html.eex:27 #, elixir-autogen, elixir-format msgid "button" msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 4d1362f4d282..328f17c0900d 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -336,7 +336,7 @@ msgstr "" msgid "All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:28 +#: lib/block_scout_web/templates/address_contract/index.html.eex:27 #, elixir-autogen, elixir-format msgid "All metadata displayed below is from that contract. In order to verify current contract, click" msgstr "" @@ -688,12 +688,12 @@ msgstr "" msgid "Compiler" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:143 +#: lib/block_scout_web/templates/address_contract/index.html.eex:142 #, elixir-autogen, elixir-format msgid "Compiler Settings" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:72 +#: lib/block_scout_web/templates/address_contract/index.html.eex:71 #, elixir-autogen, elixir-format msgid "Compiler version" msgstr "" @@ -747,7 +747,7 @@ msgstr "" msgid "Connection Lost, click to load newer validations" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:97 +#: lib/block_scout_web/templates/address_contract/index.html.eex:96 #, elixir-autogen, elixir-format msgid "Constructor Arguments" msgstr "" @@ -763,7 +763,7 @@ msgstr "" msgid "Contract" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:158 +#: lib/block_scout_web/templates/address_contract/index.html.eex:157 #, elixir-autogen, elixir-format msgid "Contract ABI" msgstr "" @@ -793,8 +793,8 @@ msgstr "" msgid "Contract Creation" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:175 -#: lib/block_scout_web/templates/address_contract/index.html.eex:190 +#: lib/block_scout_web/templates/address_contract/index.html.eex:174 +#: lib/block_scout_web/templates/address_contract/index.html.eex:189 #, elixir-autogen, elixir-format msgid "Contract Creation Code" msgstr "" @@ -811,7 +811,7 @@ msgstr "" msgid "Contract Name" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:26 +#: lib/block_scout_web/templates/address_contract/index.html.eex:25 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:11 #, elixir-autogen, elixir-format msgid "Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB" @@ -822,12 +822,12 @@ msgstr "" msgid "Contract name or address" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:64 +#: lib/block_scout_web/templates/address_contract/index.html.eex:63 #, elixir-autogen, elixir-format msgid "Contract name:" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:107 +#: lib/block_scout_web/templates/address_contract/index.html.eex:106 #, elixir-autogen, elixir-format msgid "Contract source code" msgstr "" @@ -842,7 +842,7 @@ msgstr "" msgid "Contracts" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:181 +#: lib/block_scout_web/templates/address_contract/index.html.eex:180 #, elixir-autogen, elixir-format msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified." msgstr "" @@ -852,7 +852,7 @@ msgstr "" msgid "Contribute" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:160 +#: lib/block_scout_web/templates/address_contract/index.html.eex:159 #, elixir-autogen, elixir-format msgid "Copy ABI" msgstr "" @@ -878,7 +878,7 @@ msgstr "" msgid "Copy Address" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:145 +#: lib/block_scout_web/templates/address_contract/index.html.eex:144 #, elixir-autogen, elixir-format msgid "Copy Compiler Settings" msgstr "" @@ -889,8 +889,8 @@ msgstr "" msgid "Copy Contract Address" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:177 -#: lib/block_scout_web/templates/address_contract/index.html.eex:193 +#: lib/block_scout_web/templates/address_contract/index.html.eex:176 +#: lib/block_scout_web/templates/address_contract/index.html.eex:192 #, elixir-autogen, elixir-format msgid "Copy Contract Creation Code" msgstr "" @@ -900,8 +900,8 @@ msgstr "" msgid "Copy Decompiled Contract Code" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:214 -#: lib/block_scout_web/templates/address_contract/index.html.eex:224 +#: lib/block_scout_web/templates/address_contract/index.html.eex:213 +#: lib/block_scout_web/templates/address_contract/index.html.eex:223 #, elixir-autogen, elixir-format msgid "Copy Deployed ByteCode" msgstr "" @@ -937,8 +937,8 @@ msgstr "" msgid "Copy Raw Trace" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:121 -#: lib/block_scout_web/templates/address_contract/index.html.eex:133 +#: lib/block_scout_web/templates/address_contract/index.html.eex:120 +#: lib/block_scout_web/templates/address_contract/index.html.eex:132 #, elixir-autogen, elixir-format msgid "Copy Source Code" msgstr "" @@ -1109,8 +1109,8 @@ msgstr "" msgid "Delegate Call" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:212 -#: lib/block_scout_web/templates/address_contract/index.html.eex:220 +#: lib/block_scout_web/templates/address_contract/index.html.eex:211 +#: lib/block_scout_web/templates/address_contract/index.html.eex:219 #, elixir-autogen, elixir-format msgid "Deployed ByteCode" msgstr "" @@ -1140,7 +1140,7 @@ msgstr "" msgid "Difficulty" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:182 +#: lib/block_scout_web/templates/address_contract/index.html.eex:181 #, elixir-autogen, elixir-format msgid "Displaying the init data provided of the creating transaction." msgstr "" @@ -1216,7 +1216,7 @@ msgstr "" msgid "ETH RPC API Documentation" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:83 +#: lib/block_scout_web/templates/address_contract/index.html.eex:82 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:30 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:22 #, elixir-autogen, elixir-format @@ -1334,7 +1334,7 @@ msgstr "" msgid "Export Data" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:249 +#: lib/block_scout_web/templates/address_contract/index.html.eex:248 #, elixir-autogen, elixir-format msgid "External libraries" msgstr "" @@ -2002,12 +2002,12 @@ msgstr "" msgid "Optimization" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:68 +#: lib/block_scout_web/templates/address_contract/index.html.eex:67 #, elixir-autogen, elixir-format msgid "Optimization enabled" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:77 +#: lib/block_scout_web/templates/address_contract/index.html.eex:76 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:62 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:54 #, elixir-autogen, elixir-format @@ -2776,12 +2776,12 @@ msgstr "" msgid "This block has not been processed yet." msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:48 +#: lib/block_scout_web/templates/address_contract/index.html.eex:47 #, elixir-autogen, elixir-format msgid "This contract has been partially verified via Sourcify." msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:52 +#: lib/block_scout_web/templates/address_contract/index.html.eex:51 #, elixir-autogen, elixir-format msgid "This contract has been verified via Sourcify." msgstr "" @@ -3268,7 +3268,7 @@ msgstr "" msgid "Verified Contracts" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:89 +#: lib/block_scout_web/templates/address_contract/index.html.eex:88 #, elixir-autogen, elixir-format msgid "Verified at" msgstr "" @@ -3288,10 +3288,10 @@ msgstr "" msgid "Verified contracts, %{subnetwork}, %{coin}" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:28 -#: lib/block_scout_web/templates/address_contract/index.html.eex:30 -#: lib/block_scout_web/templates/address_contract/index.html.eex:198 -#: lib/block_scout_web/templates/address_contract/index.html.eex:229 +#: lib/block_scout_web/templates/address_contract/index.html.eex:27 +#: lib/block_scout_web/templates/address_contract/index.html.eex:29 +#: lib/block_scout_web/templates/address_contract/index.html.eex:197 +#: lib/block_scout_web/templates/address_contract/index.html.eex:228 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:14 #, elixir-autogen, elixir-format msgid "Verify & Publish" @@ -3541,7 +3541,7 @@ msgstr "" msgid "balance of the address" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:28 +#: lib/block_scout_web/templates/address_contract/index.html.eex:27 #, elixir-autogen, elixir-format msgid "button" msgstr "" diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index e61f9cf6ca22..f5b9ea6f1a65 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1232,7 +1232,7 @@ defmodule Explorer.Chain do options |> Keyword.get(:necessity_by_association, %{}) |> Map.merge(%{ - smart_contract_additional_sources: :optional + [smart_contract: :smart_contract_additional_sources] => :optional }) query = @@ -1246,6 +1246,7 @@ defmodule Explorer.Chain do |> join_associations(necessity_by_association) |> with_decompiled_code_flag(hash, query_decompiled_code_flag) |> select_repo(options).one() + |> Repo.preload(smart_contract: :smart_contract_additional_sources) address_updated_result = case address_result do diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index 41bac5f23cf4..9390063859c3 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -19,7 +19,6 @@ defmodule Explorer.Chain.Address do Hash, InternalTransaction, SmartContract, - SmartContractAdditionalSource, Token, Transaction, Wei, @@ -77,8 +76,7 @@ defmodule Explorer.Chain.Address do :token, :contracts_creation_internal_transaction, :contracts_creation_transaction, - :names, - :smart_contract_additional_sources + :names ]} @derive {Jason.Encoder, @@ -89,8 +87,7 @@ defmodule Explorer.Chain.Address do :token, :contracts_creation_internal_transaction, :contracts_creation_transaction, - :names, - :smart_contract_additional_sources + :names ]} @primary_key {:hash, Hash.Address, autogenerate: false} @@ -125,7 +122,6 @@ defmodule Explorer.Chain.Address do has_many(:names, Address.Name, foreign_key: :address_hash) has_many(:decompiled_smart_contracts, DecompiledSmartContract, foreign_key: :address_hash) - has_many(:smart_contract_additional_sources, SmartContractAdditionalSource, foreign_key: :address_hash) has_many(:withdrawals, Withdrawal, foreign_key: :address_hash) timestamps() diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index 1d381ef92ec0..9a0005b2e017 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -312,6 +312,11 @@ defmodule Explorer.Chain.SmartContract do type: Hash.Address ) + has_many(:smart_contract_additional_sources, SmartContractAdditionalSource, + references: :address_hash, + foreign_key: :address_hash + ) + timestamps() end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex index 5095c6b17c5e..c5b000d5ab1e 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1167 do Module for fetching proxy implementation from https://eips.ethereum.org/EIPS/eip-1167 (Minimal Proxy Contract) """ - alias Explorer.Chain + alias Explorer.{Chain, Repo} alias Explorer.Chain.{Address, Hash, SmartContract} alias Explorer.Chain.SmartContract.Proxy @@ -54,8 +54,14 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1167 do defp implementation_to_smart_contract(nil, _options), do: nil defp implementation_to_smart_contract(address_hash, options) do + necessity_by_association = %{ + :smart_contract_additional_sources => :optional + } + address_hash |> SmartContract.get_smart_contract_query() + |> Chain.join_associations(necessity_by_association) |> Chain.select_repo(options).one(timeout: 10_000) + |> Repo.preload([:smart_contract_additional_sources]) end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex b/apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex index bdf2581becd0..87429d183354 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex @@ -15,11 +15,13 @@ defmodule Explorer.Chain.SmartContractAdditionalSource do @typedoc """ * `file_name` - the name of the Solidity file with contract code (with extension). * `contract_source_code` - the Solidity source code from the file with `file_name`. + * `address_hash` - foreign key for `smart_contract`. """ @type t :: %Explorer.Chain.SmartContractAdditionalSource{ file_name: String.t(), - contract_source_code: String.t() + contract_source_code: String.t(), + address_hash: Hash.Address.t() } schema "smart_contracts_additional_sources" do diff --git a/apps/explorer/lib/explorer/etherscan/contracts.ex b/apps/explorer/lib/explorer/etherscan/contracts.ex index de63dc05af36..5cfdc0c52b09 100644 --- a/apps/explorer/lib/explorer/etherscan/contracts.ex +++ b/apps/explorer/lib/explorer/etherscan/contracts.ex @@ -26,9 +26,8 @@ defmodule Explorer.Etherscan.Contracts do address -> address_with_smart_contract = Repo.replica().preload(address, [ - :smart_contract, - :decompiled_smart_contracts, - :smart_contract_additional_sources + [smart_contract: :smart_contract_additional_sources], + :decompiled_smart_contracts ]) if address_with_smart_contract.smart_contract do diff --git a/apps/explorer/test/explorer/chain/smart_contract_test.exs b/apps/explorer/test/explorer/chain/smart_contract_test.exs index f7c45251b0e4..273e8dcc670a 100644 --- a/apps/explorer/test/explorer/chain/smart_contract_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract_test.exs @@ -581,9 +581,10 @@ defmodule Explorer.Chain.SmartContractTest do secondary_sources: secondary_sources, changed_sources: changed_sources } do - sc_before_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources) + sc_before_call = + Repo.get_by(Address, hash: address.hash) |> Repo.preload(smart_contract: :smart_contract_additional_sources) - assert sc_before_call.smart_contract_additional_sources + assert sc_before_call.smart_contract.smart_contract_additional_sources |> Enum.with_index() |> Enum.all?(fn {el, ind} -> {:ok, src} = Enum.fetch(secondary_sources, ind) @@ -595,9 +596,10 @@ defmodule Explorer.Chain.SmartContractTest do assert {:ok, %SmartContract{}} = SmartContract.update_smart_contract(%{address_hash: address.hash}, [], changed_sources) - sc_after_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources) + sc_after_call = + Repo.get_by(Address, hash: address.hash) |> Repo.preload(smart_contract: :smart_contract_additional_sources) - assert sc_after_call.smart_contract_additional_sources + assert sc_after_call.smart_contract.smart_contract_additional_sources |> Enum.with_index() |> Enum.all?(fn {el, ind} -> {:ok, src} = Enum.fetch(changed_sources, ind) diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index fb529bb074c6..9c6236301dd2 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -3083,7 +3083,7 @@ defmodule Explorer.ChainTest do :contracts_creation_internal_transaction, :contracts_creation_transaction, :token, - :smart_contract_additional_sources + [smart_contract: :smart_contract_additional_sources] ]) options = [ From fd67e8a3cf4a757735d6248ce150221e2110a1d5 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 10 Jan 2024 16:02:42 +0300 Subject: [PATCH 268/607] Fix reviewer comments --- .../block_scout_web/controllers/smart_contract_controller.ex | 2 +- apps/explorer/lib/explorer/chain.ex | 1 - .../lib/explorer/chain/smart_contract/proxy/eip_1167.ex | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex index 398a90f022fb..acaee3f7bd4d 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex @@ -12,7 +12,7 @@ defmodule BlockScoutWeb.SmartContractController do def index(conn, %{"hash" => address_hash_string, "type" => contract_type, "action" => action} = params) do address_options = [ necessity_by_association: %{ - [smart_contract: :smart_contract_additional_sources] => :optional + :smart_contract => :optional } ] diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index f5b9ea6f1a65..f735fce9a73d 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1246,7 +1246,6 @@ defmodule Explorer.Chain do |> join_associations(necessity_by_association) |> with_decompiled_code_flag(hash, query_decompiled_code_flag) |> select_repo(options).one() - |> Repo.preload(smart_contract: :smart_contract_additional_sources) address_updated_result = case address_result do diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex index c5b000d5ab1e..2396c5419244 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1167 do Module for fetching proxy implementation from https://eips.ethereum.org/EIPS/eip-1167 (Minimal Proxy Contract) """ - alias Explorer.{Chain, Repo} + alias Explorer.Chain alias Explorer.Chain.{Address, Hash, SmartContract} alias Explorer.Chain.SmartContract.Proxy @@ -62,6 +62,5 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1167 do |> SmartContract.get_smart_contract_query() |> Chain.join_associations(necessity_by_association) |> Chain.select_repo(options).one(timeout: 10_000) - |> Repo.preload([:smart_contract_additional_sources]) end end From 1f927c199837f13e6899533cd5b401d6d9d21ba4 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 10 Jan 2024 18:23:52 +0300 Subject: [PATCH 269/607] Add regress test --- CHANGELOG.md | 2 +- .../api/v2/smart_contract_controller_test.exs | 109 ++++++++++++++++++ apps/explorer/test/support/factory.ex | 5 + 3 files changed, 115 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 302784ccb521..cb228e85b466 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ ### Fixes - [#9125](https://github.com/blockscout/blockscout/pull/9125) - Fix Explorer.Chain.Cache.GasPriceOracle.merge_fees -- [#9124](https://github.com/blockscout/blockscout/pull/9124) - EIP-1167 display multiple sources of bytecode twin +- [#9124](https://github.com/blockscout/blockscout/pull/9124) - EIP-1167 display multiple sources of implementation - [#9110](https://github.com/blockscout/blockscout/pull/9110) - Improve update_in in gas tracker - [#9109](https://github.com/blockscout/blockscout/pull/9109) - Return current exchange rate in api/v2/stats - [#9102](https://github.com/blockscout/blockscout/pull/9102) - Fix some log topics for Suave and Polygon Edge diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index 42477f8fa099..c1a0011c5bb0 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -305,6 +305,115 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do assert correct_response == response end + + test "get smart-contract multiple additional sources from EIP-1167 implementation", %{conn: conn} do + implementation_contract = + insert(:smart_contract, + external_libraries: [], + constructor_arguments: "", + abi: [ + %{ + "type" => "constructor", + "inputs" => [ + %{"type" => "address", "name" => "_proxyStorage"}, + %{"type" => "address", "name" => "_implementationAddress"} + ] + }, + %{ + "constant" => false, + "inputs" => [%{"name" => "x", "type" => "uint256"}], + "name" => "set", + "outputs" => [], + "payable" => false, + "stateMutability" => "nonpayable", + "type" => "function" + }, + %{ + "constant" => true, + "inputs" => [], + "name" => "get", + "outputs" => [%{"name" => "", "type" => "uint256"}], + "payable" => false, + "stateMutability" => "view", + "type" => "function" + } + ] + ) + + insert(:smart_contract_additional_source, + file_name: "test1", + contract_source_code: "test2", + address_hash: implementation_contract.address_hash + ) + + insert(:smart_contract_additional_source, + file_name: "test3", + contract_source_code: "test4", + address_hash: implementation_contract.address_hash + ) + + implementation_contract_address_hash_string = + Base.encode16(implementation_contract.address_hash.bytes, case: :lower) + + proxy_tx_input = + "0x11b804ab000000000000000000000000" <> + implementation_contract_address_hash_string <> + "000000000000000000000000000000000000000000000000000000000000006035323031313537360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000284e159163400000000000000000000000034420c13696f4ac650b9fafe915553a1abcd7dd30000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000ff5ae9b0a7522736299d797d80b8fc6f31d61100000000000000000000000000ff5ae9b0a7522736299d797d80b8fc6f31d6110000000000000000000000000000000000000000000000000000000000000003e8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034420c13696f4ac650b9fafe915553a1abcd7dd300000000000000000000000000000000000000000000000000000000000000184f7074696d69736d2053756273637269626572204e465473000000000000000000000000000000000000000000000000000000000000000000000000000000054f504e46540000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d66544e504839765651334b5952346d6b52325a6b757756424266456f5a5554545064395538666931503332752f300000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c82bbe41f2cf04e3a8efa18f7032bdd7f6d98a81000000000000000000000000efba8a2a82ec1fb1273806174f5e28fbb917cf9500000000000000000000000000000000000000000000000000000000" + + proxy_deployed_bytecode = + "0x363d3d373d3d3d363d73" <> implementation_contract_address_hash_string <> "5af43d82803e903d91602b57fd5bf3" + + proxy_address = + insert(:contract_address, + contract_code: proxy_deployed_bytecode + ) + + insert(:transaction, + created_contract_address_hash: proxy_address.hash, + input: proxy_tx_input + ) + |> with_block(status: :ok) + + correct_response = %{ + "verified_twin_address_hash" => Address.checksum(implementation_contract.address_hash), + "is_verified" => false, + "is_changed_bytecode" => false, + "is_partially_verified" => implementation_contract.partially_verified, + "is_fully_verified" => false, + "is_verified_via_sourcify" => false, + "is_vyper_contract" => implementation_contract.is_vyper_contract, + "minimal_proxy_address_hash" => "0x" <> implementation_contract_address_hash_string, + "sourcify_repo_url" => nil, + "can_be_visualized_via_sol2uml" => false, + "name" => implementation_contract && implementation_contract.name, + "compiler_version" => implementation_contract.compiler_version, + "optimization_enabled" => implementation_contract.optimization, + "optimization_runs" => implementation_contract.optimization_runs, + "evm_version" => implementation_contract.evm_version, + "verified_at" => implementation_contract.inserted_at |> to_string() |> String.replace(" ", "T"), + "source_code" => implementation_contract.contract_source_code, + "file_path" => implementation_contract.file_path, + "additional_sources" => [ + %{"file_path" => "test1", "source_code" => "test2"}, + %{"file_path" => "test3", "source_code" => "test4"} + ], + "compiler_settings" => implementation_contract.compiler_settings, + "external_libraries" => [], + "constructor_args" => nil, + "decoded_constructor_args" => nil, + "is_self_destructed" => false, + "deployed_bytecode" => proxy_deployed_bytecode, + "creation_bytecode" => proxy_tx_input, + "abi" => implementation_contract.abi, + "is_verified_via_eth_bytecode_db" => implementation_contract.verified_via_eth_bytecode_db, + "language" => smart_contract_language(implementation_contract) + } + + request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(proxy_address.hash)}") + response = json_response(request, 200) + + assert correct_response == response + end end describe "/smart-contracts/{address_hash} <> eth_bytecode_db" do diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 5fdd286ee056..6caf6f087b24 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -38,6 +38,7 @@ defmodule Explorer.Factory do Log, PendingBlockOperation, SmartContract, + SmartContractAdditionalSource, Token, TokenTransfer, Token.Instance, @@ -874,6 +875,10 @@ defmodule Explorer.Factory do } end + def smart_contract_additional_source_factory do + %SmartContractAdditionalSource{} + end + def unique_smart_contract_factory do Map.replace(smart_contract_factory(), :name, sequence("SimpleStorage")) end From 9482a4f04a0a37ece0c7b6d91aa0c370f753b10a Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Thu, 28 Dec 2023 18:30:56 +0600 Subject: [PATCH 270/607] Fetch realtime coin balances only for addresses for which it has changed --- CHANGELOG.md | 1 + .../lib/indexer/block/realtime/fetcher.ex | 30 ++----------------- 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b807cbefc0e4..77f73af4e783 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - [#9102](https://github.com/blockscout/blockscout/pull/9102) - Fix some log topics for Suave and Polygon Edge - [#9075](https://github.com/blockscout/blockscout/pull/9075) - Fix fetching contract codes - [#9073](https://github.com/blockscout/blockscout/pull/9073) - Allow payable function with output appear in the Read tab +- [#9069](https://github.com/blockscout/blockscout/pull/9069) - Fetch realtime coin balances only for addresses for which it has changed ### Chore diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index cb43857e1d0b..9f989d017f96 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -195,7 +195,6 @@ defmodule Indexer.Block.Realtime.Fetcher do block_fetcher, %{ address_coin_balances: %{params: address_coin_balances_params}, - address_hash_to_fetched_balance_block_number: address_hash_to_block_number, addresses: %{params: addresses_params}, block_rewards: block_rewards } = options @@ -209,7 +208,6 @@ defmodule Indexer.Block.Realtime.Fetcher do }}} <- {:balances, balances(block_fetcher, %{ - address_hash_to_block_number: address_hash_to_block_number, addresses_params: addresses_params, balances_params: address_coin_balances_params })}, @@ -474,35 +472,13 @@ defmodule Indexer.Block.Realtime.Fetcher do end end - defp fetch_balances_params_list(%{ - addresses_params: addresses_params, - address_hash_to_block_number: address_hash_to_block_number, - balances_params: balances_params - }) do - addresses_params - |> addresses_params_to_fetched_balances_params_set(%{address_hash_to_block_number: address_hash_to_block_number}) - |> MapSet.union(balances_params_to_fetch_balances_params_set(balances_params)) + defp fetch_balances_params_list(%{balances_params: balances_params}) do + balances_params + |> balances_params_to_fetch_balances_params_set() # stable order for easier moxing |> Enum.sort_by(fn %{hash_data: hash_data, block_quantity: block_quantity} -> {hash_data, block_quantity} end) end - defp addresses_params_to_fetched_balances_params_set(addresses_params, %{ - address_hash_to_block_number: address_hash_to_block_number - }) do - Enum.into(addresses_params, MapSet.new(), fn %{hash: address_hash} = address_params when is_binary(address_hash) -> - block_number = - case address_params do - %{fetched_coin_balance_block_number: block_number} when is_integer(block_number) -> - block_number - - _ -> - Map.fetch!(address_hash_to_block_number, String.downcase(address_hash)) - end - - %{hash_data: address_hash, block_quantity: integer_to_quantity(block_number)} - end) - end - defp balances_params_to_fetch_balances_params_set(balances_params) do Enum.into(balances_params, MapSet.new(), fn %{address_hash: address_hash, block_number: block_number} -> %{hash_data: address_hash, block_quantity: integer_to_quantity(block_number)} From 48bfc78b81b434c1a3f2651da327d56a8c330ab1 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 11 Jan 2024 22:55:27 +0300 Subject: [PATCH 271/607] Filecoin branch CI --- .../publish-docker-image-for-filecoin.yml | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 .github/workflows/publish-docker-image-for-filecoin.yml diff --git a/.github/workflows/publish-docker-image-for-filecoin.yml b/.github/workflows/publish-docker-image-for-filecoin.yml new file mode 100644 index 000000000000..6e98141461fc --- /dev/null +++ b/.github/workflows/publish-docker-image-for-filecoin.yml @@ -0,0 +1,55 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Publish Docker image for specific chain branches + +on: + push: + branches: + - production-filecoin +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 6.0.0 + DOCKER_CHAIN_NAME: filecoin + steps: + - name: Check out the repo + uses: actions/checkout@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4 + with: + images: blockscout/blockscout + + - name: Add SHORT_SHA env property with commit short sha + run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=polygon_edge \ No newline at end of file From 9825192aba24d88dd53d90093a79f11ee628b402 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 25 Dec 2023 18:51:55 +0300 Subject: [PATCH 272/607] Noves.fi API proxy --- CHANGELOG.md | 1 + .../lib/block_scout_web/api_router.ex | 8 +++ .../controllers/api/v2/address_controller.ex | 8 ++- .../api/v2/proxy/noves_fi_conroller.ex | 61 +++++++++++++++++ .../api/v2/transaction_controller.ex | 7 +- .../third_party_integrations/noves_fi.ex | 66 +++++++++++++++++++ config/runtime.exs | 5 ++ cspell.json | 2 + docker-compose/envs/common-blockscout.env | 63 ++++++++++-------- 9 files changed, 189 insertions(+), 32 deletions(-) create mode 100644 apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_conroller.ex create mode 100644 apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index b807cbefc0e4..b96216dbee7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - [#9072](https://github.com/blockscout/blockscout/pull/9072) - Add tracing by block logic for geth +- [#9056](https://github.com/blockscout/blockscout/pull/9056) - Noves.fi API proxy ### Fixes diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 7880bd15af05..5f8415014aaa 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -300,6 +300,14 @@ defmodule BlockScoutWeb.ApiRouter do get("/batches/:batch_number", V2.ZkevmController, :batch) end end + + scope "/proxy" do + scope "/noves-fi" do + get("/transactions/:transaction_hash_param", V2.Proxy.NovesFiController, :transaction) + get("/transactions/:transaction_hash_param/describe", V2.Proxy.NovesFiController, :describe_transaction) + get("/addresses/:address_hash_param/transactions", V2.Proxy.NovesFiController, :address_transactions) + end + end end scope "/v1", as: :api_v1 do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex index 3a78dcdb676c..6794eeb6ad17 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -25,7 +25,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.API.V2.{BlockView, TransactionView, WithdrawalView} alias Explorer.{Chain, Market} - alias Explorer.Chain.{Address, Transaction} + alias Explorer.Chain.{Address, Hash, Transaction} alias Explorer.Chain.Address.Counters alias Explorer.Chain.Token.Instance alias Indexer.Fetcher.{CoinBalanceOnDemand, TokenBalanceOnDemand} @@ -497,7 +497,11 @@ defmodule BlockScoutWeb.API.V2.AddressController do end end - defp validate_address(address_hash_string, params, options \\ @api_true) do + @doc """ + Checks if this valid address hash string, and this address is not prohibited address + """ + @spec validate_address(String.t(), any(), list()) :: {:ok, Hash.Address.t(), Address.t()} + def validate_address(address_hash_string, params, options \\ @api_true) do with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), {:not_found, {:ok, address}} <- {:not_found, Chain.hash_to_address(address_hash, options, false)} do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_conroller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_conroller.ex new file mode 100644 index 000000000000..7c0e6a327714 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_conroller.ex @@ -0,0 +1,61 @@ +defmodule BlockScoutWeb.API.V2.Proxy.NovesFiController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.API.V2.{AddressController, TransactionController} + alias Explorer.ThirdPartyIntegrations.NovesFi + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + @doc """ + Function to handle GET requests to `/api/v2/proxy/noves-fi/transactions/:transaction_hash_param` endpoint. + """ + @spec transaction(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def transaction(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do + with {:ok, _transaction, _transaction_hash} <- + TransactionController.validate_transaction(transaction_hash_string, params, + necessity_by_association: %{}, + api?: true + ), + url = NovesFi.tx_url(transaction_hash_string), + {response, status} <- NovesFi.noves_fi_api_request(url, conn), + {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do + conn + |> put_status(status) + |> json(response) + end + end + + @doc """ + Function to handle GET requests to `/api/v2/proxy/noves-fi/transactions/:transaction_hash_param/describe` endpoint. + """ + @spec describe_transaction(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def describe_transaction(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do + with {:ok, _transaction, _transaction_hash} <- + TransactionController.validate_transaction(transaction_hash_string, params, + necessity_by_association: %{}, + api?: true + ), + url = NovesFi.describe_tx_url(transaction_hash_string), + {response, status} <- NovesFi.noves_fi_api_request(url, conn), + {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do + conn + |> put_status(status) + |> json(response) + end + end + + @doc """ + Function to handle GET requests to `/api/v2/proxy/noves-fi/transactions/:transaction_hash_param/describe` endpoint. + """ + @spec address_transactions(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def address_transactions(conn, %{"address_hash_param" => address_hash_string} = params) do + with {:ok, _address_hash, _address} <- AddressController.validate_address(address_hash_string, params), + url = NovesFi.address_txs_url(address_hash_string), + {response, status} <- NovesFi.noves_fi_api_request(url, conn), + {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do + conn + |> put_status(status) + |> json(response) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex index a1e5134f4e9d..aabbc21ecb27 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -28,6 +28,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do alias BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation, as: TransactionInterpretationService alias BlockScoutWeb.Models.TransactionStateHelper alias Explorer.Chain + alias Explorer.Chain.{Hash, Transaction} alias Explorer.Chain.Zkevm.Reader alias Indexer.Fetcher.FirstTraceOnDemand @@ -388,7 +389,11 @@ defmodule BlockScoutWeb.API.V2.TransactionController do end end - defp validate_transaction(transaction_hash_string, params, options \\ @api_true) do + @doc """ + Checks if this valid transaction hash string, and this transaction doesn't belong to prohibited address + """ + @spec validate_transaction(String.t(), any(), list()) :: {:ok, Transaction.t(), Hash.t()} + def validate_transaction(transaction_hash_string, params, options \\ @api_true) do with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, {:not_found, {:ok, transaction}} <- {:not_found, Chain.hash_to_transaction(transaction_hash, options)}, diff --git a/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex b/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex new file mode 100644 index 000000000000..fef14a2bfd52 --- /dev/null +++ b/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex @@ -0,0 +1,66 @@ +defmodule Explorer.ThirdPartyIntegrations.NovesFi do + @moduledoc """ + Module for Noves.Fi API integration https://blockscout.noves.fi/swagger/index.html + """ + + alias Explorer.Helper + + @recv_timeout 60_000 + + @doc """ + Proxy request to noves.fi API endpoints + """ + @spec noves_fi_api_request(String.t(), Plug.Conn.t()) :: any() + def noves_fi_api_request(url, conn) do + headers = [{"apiKey", api_key()}] + url_with_params = url <> "?" <> conn.query_string + + case HTTPoison.get(url_with_params, headers, recv_timeout: @recv_timeout) do + {:ok, %HTTPoison.Response{status_code: status, body: body}} -> + {Helper.decode_json(body), status} + + _ -> + nil + end + end + + @doc """ + Noves.fi /evm/{chain}/tx/{txHash} endpoint + """ + @spec tx_url(String.t()) :: String.t() + def tx_url(transaction_hash_string) do + "#{base_url()}/evm/#{chain_name()}/tx/#{transaction_hash_string}" + end + + @doc """ + Noves.fi /evm/{chain}/describeTx/{txHash} endpoint + """ + @spec describe_tx_url(String.t()) :: String.t() + def describe_tx_url(transaction_hash_string) do + "#{base_url()}/evm/#{chain_name()}/describeTx/#{transaction_hash_string}" + end + + @doc """ + Noves.fi /evm/{chain}/txs/{accountAddress} endpoint + """ + @spec address_txs_url(String.t()) :: String.t() + def address_txs_url(address_hash_string) do + "#{base_url()}/evm/#{chain_name()}/txs/#{address_hash_string}" + end + + defp base_url do + api_base_url() || "https://blockscout.noves.fi" + end + + defp api_base_url do + Application.get_env(:explorer, __MODULE__)[:api_base_url] + end + + defp chain_name do + Application.get_env(:explorer, __MODULE__)[:chain_name] + end + + defp api_key do + Application.get_env(:explorer, __MODULE__)[:api_key] + end +end diff --git a/config/runtime.exs b/config/runtime.exs index 2cd105ac72e2..d506839dfd44 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -378,6 +378,11 @@ config :explorer, Explorer.ThirdPartyIntegrations.SolidityScan, chain_id: System.get_env("SOLIDITYSCAN_CHAIN_ID"), api_key: System.get_env("SOLIDITYSCAN_API_TOKEN") +config :explorer, Explorer.ThirdPartyIntegrations.NovesFi, + api_base_url: System.get_env("NOVES_FI_BASE_API_URL"), + chain_name: System.get_env("NOVES_FI_CHAIN_NAME"), + api_key: System.get_env("NOVES_FI_API_TOKEN") + enabled? = ConfigHelper.parse_bool_env_var("MICROSERVICE_SC_VERIFIER_ENABLED") # or "eth_bytecode_db" type = System.get_env("MICROSERVICE_SC_VERIFIER_TYPE", "sc_verifier") diff --git a/cspell.json b/cspell.json index ddad179c8d1b..aa6c88ac854f 100644 --- a/cspell.json +++ b/cspell.json @@ -312,6 +312,8 @@ "noproc", "noreferrer", "noreply", + "noves", + "NovesFi", "nowarn", "nowrap", "ntoa", diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 511cd2ebfcec..ce0c19d13c5c 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -99,8 +99,20 @@ CONTRACT_MAX_STRING_LENGTH_WITHOUT_TRIMMING=2040 # CONTRACT_DISABLE_INTERACTION= UNCLES_IN_AVERAGE_BLOCK_TIME=false DISABLE_WEBAPP=false +API_V2_ENABLED=true API_V1_READ_METHODS_DISABLED=false API_V1_WRITE_METHODS_DISABLED=false +#API_RATE_LIMIT_DISABLED=true +# API_SENSITIVE_ENDPOINTS_KEY= +API_RATE_LIMIT_TIME_INTERVAL=1s +API_RATE_LIMIT_BY_IP_TIME_INTERVAL=5m +API_RATE_LIMIT=50 +API_RATE_LIMIT_BY_KEY=50 +API_RATE_LIMIT_BY_WHITELISTED_IP=50 +API_RATE_LIMIT_WHITELISTED_IPS= +API_RATE_LIMIT_STATIC_API_KEY= +API_RATE_LIMIT_UI_V2_WITH_TOKEN=5 +API_RATE_LIMIT_BY_IP=3000 DISABLE_INDEXER=false DISABLE_REALTIME_INDEXER=false DISABLE_CATCHUP_INDEXER=false @@ -119,10 +131,16 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false # INDEXER_BLOCK_REWARD_BATCH_SIZE= # INDEXER_BLOCK_REWARD_CONCURRENCY= # INDEXER_TOKEN_INSTANCE_RETRY_REFETCH_INTERVAL= +# INDEXER_TOKEN_INSTANCE_RETRY_BATCH_SIZE=10 # INDEXER_TOKEN_INSTANCE_RETRY_CONCURRENCY= +# INDEXER_TOKEN_INSTANCE_REALTIME_BATCH_SIZE=1 # INDEXER_TOKEN_INSTANCE_REALTIME_CONCURRENCY= +# INDEXER_TOKEN_INSTANCE_SANITIZE_BATCH_SIZE=10 # INDEXER_TOKEN_INSTANCE_SANITIZE_CONCURRENCY= +# INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_BATCH_SIZE=10 # INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_CONCURRENCY=10 +# TOKEN_INSTANCE_OWNER_MIGRATION_CONCURRENCY=5 +# TOKEN_INSTANCE_OWNER_MIGRATION_BATCH_SIZE=50 # INDEXER_COIN_BALANCES_BATCH_SIZE= # INDEXER_COIN_BALANCES_CONCURRENCY= # INDEXER_RECEIPTS_BATCH_SIZE= @@ -194,31 +212,16 @@ EXTERNAL_APPS=[] # RESTRICTED_LIST_KEY= SHOW_MAINTENANCE_ALERT=false MAINTENANCE_ALERT_MESSAGE= -SOURCIFY_INTEGRATION_ENABLED=false -SOURCIFY_SERVER_URL= -SOURCIFY_REPO_URL= CHAIN_ID= MAX_SIZE_UNLESS_HIDE_ARRAY=50 HIDE_BLOCK_MINER=false DISPLAY_TOKEN_ICONS=false -SHOW_TENDERLY_LINK=false -TENDERLY_CHAIN_PATH= RE_CAPTCHA_SECRET_KEY= RE_CAPTCHA_CLIENT_KEY= RE_CAPTCHA_V3_SECRET_KEY= RE_CAPTCHA_V3_CLIENT_KEY= RE_CAPTCHA_DISABLED=false JSON_RPC= -#API_RATE_LIMIT_DISABLED=true -API_RATE_LIMIT_TIME_INTERVAL=1s -API_RATE_LIMIT_BY_IP_TIME_INTERVAL=5m -API_RATE_LIMIT=50 -API_RATE_LIMIT_BY_KEY=50 -API_RATE_LIMIT_BY_WHITELISTED_IP=50 -API_RATE_LIMIT_WHITELISTED_IPS= -API_RATE_LIMIT_STATIC_API_KEY= -API_RATE_LIMIT_UI_V2_WITH_TOKEN=5 -API_RATE_LIMIT_BY_IP=3000 # API_RATE_LIMIT_HAMMER_REDIS_URL=redis://redis_db:6379/1 # API_RATE_LIMIT_IS_BLOCKSCOUT_BEHIND_PROXY=false API_RATE_LIMIT_UI_V2_TOKEN_TTL_IN_SECONDS=18000 @@ -234,6 +237,8 @@ MICROSERVICE_VISUALIZE_SOL2UML_ENABLED=true MICROSERVICE_VISUALIZE_SOL2UML_URL=http://visualizer:8050/ MICROSERVICE_SIG_PROVIDER_ENABLED=true MICROSERVICE_SIG_PROVIDER_URL=http://sig-provider:8050/ +# MICROSERVICE_BENS_URL= +# MICROSERVICE_BENS_ENABLED= DECODE_NOT_A_CONTRACT_CALLS=true # DATABASE_READ_ONLY_API_URL= # ACCOUNT_DATABASE_URL= @@ -246,28 +251,28 @@ DECODE_NOT_A_CONTRACT_CALLS=true # ACCOUNT_SENDGRID_API_KEY= # ACCOUNT_SENDGRID_SENDER= # ACCOUNT_SENDGRID_TEMPLATE= +# ACCOUNT_VERIFICATION_EMAIL_RESEND_INTERVAL= +# ACCOUNT_PRIVATE_TAGS_LIMIT=2000 +# ACCOUNT_WATCHLIST_ADDRESSES_LIMIT=15 ACCOUNT_CLOAK_KEY= ACCOUNT_ENABLED=false ACCOUNT_REDIS_URL=redis://redis_db:6379 +EIP_1559_ELASTICITY_MULTIPLIER=2 # MIXPANEL_TOKEN= # MIXPANEL_URL= # AMPLITUDE_API_KEY= # AMPLITUDE_URL= -EIP_1559_ELASTICITY_MULTIPLIER=2 -# API_SENSITIVE_ENDPOINTS_KEY= -# ACCOUNT_VERIFICATION_EMAIL_RESEND_INTERVAL= -# INDEXER_TOKEN_INSTANCE_RETRY_BATCH_SIZE=10 -# INDEXER_TOKEN_INSTANCE_REALTIME_BATCH_SIZE=1 -# INDEXER_TOKEN_INSTANCE_SANITIZE_BATCH_SIZE=10 -# INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_BATCH_SIZE=10 -# TOKEN_INSTANCE_OWNER_MIGRATION_CONCURRENCY=5 -# TOKEN_INSTANCE_OWNER_MIGRATION_BATCH_SIZE=50 # IPFS_GATEWAY_URL= -API_V2_ENABLED=true # ADDRESSES_TABS_COUNTERS_TTL=10m -# ACCOUNT_PRIVATE_TAGS_LIMIT=2000 -# ACCOUNT_WATCHLIST_ADDRESSES_LIMIT=15 -# MICROSERVICE_BENS_URL= -# MICROSERVICE_BENS_ENABLED= # DENORMALIZATION_MIGRATION_BATCH_SIZE= # DENORMALIZATION_MIGRATION_CONCURRENCY= +SOURCIFY_INTEGRATION_ENABLED=false +SOURCIFY_SERVER_URL= +SOURCIFY_REPO_URL= +SHOW_TENDERLY_LINK=false +TENDERLY_CHAIN_PATH= +# SOLIDITYSCAN_CHAIN_ID= +# SOLIDITYSCAN_API_TOKEN= +# NOVES_FI_BASE_API_URL= +# NOVES_FI_CHAIN_NAME= +# NOVES_FI_API_TOKEN= From 1f87efb1966cba4bfad1c8a1efff27270d8fc3ba Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Tue, 9 Jan 2024 16:03:50 +0300 Subject: [PATCH 273/607] Update apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex Co-authored-by: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> --- .../controllers/api/v2/address_controller.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex index 6794eeb6ad17..fe0ec5bee1a2 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -500,7 +500,11 @@ defmodule BlockScoutWeb.API.V2.AddressController do @doc """ Checks if this valid address hash string, and this address is not prohibited address """ - @spec validate_address(String.t(), any(), list()) :: {:ok, Hash.Address.t(), Address.t()} + @spec validate_address(String.t(), any(), Keyword.t()) :: + {:format, :error} + | {:not_found, {:error, :not_found}} + | {:restricted_access, true} + | {:ok, Hash.t(), Address.t()} def validate_address(address_hash_string, params, options \\ @api_true) do with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), From 9f3048b2cdb05b8458d4e8c60ec1c75deb876fbb Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Tue, 9 Jan 2024 16:03:57 +0300 Subject: [PATCH 274/607] Update apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex Co-authored-by: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> --- .../controllers/api/v2/transaction_controller.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex index aabbc21ecb27..ff62beb9f5d8 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -392,7 +392,11 @@ defmodule BlockScoutWeb.API.V2.TransactionController do @doc """ Checks if this valid transaction hash string, and this transaction doesn't belong to prohibited address """ - @spec validate_transaction(String.t(), any(), list()) :: {:ok, Transaction.t(), Hash.t()} + @spec validate_transaction(String.t(), any(), Keyword.t()) :: + {:format, :error} + | {:not_found, {:error, :not_found}} + | {:restricted_access, true} + | {:ok, Transaction.t(), Hash.t()} def validate_transaction(transaction_hash_string, params, options \\ @api_true) do with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, {:not_found, {:ok, transaction}} <- From 9ee50997736e07dc4403091e753c012853d8d466 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 9 Jan 2024 18:29:17 +0300 Subject: [PATCH 275/607] Clear GA cache --- .github/workflows/config.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 82c11d6d85ec..2e71299d83c1 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -75,7 +75,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_33-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ 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_33-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ 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_33-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ 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_33-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ 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_33-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ 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_33-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ 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_33-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ 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_33-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ 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_33-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ 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_33-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ 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_33-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ 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_33-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ 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_33-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" From 7c24d90976dd0d1d4e52b63d4ae1678c47db1444 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 10 Jan 2024 15:09:07 +0300 Subject: [PATCH 276/607] Process reviewer comments --- .../lib/explorer/third_party_integrations/noves_fi.ex | 11 ++++------- config/runtime.exs | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex b/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex index fef14a2bfd52..dcf0e473b24e 100644 --- a/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex +++ b/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex @@ -4,13 +4,14 @@ defmodule Explorer.ThirdPartyIntegrations.NovesFi do """ alias Explorer.Helper + alias Explorer.Utility.Microservice @recv_timeout 60_000 @doc """ Proxy request to noves.fi API endpoints """ - @spec noves_fi_api_request(String.t(), Plug.Conn.t()) :: any() + @spec noves_fi_api_request(String.t(), Plug.Conn.t()) :: {any(), integer()} def noves_fi_api_request(url, conn) do headers = [{"apiKey", api_key()}] url_with_params = url <> "?" <> conn.query_string @@ -20,7 +21,7 @@ defmodule Explorer.ThirdPartyIntegrations.NovesFi do {Helper.decode_json(body), status} _ -> - nil + {nil, 500} end end @@ -49,11 +50,7 @@ defmodule Explorer.ThirdPartyIntegrations.NovesFi do end defp base_url do - api_base_url() || "https://blockscout.noves.fi" - end - - defp api_base_url do - Application.get_env(:explorer, __MODULE__)[:api_base_url] + Microservice.base_url(__MODULE__) end defp chain_name do diff --git a/config/runtime.exs b/config/runtime.exs index d506839dfd44..5b7b09717066 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -379,7 +379,7 @@ config :explorer, Explorer.ThirdPartyIntegrations.SolidityScan, api_key: System.get_env("SOLIDITYSCAN_API_TOKEN") config :explorer, Explorer.ThirdPartyIntegrations.NovesFi, - api_base_url: System.get_env("NOVES_FI_BASE_API_URL"), + service_url: System.get_env("NOVES_FI_BASE_API_URL") || "https://blockscout.noves.fi", chain_name: System.get_env("NOVES_FI_CHAIN_NAME"), api_key: System.get_env("NOVES_FI_API_TOKEN") From e2583f1c69d0aa103a980132e35c8f5424aec2d7 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Thu, 11 Jan 2024 23:20:32 +0300 Subject: [PATCH 277/607] Fix bug with match error; Add sorting before broadcasting updated token balances --- CHANGELOG.md | 1 + .../channels/address_channel.ex | 24 +++++++++- .../fetcher/token_balance_on_demand.ex | 44 ++++++++++--------- 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b807cbefc0e4..41ce17623548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixes +- [#9139](https://github.com/blockscout/blockscout/pull/9139) - TokenBalanceOnDemand fixes - [#9125](https://github.com/blockscout/blockscout/pull/9125) - Fix Explorer.Chain.Cache.GasPriceOracle.merge_fees - [#9110](https://github.com/blockscout/blockscout/pull/9110) - Improve update_in in gas tracker - [#9109](https://github.com/blockscout/blockscout/pull/9109) - Return current exchange rate in api/v2/stats diff --git a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex index f0ead554c72d..89795fb8a76f 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex @@ -246,7 +246,20 @@ defmodule BlockScoutWeb.AddressChannel do end defp push_current_token_balances(socket, address_current_token_balances, event_postfix, token_type) do - filtered_ctbs = address_current_token_balances |> Enum.filter(fn ctb -> ctb.token_type == token_type end) + filtered_ctbs = + address_current_token_balances + |> Enum.filter(fn ctb -> ctb.token_type == token_type end) + |> Enum.sort_by( + fn ctb -> + value = + if ctb.token.decimals, + do: Decimal.div(ctb.value, Decimal.new(Integer.pow(10, Decimal.to_integer(ctb.token.decimals)))), + else: ctb.value + + {(ctb.token.fiat_value && Decimal.mult(value, ctb.token.fiat_value)) || Decimal.new(0), value} + end, + &sorter/2 + ) push(socket, "updated_token_balances_" <> event_postfix, %{ token_balances: @@ -257,6 +270,15 @@ defmodule BlockScoutWeb.AddressChannel do }) end + defp sorter({fiat_value_1, value_1}, {fiat_value_2, value_2}) do + case {Decimal.compare(fiat_value_1, fiat_value_2), Decimal.compare(value_1, value_2)} do + {:gt, _} -> true + {:eq, :gt} -> true + {:eq, :eq} -> true + _ -> false + end + end + def push_current_coin_balance( %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket, block_number, diff --git a/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex b/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex index cd090f19fe36..8b8336c906db 100644 --- a/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex +++ b/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex @@ -128,28 +128,30 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do (updated_erc_1155_ctbs ++ updated_other_ctbs) |> Enum.filter(&(!is_nil(&1))) - {:ok, - %{ - address_current_token_balances: imported_ctbs - }} = - Chain.import(%{ - address_current_token_balances: %{ - params: filtered_current_token_balances_update_params + if Enum.count(filtered_current_token_balances_update_params) > 0 do + {:ok, + %{ + address_current_token_balances: imported_ctbs + }} = + Chain.import(%{ + address_current_token_balances: %{ + params: filtered_current_token_balances_update_params + }, + broadcast: false + }) + + Publisher.broadcast( + %{ + address_current_token_balances: %{ + address_hash: to_string(address_hash), + address_current_token_balances: + imported_ctbs + |> Enum.map(fn ctb -> %CurrentTokenBalance{ctb | token: tokens[ctb.token_contract_address_hash.bytes]} end) + } }, - broadcast: false - }) - - Publisher.broadcast( - %{ - address_current_token_balances: %{ - address_hash: to_string(address_hash), - address_current_token_balances: - imported_ctbs - |> Enum.map(fn ctb -> %CurrentTokenBalance{ctb | token: tokens[ctb.token_contract_address_hash.bytes]} end) - } - }, - :on_demand - ) + :on_demand + ) + end end defp prepare_updated_balance({{:ok, updated_balance}, stale_current_token_balance}, block_number) do From 5ea59f3f21f259eb0bdfe59cda48244f6d7cd0f1 Mon Sep 17 00:00:00 2001 From: shuoer86 <129674997+shuoer86@users.noreply.github.com> Date: Fri, 12 Jan 2024 22:45:17 +0800 Subject: [PATCH 278/607] Fix typo --- apps/explorer/test/support/fixture/smart_contract/ERC677.sol | 4 ++-- .../test/support/fixture/smart_contract/issue_3082.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/explorer/test/support/fixture/smart_contract/ERC677.sol b/apps/explorer/test/support/fixture/smart_contract/ERC677.sol index e999f2b306c5..f1b4cf495f76 100644 --- a/apps/explorer/test/support/fixture/smart_contract/ERC677.sol +++ b/apps/explorer/test/support/fixture/smart_contract/ERC677.sol @@ -64,7 +64,7 @@ interface IERC677MultiBridgeToken { * specific functions. * * This module is used through inheritance. It will make available the modifier -* `onlyOwner`, which can be aplied to your functions to restrict their use to +* `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ contract Ownable { @@ -1160,4 +1160,4 @@ contract ERC677MultiBridgeToken is IERC677MultiBridgeToken, ERC677BridgeToken { function isBridge(address _address) public view returns (bool) { return _address != F_ADDR && bridgePointers[_address] != address(0); } -} \ No newline at end of file +} diff --git a/apps/explorer/test/support/fixture/smart_contract/issue_3082.sol b/apps/explorer/test/support/fixture/smart_contract/issue_3082.sol index 7a06a2bfb48b..b0040d68a1a8 100644 --- a/apps/explorer/test/support/fixture/smart_contract/issue_3082.sol +++ b/apps/explorer/test/support/fixture/smart_contract/issue_3082.sol @@ -28,7 +28,7 @@ interface IERC677MultiBridgeToken { * specific functions. * * This module is used through inheritance. It will make available the modifier - * `onlyOwner`, which can be aplied to your functions to restrict their use to + * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ contract Ownable { @@ -591,4 +591,4 @@ contract Distribution is Ownable, IDistribution { revert("invalid address"); } } -} \ No newline at end of file +} From b31dd86bb09ab796a4cfbf5e26e29f9d27aae2b7 Mon Sep 17 00:00:00 2001 From: shuoer86 <129674997+shuoer86@users.noreply.github.com> Date: Fri, 12 Jan 2024 22:45:40 +0800 Subject: [PATCH 279/607] Fix typo --- .../test/support/fixture/smart_contract/issue_5114.sol | 4 ++-- .../fixture/smart_contract/issue_with_constructor_args.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/explorer/test/support/fixture/smart_contract/issue_5114.sol b/apps/explorer/test/support/fixture/smart_contract/issue_5114.sol index 0c5458210089..5a997e254e0f 100644 --- a/apps/explorer/test/support/fixture/smart_contract/issue_5114.sol +++ b/apps/explorer/test/support/fixture/smart_contract/issue_5114.sol @@ -16,7 +16,7 @@ abstract contract Proxy { /** * @dev Delegates the current call to `implementation`. * - * This function does not return to its internall call site, it will return directly to the external caller. + * This function does not return to its internal call site, it will return directly to the external caller. */ function _delegate(address implementation) internal virtual { // solhint-disable-next-line no-inline-assembly @@ -49,7 +49,7 @@ abstract contract Proxy { /** * @dev Delegates the current call to the address returned by `_implementation()`. * - * This function does not return to its internall call site, it will return directly to the external caller. + * This function does not return to its internal call site, it will return directly to the external caller. */ function _fallback() internal virtual { _beforeFallback(); diff --git a/apps/explorer/test/support/fixture/smart_contract/issue_with_constructor_args.sol b/apps/explorer/test/support/fixture/smart_contract/issue_with_constructor_args.sol index d17247212c07..a561dfc1b739 100644 --- a/apps/explorer/test/support/fixture/smart_contract/issue_with_constructor_args.sol +++ b/apps/explorer/test/support/fixture/smart_contract/issue_with_constructor_args.sol @@ -292,7 +292,7 @@ abstract contract Proxy { /** * @dev Delegates the current call to `implementation`. * - * This function does not return to its internall call site, it will return directly to the external caller. + * This function does not return to its internal call site, it will return directly to the external caller. */ function _delegate(address implementation) internal virtual { // solhint-disable-next-line no-inline-assembly @@ -325,7 +325,7 @@ abstract contract Proxy { /** * @dev Delegates the current call to the address returned by `_implementation()`. * - * This function does not return to its internall call site, it will return directly to the external caller. + * This function does not return to its internal call site, it will return directly to the external caller. */ function _fallback() internal virtual { _beforeFallback(); From 3aef113f6f8101d11c4709bd96633a2898e6b3ba Mon Sep 17 00:00:00 2001 From: shuoer86 <129674997+shuoer86@users.noreply.github.com> Date: Fri, 12 Jan 2024 22:46:26 +0800 Subject: [PATCH 280/607] Fix typo --- .../fixture/smart_contract/large_smart_contract.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol b/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol index 6b147f21d86e..2b42daeb972d 100644 --- a/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol +++ b/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol @@ -113,7 +113,7 @@ interface IHomeWork { * @param key bytes32 The unique value used to derive the home address. * @param owner address The account that will be granted ownership of the * ERC721 token. - * @dev In order to mint an ERC721 token, the assocated home address cannot be + * @dev In order to mint an ERC721 token, the associated home address cannot be * in use, or else the token will not be able to deploy to the home address. * The controller is set to this contract until the token is redeemed, at * which point the redeemer designates a new controller for the home address. @@ -239,7 +239,7 @@ interface IHomeWork { * @param owner address The account that will be granted ownership of the * ERC721 token. * @return The derived key. - * @dev In order to mint an ERC721 token, the assocated home address cannot be + * @dev In order to mint an ERC721 token, the associated home address cannot be * in use, or else the token will not be able to deploy to the home address. * The controller is set to this contract until the token is redeemed, at * which point the redeemer designates a new controller for the home address. @@ -1778,7 +1778,7 @@ contract HomeWork is IHomeWork, ERC721Enumerable, IERC721Metadata, IERC1412 { * @param key bytes32 The unique value used to derive the home address. * @param owner address The account that will be granted ownership of the * ERC721 token. - * @dev In order to mint an ERC721 token, the assocated home address cannot be + * @dev In order to mint an ERC721 token, the associated home address cannot be * in use, or else the token will not be able to deploy to the home address. * The controller is set to this contract until the token is redeemed, at * which point the redeemer designates a new controller for the home address. @@ -2011,7 +2011,7 @@ contract HomeWork is IHomeWork, ERC721Enumerable, IERC721Metadata, IERC1412 { * @param owner address The account that will be granted ownership of the * ERC721 token. * @return The derived key. - * @dev In order to mint an ERC721 token, the assocated home address cannot be + * @dev In order to mint an ERC721 token, the associated home address cannot be * in use, or else the token will not be able to deploy to the home address. * The controller is set to this contract until the token is redeemed, at * which point the redeemer designates a new controller for the home address. From e6b39be251dbbf79728f66e3acb0c95ec9367388 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 15 Jan 2024 11:26:04 +0300 Subject: [PATCH 281/607] Manage proxy implementation ttl via avg block time --- CHANGELOG.md | 1 + apps/explorer/config/test.exs | 10 ++- .../lib/explorer/chain/smart_contract.ex | 14 +-- .../explorer/chain/smart_contract_test.exs | 89 +++++++++++++++---- config/runtime.exs | 11 ++- 5 files changed, 94 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b96216dbee7a..42c62c075dec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#9155](https://github.com/blockscout/blockscout/pull/9155) - Allow bypassing avg block time in proxy implementation re-fetch ttl calculation - [#9072](https://github.com/blockscout/blockscout/pull/9072) - Add tracing by block logic for geth - [#9056](https://github.com/blockscout/blockscout/pull/9056) - Noves.fi API proxy diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index 955f9df84267..b292598e17ff 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -24,12 +24,14 @@ config :explorer, Explorer.Repo.Replica1, ownership_timeout: :timer.minutes(1), timeout: :timer.seconds(60), queue_target: 1000, - enable_caching_implementation_data_of_proxy: true, - avg_block_time_as_ttl_cached_implementation_data_of_proxy: false, - fallback_ttl_cached_implementation_data_of_proxy: :timer.seconds(20), - implementation_data_fetching_timeout: :timer.seconds(20), log: false +config :explorer, :proxy, + caching_implementation_data_enabled: true, + implementation_data_ttl_via_avg_block_time: false, + fallback_cached_implementation_data_ttl: :timer.seconds(20), + implementation_data_fetching_timeout: :timer.seconds(20) + # Configure API database config :explorer, Explorer.Repo.Account, database: "explorer_test_account", diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index 1d381ef92ec0..65105cbf853f 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -516,7 +516,7 @@ defmodule Explorer.Chain.SmartContract do options ) do updated_smart_contract = - if Application.get_env(:explorer, :enable_caching_implementation_data_of_proxy) && + if Application.get_env(:explorer, :proxy)[:caching_implementation_data_enabled] && check_implementation_refetch_necessity(implementation_fetched_at) do address_hash_to_smart_contract_without_twin(address_hash, options) else @@ -544,7 +544,7 @@ defmodule Explorer.Chain.SmartContract do Proxy.fetch_implementation_address_hash(address_hash, abi, metadata_from_verified_twin, options) end) - timeout = Application.get_env(:explorer, :implementation_data_fetching_timeout) + timeout = Application.get_env(:explorer, :proxy)[:implementation_data_fetching_timeout] case Task.yield(get_implementation_address_hash_task, timeout) || Task.ignore(get_implementation_address_hash_task) do @@ -1182,15 +1182,15 @@ defmodule Explorer.Chain.SmartContract do defp check_implementation_refetch_necessity(nil), do: true defp check_implementation_refetch_necessity(timestamp) do - if Application.get_env(:explorer, :enable_caching_implementation_data_of_proxy) do + if Application.get_env(:explorer, :proxy)[:caching_implementation_data_enabled] do now = DateTime.utc_now() - average_block_time = get_average_block_time() + average_block_time = get_average_block_time_for_implementation_refetch() fresh_time_distance = case average_block_time do 0 -> - Application.get_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy) + Application.get_env(:explorer, :proxy)[:fallback_cached_implementation_data_ttl] time -> round(time) @@ -1204,8 +1204,8 @@ defmodule Explorer.Chain.SmartContract do end end - defp get_average_block_time do - if Application.get_env(:explorer, :avg_block_time_as_ttl_cached_implementation_data_of_proxy) do + defp get_average_block_time_for_implementation_refetch do + if Application.get_env(:explorer, :proxy)[:implementation_data_ttl_via_avg_block_time] do case AverageBlockTime.average_block_time() do {:error, :disabled} -> 0 diff --git a/apps/explorer/test/explorer/chain/smart_contract_test.exs b/apps/explorer/test/explorer/chain/smart_contract_test.exs index f7c45251b0e4..1501d08859cd 100644 --- a/apps/explorer/test/explorer/chain/smart_contract_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract_test.exs @@ -15,8 +15,13 @@ defmodule Explorer.Chain.SmartContractTest do test "check proxy_contract/1 function" do smart_contract = insert(:smart_contract) - Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) - Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20)) + proxy = + :explorer + |> Application.get_env(:proxy) + |> Keyword.replace(:fallback_cached_implementation_data_ttl, :timer.seconds(20)) + |> Keyword.replace(:implementation_data_fetching_timeout, :timer.seconds(20)) + + Application.put_env(:explorer, :proxy, proxy) refute smart_contract.implementation_fetched_at @@ -26,7 +31,12 @@ defmodule Explorer.Chain.SmartContractTest do verify!(EthereumJSONRPC.Mox) assert_implementation_never_fetched(smart_contract.address_hash) - Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0) + proxy = + :explorer + |> Application.get_env(:proxy) + |> Keyword.replace(:fallback_cached_implementation_data_ttl, 0) + + Application.put_env(:explorer, :proxy, proxy) get_eip1967_implementation_error_response() refute Proxy.proxy_contract?(smart_contract) @@ -42,10 +52,22 @@ defmodule Explorer.Chain.SmartContractTest do verify!(EthereumJSONRPC.Mox) assert_implementation_address(smart_contract.address_hash) - Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) + proxy = + :explorer + |> Application.get_env(:proxy) + |> Keyword.replace(:fallback_cached_implementation_data_ttl, :timer.seconds(20)) + + Application.put_env(:explorer, :proxy, proxy) + assert Proxy.proxy_contract?(smart_contract) - Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0) + proxy = + :explorer + |> Application.get_env(:proxy) + |> Keyword.replace(:fallback_cached_implementation_data_ttl, 0) + + Application.put_env(:explorer, :proxy, proxy) + get_eip1967_implementation_non_zero_address() assert Proxy.proxy_contract?(smart_contract) verify!(EthereumJSONRPC.Mox) @@ -59,8 +81,13 @@ defmodule Explorer.Chain.SmartContractTest do smart_contract = insert(:smart_contract) implementation_smart_contract = insert(:smart_contract, name: "proxy") - Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) - Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20)) + proxy = + :explorer + |> Application.get_env(:proxy) + |> Keyword.replace(:fallback_cached_implementation_data_ttl, :timer.seconds(20)) + |> Keyword.replace(:implementation_data_fetching_timeout, :timer.seconds(20)) + + Application.put_env(:explorer, :proxy, proxy) refute smart_contract.implementation_fetched_at @@ -71,7 +98,12 @@ defmodule Explorer.Chain.SmartContractTest do assert_implementation_never_fetched(smart_contract.address_hash) # extract proxy info from db - Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0) + proxy = + :explorer + |> Application.get_env(:proxy) + |> Keyword.replace(:fallback_cached_implementation_data_ttl, 0) + + Application.put_env(:explorer, :proxy, proxy) string_implementation_address_hash = to_string(implementation_smart_contract.address_hash) @@ -116,7 +148,12 @@ defmodule Explorer.Chain.SmartContractTest do contract_1 = SmartContract.address_hash_to_smart_contract(smart_contract.address_hash) - Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) + proxy = + :explorer + |> Application.get_env(:proxy) + |> Keyword.replace(:fallback_cached_implementation_data_ttl, :timer.seconds(20)) + + Application.put_env(:explorer, :proxy, proxy) assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(smart_contract) @@ -126,7 +163,12 @@ defmodule Explorer.Chain.SmartContractTest do assert contract_1.implementation_fetched_at == contract_2.implementation_fetched_at && contract_1.updated_at == contract_2.updated_at - Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0) + proxy = + :explorer + |> Application.get_env(:proxy) + |> Keyword.replace(:fallback_cached_implementation_data_ttl, 0) + + Application.put_env(:explorer, :proxy, proxy) get_eip1967_implementation_zero_addresses() assert {^string_implementation_address_hash, "proxy"} = @@ -147,8 +189,13 @@ defmodule Explorer.Chain.SmartContractTest do twin = SmartContract.address_hash_to_smart_contract(another_address.hash) implementation_smart_contract = insert(:smart_contract, name: "proxy") - Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) - Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20)) + proxy = + :explorer + |> Application.get_env(:proxy) + |> Keyword.replace(:fallback_cached_implementation_data_ttl, :timer.seconds(20)) + |> Keyword.replace(:implementation_data_fetching_timeout, :timer.seconds(20)) + + Application.put_env(:explorer, :proxy, proxy) # fetch nil implementation get_eip1967_implementation_zero_addresses() @@ -184,8 +231,13 @@ defmodule Explorer.Chain.SmartContractTest do implementation_smart_contract = insert(:smart_contract, name: "proxy") - Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) - Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20)) + proxy = + :explorer + |> Application.get_env(:proxy) + |> Keyword.replace(:fallback_cached_implementation_data_ttl, :timer.seconds(20)) + |> Keyword.replace(:implementation_data_fetching_timeout, :timer.seconds(20)) + + Application.put_env(:explorer, :proxy, proxy) # fetch nil implementation get_eip1967_implementation_zero_addresses() @@ -231,8 +283,13 @@ defmodule Explorer.Chain.SmartContractTest do implementation_smart_contract = insert(:smart_contract, name: "proxy") - Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) - Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20)) + proxy = + :explorer + |> Application.get_env(:proxy) + |> Keyword.replace(:fallback_cached_implementation_data_ttl, :timer.seconds(20)) + |> Keyword.replace(:implementation_data_fetching_timeout, :timer.seconds(20)) + + Application.put_env(:explorer, :proxy, proxy) # fetch nil implementation get_eip1967_implementation_zero_addresses() diff --git a/config/runtime.exs b/config/runtime.exs index 5b7b09717066..c81ceeafbbc4 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -203,15 +203,18 @@ config :explorer, do: Explorer.Chain.Events.DBSender, else: Explorer.Chain.Events.SimpleSender ), - enable_caching_implementation_data_of_proxy: true, - avg_block_time_as_ttl_cached_implementation_data_of_proxy: true, - fallback_ttl_cached_implementation_data_of_proxy: :timer.seconds(4), - implementation_data_fetching_timeout: :timer.seconds(2), restricted_list: System.get_env("RESTRICTED_LIST"), restricted_list_key: System.get_env("RESTRICTED_LIST_KEY"), checksum_function: checksum_function && String.to_atom(checksum_function), elasticity_multiplier: ConfigHelper.parse_integer_env_var("EIP_1559_ELASTICITY_MULTIPLIER", 2) +config :explorer, :proxy, + caching_implementation_data_enabled: true, + implementation_data_ttl_via_avg_block_time: + ConfigHelper.parse_bool_env_var("CONTRACT_PROXY_IMPLEMENTATION_TTL_VIA_AVG_BLOCK_TIME", "true"), + fallback_cached_implementation_data_ttl: :timer.seconds(4), + implementation_data_fetching_timeout: :timer.seconds(2) + config :explorer, Explorer.Chain.Events.Listener, enabled: if(disable_webapp? && disable_indexer?, From 9efd87fece409c39d57f918bf95690cf9590fea2 Mon Sep 17 00:00:00 2001 From: Andrijan Ostrun Date: Mon, 15 Jan 2024 17:10:27 +0100 Subject: [PATCH 282/607] Increased shared memory for Postgres containers --- docker-compose/services/db.yml | 1 + docker-compose/services/stats.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/docker-compose/services/db.yml b/docker-compose/services/db.yml index b7d036e966b2..b58aba6e0e35 100644 --- a/docker-compose/services/db.yml +++ b/docker-compose/services/db.yml @@ -17,6 +17,7 @@ services: condition: service_completed_successfully image: postgres:14 user: 2000:2000 + shm_size: 256m restart: always container_name: 'db' command: postgres -c 'max_connections=200' -c 'client_connection_check_interval=60000' diff --git a/docker-compose/services/stats.yml b/docker-compose/services/stats.yml index 0ba073432d33..85bee46b9909 100644 --- a/docker-compose/services/stats.yml +++ b/docker-compose/services/stats.yml @@ -17,6 +17,7 @@ services: condition: service_completed_successfully image: postgres:14 user: 2000:2000 + shm_size: 256m restart: always container_name: 'stats-postgres' command: postgres -c 'max_connections=200' From 2b1dbc41b773a5503478052c91f52e8d09022ea4 Mon Sep 17 00:00:00 2001 From: Andrijan Ostrun Date: Mon, 15 Jan 2024 17:24:24 +0100 Subject: [PATCH 283/607] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9fc17df2c40..084a3cb89075 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [#9155](https://github.com/blockscout/blockscout/pull/9155) - Allow bypassing avg block time in proxy implementation re-fetch ttl calculation - [#9072](https://github.com/blockscout/blockscout/pull/9072) - Add tracing by block logic for geth - [#9056](https://github.com/blockscout/blockscout/pull/9056) - Noves.fi API proxy +- [#9158](https://github.com/blockscout/blockscout/pull/9158) - Increase shared memory for PostgreSQL containers ### Fixes From 5713e73f1eec3cf7f4011a0288527e1f767e31b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jan 2024 18:29:04 +0000 Subject: [PATCH 284/607] Bump copy-webpack-plugin in /apps/block_scout_web/assets Bumps [copy-webpack-plugin](https://github.com/webpack-contrib/copy-webpack-plugin) from 11.0.0 to 12.0.1. - [Release notes](https://github.com/webpack-contrib/copy-webpack-plugin/releases) - [Changelog](https://github.com/webpack-contrib/copy-webpack-plugin/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v11.0.0...v12.0.1) --- updated-dependencies: - dependency-name: copy-webpack-plugin dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 205 ++++++++++-------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 121 insertions(+), 86 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 1ad62aa64380..e2336a758878 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -75,7 +75,7 @@ "@babel/preset-env": "^7.23.7", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", - "copy-webpack-plugin": "^11.0.0", + "copy-webpack-plugin": "^12.0.1", "css-loader": "^6.8.1", "css-minimizer-webpack-plugin": "^5.0.1", "eslint": "^8.56.0", @@ -3468,6 +3468,18 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, + "node_modules/@sindresorhus/merge-streams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz", + "integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@sinonjs/commons": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", @@ -5914,20 +5926,20 @@ } }, "node_modules/copy-webpack-plugin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", - "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.1.tgz", + "integrity": "sha512-dhMfjJMYKDmmbG6Yn2pRSs1g8FgeQRtbE/JM6VAM9Xouk3KO1UVrwlLHLXxaI5F+o9WgnRfhFZzY9eV34O2gZQ==", "dev": true, "dependencies": { - "fast-glob": "^3.2.11", + "fast-glob": "^3.3.2", "glob-parent": "^6.0.1", - "globby": "^13.1.1", + "globby": "^14.0.0", "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", @@ -6812,18 +6824,6 @@ "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.2.tgz", "integrity": "sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==" }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -8718,9 +8718,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -9228,28 +9228,29 @@ } }, "node_modules/globby": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz", - "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz", + "integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==", "dev": true, "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.11", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^4.0.0" + "@sindresorhus/merge-streams": "^1.0.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "node_modules/globby/node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", "dev": true, "engines": { "node": ">=12" @@ -9258,6 +9259,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globby/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/good-listener": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", @@ -9640,9 +9653,9 @@ ] }, "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", "dev": true, "engines": { "node": ">= 4" @@ -15268,9 +15281,9 @@ } }, "node_modules/schema-utils": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.1.0.tgz", - "integrity": "sha512-Jw+GZVbP5IggB2WAn6UHI02LBwGmsIeYN/lNbSMZyDziQ7jmtAUrqKqDja+W89YHVs+KL/3IkIMltAklqB1vAw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", @@ -15407,9 +15420,9 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "dependencies": { "randombytes": "^2.1.0" @@ -16587,6 +16600,18 @@ "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -20233,6 +20258,12 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" }, + "@sindresorhus/merge-streams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz", + "integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==", + "dev": true + }, "@sinonjs/commons": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", @@ -22212,17 +22243,17 @@ } }, "copy-webpack-plugin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", - "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.1.tgz", + "integrity": "sha512-dhMfjJMYKDmmbG6Yn2pRSs1g8FgeQRtbE/JM6VAM9Xouk3KO1UVrwlLHLXxaI5F+o9WgnRfhFZzY9eV34O2gZQ==", "dev": true, "requires": { - "fast-glob": "^3.2.11", + "fast-glob": "^3.3.2", "glob-parent": "^6.0.1", - "globby": "^13.1.1", + "globby": "^14.0.0", "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" } }, "core-js": { @@ -22861,15 +22892,6 @@ "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.2.tgz", "integrity": "sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==" }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -24413,9 +24435,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -24801,22 +24823,29 @@ } }, "globby": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz", - "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz", + "integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==", "dev": true, "requires": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.11", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^4.0.0" + "@sindresorhus/merge-streams": "^1.0.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" }, "dependencies": { + "path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true + }, "slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "dev": true } } @@ -25094,9 +25123,9 @@ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", "dev": true }, "immediate": { @@ -29281,9 +29310,9 @@ } }, "schema-utils": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.1.0.tgz", - "integrity": "sha512-Jw+GZVbP5IggB2WAn6UHI02LBwGmsIeYN/lNbSMZyDziQ7jmtAUrqKqDja+W89YHVs+KL/3IkIMltAklqB1vAw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", @@ -29399,9 +29428,9 @@ } }, "serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "requires": { "randombytes": "^2.1.0" @@ -30258,6 +30287,12 @@ "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", "dev": true }, + "unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index e47b66d1bc48..567bb8f6ddf3 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -87,7 +87,7 @@ "@babel/preset-env": "^7.23.7", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", - "copy-webpack-plugin": "^11.0.0", + "copy-webpack-plugin": "^12.0.1", "css-loader": "^6.8.1", "css-minimizer-webpack-plugin": "^5.0.1", "eslint": "^8.56.0", From e2e6c7a1c4a72e984ffbe0d2d687a4ec69c96ba2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jan 2024 18:29:19 +0000 Subject: [PATCH 285/607] Bump sass-loader from 13.3.3 to 14.0.0 in /apps/block_scout_web/assets Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 13.3.3 to 14.0.0. - [Release notes](https://github.com/webpack-contrib/sass-loader/releases) - [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/sass-loader/compare/v13.3.3...v14.0.0) --- updated-dependencies: - dependency-name: sass-loader dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 20 ++++++++----------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 1ad62aa64380..bb44fb32c66c 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -90,7 +90,7 @@ "postcss": "^8.4.33", "postcss-loader": "^7.3.4", "sass": "^1.69.7", - "sass-loader": "^13.3.3", + "sass-loader": "^14.0.0", "style-loader": "^3.3.3", "webpack": "^5.89.0", "webpack-cli": "^5.1.4" @@ -15210,31 +15210,27 @@ } }, "node_modules/sass-loader": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.3.tgz", - "integrity": "sha512-mt5YN2F1MOZr3d/wBRcZxeFgwgkH44wVc2zohO2YF6JiOMkiXe4BYRZpSu2sO1g71mo/j16txzUhsKZlqjVGzA==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.0.0.tgz", + "integrity": "sha512-oceP9wWbep/yRJ2+sMbCzk0UsXsDzdNis+N8nu9i5GwPXjy6v3DNB6TqfJLSpPO9k4+B8x8p/CEgjA9ZLkoLug==", "dev": true, "dependencies": { "neo-async": "^2.6.2" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "fibers": ">= 3.1.0", "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", "sass": "^1.3.0", "sass-embedded": "*", "webpack": "^5.0.0" }, "peerDependenciesMeta": { - "fibers": { - "optional": true - }, "node-sass": { "optional": true }, @@ -29254,9 +29250,9 @@ } }, "sass-loader": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.3.tgz", - "integrity": "sha512-mt5YN2F1MOZr3d/wBRcZxeFgwgkH44wVc2zohO2YF6JiOMkiXe4BYRZpSu2sO1g71mo/j16txzUhsKZlqjVGzA==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.0.0.tgz", + "integrity": "sha512-oceP9wWbep/yRJ2+sMbCzk0UsXsDzdNis+N8nu9i5GwPXjy6v3DNB6TqfJLSpPO9k4+B8x8p/CEgjA9ZLkoLug==", "dev": true, "requires": { "neo-async": "^2.6.2" diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index e47b66d1bc48..e2046bf9a1a9 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -102,7 +102,7 @@ "postcss": "^8.4.33", "postcss-loader": "^7.3.4", "sass": "^1.69.7", - "sass-loader": "^13.3.3", + "sass-loader": "^14.0.0", "style-loader": "^3.3.3", "webpack": "^5.89.0", "webpack-cli": "^5.1.4" From e0d87d6d4a428c762e83bcbea1f6321d141ccde7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jan 2024 18:29:53 +0000 Subject: [PATCH 286/607] Bump mini-css-extract-plugin in /apps/block_scout_web/assets Bumps [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) from 2.7.6 to 2.7.7. - [Release notes](https://github.com/webpack-contrib/mini-css-extract-plugin/releases) - [Changelog](https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/mini-css-extract-plugin/compare/v2.7.6...v2.7.7) --- updated-dependencies: - dependency-name: mini-css-extract-plugin dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 1ad62aa64380..5b6b15b22845 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -86,7 +86,7 @@ "file-loader": "^6.2.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "mini-css-extract-plugin": "^2.7.6", + "mini-css-extract-plugin": "^2.7.7", "postcss": "^8.4.33", "postcss-loader": "^7.3.4", "sass": "^1.69.7", @@ -12826,9 +12826,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.7.6", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz", - "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==", + "version": "2.7.7", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.7.tgz", + "integrity": "sha512-+0n11YGyRavUR3IlaOzJ0/4Il1avMvJ1VJfhWfCn24ITQXhRr1gghbhhrda6tgtNcpZaWKdSuwKq20Jb7fnlyw==", "dev": true, "dependencies": { "schema-utils": "^4.0.0" @@ -27542,9 +27542,9 @@ } }, "mini-css-extract-plugin": { - "version": "2.7.6", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz", - "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==", + "version": "2.7.7", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.7.tgz", + "integrity": "sha512-+0n11YGyRavUR3IlaOzJ0/4Il1avMvJ1VJfhWfCn24ITQXhRr1gghbhhrda6tgtNcpZaWKdSuwKq20Jb7fnlyw==", "dev": true, "requires": { "schema-utils": "^4.0.0" diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index e47b66d1bc48..69507387c4b4 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -98,7 +98,7 @@ "file-loader": "^6.2.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "mini-css-extract-plugin": "^2.7.6", + "mini-css-extract-plugin": "^2.7.7", "postcss": "^8.4.33", "postcss-loader": "^7.3.4", "sass": "^1.69.7", From 898f316033432e25d9c3aaab968efd17dcfb45a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jan 2024 18:30:36 +0000 Subject: [PATCH 287/607] Bump sweetalert2 from 11.10.2 to 11.10.3 in /apps/block_scout_web/assets Bumps [sweetalert2](https://github.com/sweetalert2/sweetalert2) from 11.10.2 to 11.10.3. - [Release notes](https://github.com/sweetalert2/sweetalert2/releases) - [Changelog](https://github.com/sweetalert2/sweetalert2/blob/main/CHANGELOG.md) - [Commits](https://github.com/sweetalert2/sweetalert2/compare/v11.10.2...v11.10.3) --- updated-dependencies: - dependency-name: sweetalert2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 1ad62aa64380..6ff7268946cf 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -61,7 +61,7 @@ "redux": "^5.0.1", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", - "sweetalert2": "^11.10.2", + "sweetalert2": "^11.10.3", "urijs": "^1.19.11", "url": "^0.11.3", "util": "^0.12.5", @@ -16092,9 +16092,9 @@ } }, "node_modules/sweetalert2": { - "version": "11.10.2", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.2.tgz", - "integrity": "sha512-BYlIxGw6OF9Rw2z1wlnh1U+fvHHkvtg4BGyimV9nZxQRGvCBfx9uonxgwuYpJuYqCtM+2W1KOm8iMIEb/2v7Hg==", + "version": "11.10.3", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.3.tgz", + "integrity": "sha512-mZYtQR7v+khyEruq0SsVUa6XIdI9Aue8s2XAIpAwdlLN1T0w7mxKEjyubiBZ3/bLbHC/wGS4wNABvXWubCizvA==", "funding": { "type": "individual", "url": "https://github.com/sponsors/limonte" @@ -29895,9 +29895,9 @@ } }, "sweetalert2": { - "version": "11.10.2", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.2.tgz", - "integrity": "sha512-BYlIxGw6OF9Rw2z1wlnh1U+fvHHkvtg4BGyimV9nZxQRGvCBfx9uonxgwuYpJuYqCtM+2W1KOm8iMIEb/2v7Hg==" + "version": "11.10.3", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.3.tgz", + "integrity": "sha512-mZYtQR7v+khyEruq0SsVUa6XIdI9Aue8s2XAIpAwdlLN1T0w7mxKEjyubiBZ3/bLbHC/wGS4wNABvXWubCizvA==" }, "symbol-tree": { "version": "3.2.4", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index e47b66d1bc48..d7756905d321 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -73,7 +73,7 @@ "redux": "^5.0.1", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", - "sweetalert2": "^11.10.2", + "sweetalert2": "^11.10.3", "urijs": "^1.19.11", "url": "^0.11.3", "util": "^0.12.5", From 4fdbfa6293648860f84d246a6f439168302c5205 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jan 2024 08:30:26 +0000 Subject: [PATCH 288/607] Bump style-loader from 3.3.3 to 3.3.4 in /apps/block_scout_web/assets Bumps [style-loader](https://github.com/webpack-contrib/style-loader) from 3.3.3 to 3.3.4. - [Release notes](https://github.com/webpack-contrib/style-loader/releases) - [Changelog](https://github.com/webpack-contrib/style-loader/blob/v3.3.4/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/style-loader/compare/v3.3.3...v3.3.4) --- updated-dependencies: - dependency-name: style-loader dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 578883795cf6..89307537a0d0 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -91,7 +91,7 @@ "postcss-loader": "^7.3.4", "sass": "^1.69.7", "sass-loader": "^14.0.0", - "style-loader": "^3.3.3", + "style-loader": "^3.3.4", "webpack": "^5.89.0", "webpack-cli": "^5.1.4" }, @@ -15905,9 +15905,9 @@ } }, "node_modules/style-loader": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", - "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", "dev": true, "engines": { "node": ">= 12.13.0" @@ -29789,9 +29789,9 @@ "dev": true }, "style-loader": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", - "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", "dev": true, "requires": {} }, diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 2fef32adf99a..9d3778af3c9f 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -103,7 +103,7 @@ "postcss-loader": "^7.3.4", "sass": "^1.69.7", "sass-loader": "^14.0.0", - "style-loader": "^3.3.3", + "style-loader": "^3.3.4", "webpack": "^5.89.0", "webpack-cli": "^5.1.4" }, From 889814b336c0cce18b109cf97eadc7acd9c70025 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jan 2024 08:31:23 +0000 Subject: [PATCH 289/607] Bump @babel/preset-env in /apps/block_scout_web/assets Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.23.7 to 7.23.8. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.8/packages/babel-preset-env) --- updated-dependencies: - dependency-name: "@babel/preset-env" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 36 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 578883795cf6..d096f8fe7136 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -72,7 +72,7 @@ }, "devDependencies": { "@babel/core": "^7.23.7", - "@babel/preset-env": "^7.23.7", + "@babel/preset-env": "^7.23.8", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.1", @@ -1089,16 +1089,15 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", - "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz", + "integrity": "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-replace-supers": "^7.22.20", "@babel/helper-split-export-declaration": "^7.22.6", @@ -1780,9 +1779,9 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.7.tgz", - "integrity": "sha512-SY27X/GtTz/L4UryMNJ6p4fH4nsgWbz84y9FE0bQeWJP6O5BhgVCt53CotQKHCOeXJel8VyhlhujhlltKms/CA==", + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.8.tgz", + "integrity": "sha512-lFlpmkApLkEP6woIKprO6DO60RImpatTQKtz4sUcDjVcK8M8mQ4sZsuxaTMNOZf0sqAq/ReYW1ZBHnOQwKpLWA==", "dev": true, "dependencies": { "@babel/compat-data": "^7.23.5", @@ -1818,7 +1817,7 @@ "@babel/plugin-transform-block-scoping": "^7.23.4", "@babel/plugin-transform-class-properties": "^7.23.3", "@babel/plugin-transform-class-static-block": "^7.23.4", - "@babel/plugin-transform-classes": "^7.23.5", + "@babel/plugin-transform-classes": "^7.23.8", "@babel/plugin-transform-computed-properties": "^7.23.3", "@babel/plugin-transform-destructuring": "^7.23.3", "@babel/plugin-transform-dotall-regex": "^7.23.3", @@ -18600,16 +18599,15 @@ } }, "@babel/plugin-transform-classes": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", - "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz", + "integrity": "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-replace-supers": "^7.22.20", "@babel/helper-split-export-declaration": "^7.22.6", @@ -19038,9 +19036,9 @@ } }, "@babel/preset-env": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.7.tgz", - "integrity": "sha512-SY27X/GtTz/L4UryMNJ6p4fH4nsgWbz84y9FE0bQeWJP6O5BhgVCt53CotQKHCOeXJel8VyhlhujhlltKms/CA==", + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.8.tgz", + "integrity": "sha512-lFlpmkApLkEP6woIKprO6DO60RImpatTQKtz4sUcDjVcK8M8mQ4sZsuxaTMNOZf0sqAq/ReYW1ZBHnOQwKpLWA==", "dev": true, "requires": { "@babel/compat-data": "^7.23.5", @@ -19076,7 +19074,7 @@ "@babel/plugin-transform-block-scoping": "^7.23.4", "@babel/plugin-transform-class-properties": "^7.23.3", "@babel/plugin-transform-class-static-block": "^7.23.4", - "@babel/plugin-transform-classes": "^7.23.5", + "@babel/plugin-transform-classes": "^7.23.8", "@babel/plugin-transform-computed-properties": "^7.23.3", "@babel/plugin-transform-destructuring": "^7.23.3", "@babel/plugin-transform-dotall-regex": "^7.23.3", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 2fef32adf99a..fde156337dce 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -84,7 +84,7 @@ }, "devDependencies": { "@babel/core": "^7.23.7", - "@babel/preset-env": "^7.23.7", + "@babel/preset-env": "^7.23.8", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.1", From 19af21189a9b5ccb0629786c7203eca772a70e81 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Wed, 10 Jan 2024 14:06:13 +0400 Subject: [PATCH 290/607] Merge addresses stage with address referencing --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/chain/import.ex | 3 +- .../chain/import/stage/address_referencing.ex | 26 -------------- .../explorer/chain/import/stage/addresses.ex | 22 ------------ .../stage/addresses_blocks_coin_balances.ex | 34 +++++++++++++++++++ .../chain/import/stage/block_following.ex | 4 +-- .../chain/import/stage/block_pending.ex | 4 +-- .../chain/import/stage/block_referencing.ex | 3 +- 8 files changed, 39 insertions(+), 58 deletions(-) delete mode 100644 apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex delete mode 100644 apps/explorer/lib/explorer/chain/import/stage/addresses.ex create mode 100644 apps/explorer/lib/explorer/chain/import/stage/addresses_blocks_coin_balances.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index e9fc17df2c40..f5161e7410de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - [#9155](https://github.com/blockscout/blockscout/pull/9155) - Allow bypassing avg block time in proxy implementation re-fetch ttl calculation +- [#9131](https://github.com/blockscout/blockscout/pull/9131) - Merge addresses stage with address referencing - [#9072](https://github.com/blockscout/blockscout/pull/9072) - Add tracing by block logic for geth - [#9056](https://github.com/blockscout/blockscout/pull/9056) - Noves.fi API proxy diff --git a/apps/explorer/lib/explorer/chain/import.ex b/apps/explorer/lib/explorer/chain/import.ex index 138db2c676fa..9372a55c01e8 100644 --- a/apps/explorer/lib/explorer/chain/import.ex +++ b/apps/explorer/lib/explorer/chain/import.ex @@ -12,8 +12,7 @@ defmodule Explorer.Chain.Import do require Logger @stages [ - Import.Stage.Addresses, - Import.Stage.AddressReferencing, + Import.Stage.AddressesBlocksCoinBalances, Import.Stage.BlockReferencing, Import.Stage.BlockFollowing, Import.Stage.BlockPending diff --git a/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex deleted file mode 100644 index 2d40ad74fb47..000000000000 --- a/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex +++ /dev/null @@ -1,26 +0,0 @@ -defmodule Explorer.Chain.Import.Stage.AddressReferencing do - @moduledoc """ - Imports any tables that reference `t:Explorer.Chain.Address.t/0` and that were imported by - `Explorer.Chain.Import.Stage.Addresses`. - """ - - alias Explorer.Chain.Import.{Runner, Stage} - - @behaviour Stage - - @impl Stage - def runners, - do: [ - Runner.Address.CoinBalances, - Runner.Blocks, - Runner.Address.CoinBalancesDaily - ] - - @impl Stage - def multis(runner_to_changes_list, options) do - {final_multi, final_remaining_runner_to_changes_list} = - Stage.single_multi(runners(), runner_to_changes_list, options) - - {[final_multi], final_remaining_runner_to_changes_list} - end -end diff --git a/apps/explorer/lib/explorer/chain/import/stage/addresses.ex b/apps/explorer/lib/explorer/chain/import/stage/addresses.ex deleted file mode 100644 index 03c8a5772449..000000000000 --- a/apps/explorer/lib/explorer/chain/import/stage/addresses.ex +++ /dev/null @@ -1,22 +0,0 @@ -defmodule Explorer.Chain.Import.Stage.Addresses do - @moduledoc """ - Imports addresses before anything else that references them because an unused address is still valid and recoverable - if the other stage(s) don't commit. - """ - - alias Explorer.Chain.Import.{Runner, Stage} - - @behaviour Stage - - @runner Runner.Addresses - - @impl Stage - def runners, do: [@runner] - - @chunk_size 50 - - @impl Stage - def multis(runner_to_changes_list, options) do - Stage.chunk_every(runner_to_changes_list, @runner, @chunk_size, options) - end -end diff --git a/apps/explorer/lib/explorer/chain/import/stage/addresses_blocks_coin_balances.ex b/apps/explorer/lib/explorer/chain/import/stage/addresses_blocks_coin_balances.ex new file mode 100644 index 000000000000..bdd8ae478e84 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/stage/addresses_blocks_coin_balances.ex @@ -0,0 +1,34 @@ +defmodule Explorer.Chain.Import.Stage.AddressesBlocksCoinBalances do + @moduledoc """ + Import addresses, blocks and balances. + No tables have foreign key to addresses anymore, so it's possible to import addresses along with them. + """ + + alias Explorer.Chain.Import.{Runner, Stage} + + @behaviour Stage + + @addresses_runner Runner.Addresses + + @rest_runners [ + Runner.Address.CoinBalances, + Runner.Blocks, + Runner.Address.CoinBalancesDaily + ] + + @impl Stage + def runners, do: [@addresses_runner | @rest_runners] + + @addresses_chunk_size 50 + + @impl Stage + def multis(runner_to_changes_list, options) do + {addresses_multis, remaining_runner_to_changes_list} = + Stage.chunk_every(runner_to_changes_list, Runner.Addresses, @addresses_chunk_size, options) + + {final_multi, final_remaining_runner_to_changes_list} = + Stage.single_multi(@rest_runners, remaining_runner_to_changes_list, options) + + {[final_multi | addresses_multis], final_remaining_runner_to_changes_list} + end +end diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_following.ex b/apps/explorer/lib/explorer/chain/import/stage/block_following.ex index 8abbf9f79b7d..2a533c1b680b 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_following.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_following.ex @@ -1,9 +1,7 @@ defmodule Explorer.Chain.Import.Stage.BlockFollowing do @moduledoc """ Imports any tables that follows and cannot be imported at the same time as - those imported by `Explorer.Chain.Import.Stage.Addresses`, - `Explorer.Chain.Import.Stage.AddressReferencing` and - `Explorer.Chain.Import.Stage.BlockReferencing` + those imported by `Explorer.Chain.Import.Stage.AddressesBlocksCoinBalances` and `Explorer.Chain.Import.Stage.BlockReferencing` """ alias Explorer.Chain.Import.{Runner, Stage} diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_pending.ex b/apps/explorer/lib/explorer/chain/import/stage/block_pending.ex index fba315e142d4..824a4dc3ce0a 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_pending.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_pending.ex @@ -2,9 +2,7 @@ defmodule Explorer.Chain.Import.Stage.BlockPending do @moduledoc """ Imports any tables that uses `Explorer.Chain.PendingBlockOperation` to track progress and cannot be imported at the same time as those imported by - `Explorer.Chain.Import.Stage.Addresses`, - `Explorer.Chain.Import.Stage.AddressReferencing` and - `Explorer.Chain.Import.Stage.BlockReferencing` + `Explorer.Chain.Import.Stage.AddressesBlocksCoinBalances` and `Explorer.Chain.Import.Stage.BlockReferencing` """ alias Explorer.Chain.Import.{Runner, Stage} diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex index 1589c95a38cb..142a2326545a 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex @@ -1,8 +1,7 @@ defmodule Explorer.Chain.Import.Stage.BlockReferencing do @moduledoc """ Imports any tables that reference `t:Explorer.Chain.Block.t/0` and that were - imported by `Explorer.Chain.Import.Stage.Addresses` and - `Explorer.Chain.Import.Stage.AddressReferencing`. + imported by `Explorer.Chain.Import.Stage.AddressesBlocksCoinBalances`. """ alias Explorer.Chain.Import.{Runner, Stage} From 2a232c9ee45b16d7697c77542744031ef4fcd011 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jan 2024 09:34:54 +0000 Subject: [PATCH 291/607] Bump css-loader from 6.8.1 to 6.9.0 in /apps/block_scout_web/assets Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 6.8.1 to 6.9.0. - [Release notes](https://github.com/webpack-contrib/css-loader/releases) - [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/css-loader/compare/v6.8.1...v6.9.0) --- updated-dependencies: - dependency-name: css-loader dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 38 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index b26075f7f4c4..49fded9ff7b5 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -76,7 +76,7 @@ "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.1", - "css-loader": "^6.8.1", + "css-loader": "^6.9.0", "css-minimizer-webpack-plugin": "^5.0.1", "eslint": "^8.56.0", "eslint-config-standard": "^17.1.0", @@ -6237,19 +6237,19 @@ } }, "node_modules/css-loader": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", - "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.9.0.tgz", + "integrity": "sha512-3I5Nu4ytWlHvOP6zItjiHlefBNtrH+oehq8tnQa2kO305qpVyx9XNIT1CXIj5bgCJs7qICBCkgCYxQLKPANoLA==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.21", + "postcss": "^8.4.31", "postcss-modules-extract-imports": "^3.0.0", "postcss-modules-local-by-default": "^4.0.3", - "postcss-modules-scope": "^3.0.0", + "postcss-modules-scope": "^3.1.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.3.8" + "semver": "^7.5.4" }, "engines": { "node": ">= 12.13.0" @@ -14045,9 +14045,9 @@ } }, "node_modules/postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.0.tgz", + "integrity": "sha512-SaIbK8XW+MZbd0xHPf7kdfA/3eOt7vxJ72IRecn3EzuZVLr1r0orzf0MX/pN8m+NMDoo6X/SQd8oeKqGZd8PXg==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.4" @@ -22469,19 +22469,19 @@ "requires": {} }, "css-loader": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", - "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.9.0.tgz", + "integrity": "sha512-3I5Nu4ytWlHvOP6zItjiHlefBNtrH+oehq8tnQa2kO305qpVyx9XNIT1CXIj5bgCJs7qICBCkgCYxQLKPANoLA==", "dev": true, "requires": { "icss-utils": "^5.1.0", - "postcss": "^8.4.21", + "postcss": "^8.4.31", "postcss-modules-extract-imports": "^3.0.0", "postcss-modules-local-by-default": "^4.0.3", - "postcss-modules-scope": "^3.0.0", + "postcss-modules-scope": "^3.1.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.3.8" + "semver": "^7.5.4" }, "dependencies": { "semver": { @@ -28413,9 +28413,9 @@ } }, "postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.0.tgz", + "integrity": "sha512-SaIbK8XW+MZbd0xHPf7kdfA/3eOt7vxJ72IRecn3EzuZVLr1r0orzf0MX/pN8m+NMDoo6X/SQd8oeKqGZd8PXg==", "dev": true, "requires": { "postcss-selector-parser": "^6.0.4" diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 02891d831acc..86e38b7df8f5 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -88,7 +88,7 @@ "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.1", - "css-loader": "^6.8.1", + "css-loader": "^6.9.0", "css-minimizer-webpack-plugin": "^5.0.1", "eslint": "^8.56.0", "eslint-config-standard": "^17.1.0", From ec58cd83ad890777864c3638cee864a6846f6103 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Oct 2023 19:02:45 +0000 Subject: [PATCH 292/607] Bump dialyxir from 1.4.1 to 1.4.2 Bumps [dialyxir](https://github.com/jeremyjh/dialyxir) from 1.4.1 to 1.4.2. - [Release notes](https://github.com/jeremyjh/dialyxir/releases) - [Changelog](https://github.com/jeremyjh/dialyxir/blob/master/CHANGELOG.md) - [Commits](https://github.com/jeremyjh/dialyxir/compare/1.4.1...1.4.2) --- updated-dependencies: - dependency-name: dialyxir dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 619743cf93a8..026b1cfa7b29 100644 --- a/mix.lock +++ b/mix.lock @@ -34,7 +34,7 @@ "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "decorator": {:hex, :decorator, "1.4.0", "a57ac32c823ea7e4e67f5af56412d12b33274661bb7640ec7fc882f8d23ac419", [:mix], [], "hexpm", "0a07cedd9083da875c7418dea95b78361197cf2bf3211d743f6f7ce39656597f"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, - "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, + "dialyxir": {:hex, :dialyxir, "1.4.2", "764a6e8e7a354f0ba95d58418178d486065ead1f69ad89782817c296d0d746a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "516603d8067b2fd585319e4b13d3674ad4f314a5902ba8130cd97dc902ce6bbd"}, "digital_token": {:hex, :digital_token, "0.6.0", "13e6de581f0b1f6c686f7c7d12ab11a84a7b22fa79adeb4b50eec1a2d278d258", [:mix], [{:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "2455d626e7c61a128b02a4a8caddb092548c3eb613ac6f6a85e4cbb6caddc4d1"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, From e2cf1908777ce1b1bed6610f431254ab08a9376d Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 24 Oct 2023 21:15:47 +0300 Subject: [PATCH 293/607] plt_add_deps: from :transitive to :app_tree --- apps/block_scout_web/mix.exs | 2 +- apps/ethereum_jsonrpc/mix.exs | 2 +- apps/explorer/mix.exs | 2 +- mix.exs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index e90612b58202..741558064926 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -11,7 +11,7 @@ defmodule BlockScoutWeb.Mixfile do deps_path: "../../deps", description: "Web interface for BlockScout.", dialyzer: [ - plt_add_deps: :transitive, + plt_add_deps: :app_tree, ignore_warnings: "../../.dialyzer-ignore" ], elixir: "~> 1.13", diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index 3545774c33a3..2446c3e2e28b 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -11,7 +11,7 @@ defmodule EthereumJsonrpc.MixProject do deps_path: "../../deps", description: "Ethereum JSONRPC client.", dialyzer: [ - plt_add_deps: :transitive, + plt_add_deps: :app_tree, plt_add_apps: [:mix], ignore_warnings: "../../.dialyzer-ignore" ], diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index ae066dfdc75f..5f98f045ff91 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -11,7 +11,7 @@ defmodule Explorer.Mixfile do deps_path: "../../deps", description: "Read-access to indexed block chain data.", dialyzer: [ - plt_add_deps: :transitive, + plt_add_deps: :app_tree, plt_add_apps: ~w(ex_unit mix)a, ignore_warnings: "../../.dialyzer-ignore" ], diff --git a/mix.exs b/mix.exs index 06f570b70e79..cd6740114bf6 100644 --- a/mix.exs +++ b/mix.exs @@ -52,7 +52,7 @@ defmodule BlockScout.Mixfile do defp dialyzer() do [ - plt_add_deps: :transitive, + plt_add_deps: :app_tree, plt_add_apps: ~w(ex_unit mix)a, ignore_warnings: ".dialyzer-ignore", plt_core_path: "priv/plts", From d49020ef898b8d40bcfc67f15d29ad1a5deb5d9e Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Thu, 11 Jan 2024 15:43:55 +0200 Subject: [PATCH 294/607] feat: blobs migrations and api --- .../lib/block_scout_web/api_router.ex | 11 + .../lib/block_scout_web/chain.ex | 5 + .../controllers/api/v2/blob_controller.ex | 59 ++++ .../controllers/api/v2/block_controller.ex | 28 +- .../api/v2/transaction_controller.ex | 44 ++- .../block_scout_web/views/api/v2/blob_view.ex | 42 +++ .../views/api/v2/block_view.ex | 41 ++- .../views/api/v2/transaction_view.ex | 14 + .../lib/ethereum_jsonrpc/block.ex | 171 ++++++----- .../lib/ethereum_jsonrpc/blocks.ex | 27 +- .../lib/ethereum_jsonrpc/receipt.ex | 45 ++- .../lib/ethereum_jsonrpc/receipts.ex | 8 +- .../lib/ethereum_jsonrpc/transaction.ex | 269 ++++++------------ .../test/ethereum_jsonrpc/block_test.exs | 15 +- .../test/ethereum_jsonrpc/receipt_test.exs | 2 +- apps/explorer/lib/explorer/application.ex | 2 +- apps/explorer/lib/explorer/chain.ex | 38 +++ .../lib/explorer/chain/beacon/blob.ex | 36 +++ .../explorer/chain/beacon/blob_transaction.ex | 47 +++ .../lib/explorer/chain/beacon/reader.ex | 69 +++++ apps/explorer/lib/explorer/chain/block.ex | 112 +++++--- .../import/runner/beacon/blob_transactions.ex | 115 ++++++++ .../chain/import/stage/block_referencing.ex | 6 + .../lib/explorer/chain/transaction.ex | 53 ++-- apps/explorer/lib/explorer/repo.ex | 24 ++ apps/explorer/lib/explorer/sorting_helper.ex | 2 - .../20240109102458_create_blobs_tables.exs | 33 +++ apps/explorer/test/support/factory.ex | 4 +- apps/indexer/lib/indexer/block/fetcher.ex | 20 +- config/config_helper.exs | 1 + config/runtime/dev.exs | 9 + config/runtime/prod.exs | 8 + 32 files changed, 989 insertions(+), 371 deletions(-) create mode 100644 apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex create mode 100644 apps/block_scout_web/lib/block_scout_web/views/api/v2/blob_view.ex create mode 100644 apps/explorer/lib/explorer/chain/beacon/blob.ex create mode 100644 apps/explorer/lib/explorer/chain/beacon/blob_transaction.ex create mode 100644 apps/explorer/lib/explorer/chain/beacon/reader.ex create mode 100644 apps/explorer/lib/explorer/chain/import/runner/beacon/blob_transactions.ex create mode 100644 apps/explorer/priv/beacon/migrations/20240109102458_create_blobs_tables.exs diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 5f8415014aaa..35463fc9f1fb 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -217,6 +217,10 @@ defmodule BlockScoutWeb.ApiRouter do get("/:transaction_hash_param/raw-trace", V2.TransactionController, :raw_trace) get("/:transaction_hash_param/state-changes", V2.TransactionController, :state_changes) get("/:transaction_hash_param/summary", V2.TransactionController, :summary) + + if System.get_env("CHAIN_TYPE") == "ethereum" do + get("/:transaction_hash_param/blobs", V2.TransactionController, :blobs) + end end scope "/blocks" do @@ -308,6 +312,13 @@ defmodule BlockScoutWeb.ApiRouter do get("/addresses/:address_hash_param/transactions", V2.Proxy.NovesFiController, :address_transactions) end end + + scope "/blobs" do + if System.get_env("CHAIN_TYPE") == "ethereum" do + get("/", V2.BlobController, :blobs) + get("/:blob_hash_param", V2.BlobController, :blob) + end + end end scope "/v1", as: :api_v1 do diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index 8bb9d4d0eb2c..81b212859123 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -649,6 +649,11 @@ defmodule BlockScoutWeb.Chain do %{"id" => msg_id} end + # Beacon blob transactions + defp paging_params(%{block_number: block_number, index: index}) do + %{"block_number" => block_number, "index" => index} + end + @spec paging_params_with_fiat_value(CurrentTokenBalance.t()) :: %{ required(String.t()) => Decimal.t() | non_neg_integer() | nil } diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex new file mode 100644 index 000000000000..67b2db241d34 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex @@ -0,0 +1,59 @@ +defmodule BlockScoutWeb.API.V2.BlobController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Chain, + only: [ + next_page_params: 3, + paging_options: 1, + split_list_by_page: 1 + ] + + alias Explorer.Chain.Beacon.Reader + alias Explorer.Chain + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + @doc """ + Function to handle GET requests to `/api/v2/blobs/:blob_hash_param` endpoint. + """ + @spec blob(Plug.Conn.t(), map()) :: Plug.Conn.t() + def blob(conn, %{"blob_hash_param" => blob_hash_string} = _params) do + with {:format, {:ok, blob_hash}} <- {:format, Chain.string_to_transaction_hash(blob_hash_string)} do + transaction_hashes = Reader.blob_hash_to_transactions(blob_hash, api?: true) + + case Reader.blob(blob_hash, api?: true) do + {:ok, blob} -> + conn + |> put_status(200) + |> render(:blob, %{blob: blob, transaction_hashes: transaction_hashes}) + + {:error, :not_found} -> + conn + |> put_status(200) + |> render(:blob, %{transaction_hashes: transaction_hashes}) + end + end + end + + @doc """ + Function to handle GET requests to `/api/v2/blobs` endpoint. + """ + @spec blobs(Plug.Conn.t(), map()) :: Plug.Conn.t() + def blobs(conn, params) do + {blobs_transactions, next_page} = + params + |> paging_options() + |> Keyword.put(:api?, true) + |> Reader.blobs_transactions() + |> split_list_by_page() + + next_page_params = next_page_params(next_page, blobs_transactions, params) + + conn + |> put_status(200) + |> render(:blobs_transactions, %{ + blobs_transactions: blobs_transactions, + next_page_params: next_page_params + }) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex index 49e21dcce202..d4879184ca83 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex @@ -16,6 +16,16 @@ defmodule BlockScoutWeb.API.V2.BlockController do alias BlockScoutWeb.API.V2.{TransactionView, WithdrawalView} alias Explorer.Chain + case Application.compile_env(:explorer, :chain_type) do + "ethereum" -> + @chain_type_block_necessity_by_association %{ + [transactions: :beacon_blob_transaction] => :optional + } + + _ -> + @chain_type_block_necessity_by_association %{} + end + @transaction_necessity_by_association [ necessity_by_association: %{ [created_contract_address: :names] => :optional, @@ -31,14 +41,16 @@ defmodule BlockScoutWeb.API.V2.BlockController do @api_true [api?: true] @block_params [ - necessity_by_association: %{ - [miner: :names] => :optional, - :uncles => :optional, - :nephews => :optional, - :rewards => :optional, - :transactions => :optional, - :withdrawals => :optional - }, + necessity_by_association: + %{ + [miner: :names] => :optional, + :uncles => :optional, + :nephews => :optional, + :rewards => :optional, + :transactions => :optional, + :withdrawals => :optional + } + |> Map.merge(@chain_type_block_necessity_by_association), api?: true ] diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex index ff62beb9f5d8..6ace85a4890a 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -2,6 +2,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do use BlockScoutWeb, :controller import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + alias BlockScoutWeb.API.V2.{BlobView} import BlockScoutWeb.Chain, only: [ @@ -34,14 +35,26 @@ defmodule BlockScoutWeb.API.V2.TransactionController do action_fallback(BlockScoutWeb.API.V2.FallbackController) + case Application.compile_env(:explorer, :chain_type) do + "ethereum" -> + @chain_type_transaction_necessity_by_association %{ + :beacon_blob_transaction => :optional + } + + _ -> + @chain_type_transaction_necessity_by_association %{} + end + + # TODO might be redundant to preload blob fields in some of the endpoints @transaction_necessity_by_association %{ - :block => :optional, - [created_contract_address: :names] => :optional, - [created_contract_address: :token] => :optional, - [from_address: :names] => :optional, - [to_address: :names] => :optional, - [to_address: :smart_contract] => :optional - } + :block => :optional, + [created_contract_address: :names] => :optional, + [created_contract_address: :token] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + [to_address: :smart_contract] => :optional + } + |> Map.merge(@chain_type_transaction_necessity_by_association) @token_transfers_necessity_by_association %{ [from_address: :smart_contract] => :optional, @@ -389,6 +402,23 @@ defmodule BlockScoutWeb.API.V2.TransactionController do end end + @doc """ + Function to handle GET requests to `/api/v2/transactions/:transaction_hash_param/blobs` endpoint. + """ + @spec blobs(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def blobs(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do + with {:ok, _transaction, transaction_hash} <- validate_transaction(transaction_hash_string, params) do + full_options = @api_true + + blobs = Chain.transaction_to_blobs(transaction_hash, full_options) + + conn + |> put_status(200) + |> put_view(BlobView) + |> render(:blobs, %{blobs: blobs}) + end + end + @doc """ Checks if this valid transaction hash string, and this transaction doesn't belong to prohibited address """ diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/blob_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/blob_view.ex new file mode 100644 index 000000000000..9f0b508ff4a0 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/blob_view.ex @@ -0,0 +1,42 @@ +defmodule BlockScoutWeb.API.V2.BlobView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.API.V2.Helper + alias Explorer.Chain.Beacon.Blob + + def render("blob.json", %{blob: blob, transaction_hashes: transaction_hashes}) do + prepare_blob(blob) |> Map.put("transaction_hashes", transaction_hashes) + end + + def render("blob.json", %{transaction_hashes: transaction_hashes}) do + %{"transaction_hashes" => transaction_hashes} + end + + def render("blobs.json", %{blobs: blobs}) do + %{"items" => Enum.map(blobs, &prepare_blob(&1))} + end + + def render("blobs_transactions.json", %{blobs_transactions: blobs_transactions, next_page_params: next_page_params}) do + %{"items" => Enum.map(blobs_transactions, &prepare_blob_transaction(&1)), "next_page_params" => next_page_params} + end + + @spec prepare_blob(Blob.t()) :: map() + def prepare_blob(blob) do + %{ + "hash" => blob.hash, + "blob_data" => blob.blob_data, + "kzg_commitment" => blob.kzg_commitment, + "kzg_proof" => blob.kzg_proof + } + end + + @spec prepare_blob_transaction(%{block_number: non_neg_integer(), blob_hashes: [Hash.t()], transaction_hash: Hash.t()}) :: + map() + def prepare_blob_transaction(blob_transaction) do + %{ + "block_number" => blob_transaction.block_number, + "blob_hashes" => blob_transaction.blob_hashes, + "transaction_hash" => blob_transaction.transaction_hash + } + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex index 9c4b40a3516f..217b7c85514a 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex @@ -33,6 +33,19 @@ defmodule BlockScoutWeb.API.V2.BlockView do transaction_fees = Block.transaction_fees(block.transactions) + {transaction_fees, burnt_fees, blob_gas_price} = + if Application.get_env(:explorer, :chain_type) == "ethereum" do + blob_transaction_fees = Block.blob_transaction_fees(block.transactions) + + { + transaction_fees |> Decimal.add(blob_transaction_fees), + burnt_fees |> Decimal.add(blob_transaction_fees), + blob_transaction_fees |> Decimal.div(block.blob_gas_used) + } + else + {transaction_fees, burnt_fees, nil} + end + %{ "height" => block.number, "timestamp" => block.timestamp, @@ -60,7 +73,7 @@ defmodule BlockScoutWeb.API.V2.BlockView do "tx_fees" => transaction_fees, "withdrawals_count" => count_withdrawals(block) } - |> chain_type_fields(block, single_block?) + |> chain_type_fields(block, blob_gas_price, single_block?) end def prepare_rewards(rewards, block, single_block?) do @@ -106,7 +119,7 @@ defmodule BlockScoutWeb.API.V2.BlockView do def burnt_fees_percentage(burnt_fees, transaction_fees) when not is_nil(transaction_fees) and not is_nil(burnt_fees) do - burnt_fees.value |> Decimal.div(transaction_fees) |> Decimal.mult(100) |> Decimal.to_float() + burnt_fees |> Decimal.div(transaction_fees) |> Decimal.mult(100) |> Decimal.to_float() end def burnt_fees_percentage(_, _), do: nil @@ -117,15 +130,25 @@ defmodule BlockScoutWeb.API.V2.BlockView do def count_withdrawals(%Block{withdrawals: withdrawals}) when is_list(withdrawals), do: Enum.count(withdrawals) def count_withdrawals(_), do: nil - defp chain_type_fields(result, block, single_block?) do - case single_block? && Application.get_env(:explorer, :chain_type) do + defp chain_type_fields(result, block, blob_gas_price, single_block?) do + case Application.get_env(:explorer, :chain_type) do "rsk" -> + if single_block? do + result + |> Map.put("minimum_gas_price", block.minimum_gas_price) + |> Map.put("bitcoin_merged_mining_header", block.bitcoin_merged_mining_header) + |> Map.put("bitcoin_merged_mining_coinbase_transaction", block.bitcoin_merged_mining_coinbase_transaction) + |> Map.put("bitcoin_merged_mining_merkle_proof", block.bitcoin_merged_mining_merkle_proof) + |> Map.put("hash_for_merged_mining", block.hash_for_merged_mining) + else + result + end + + "ethereum" -> result - |> Map.put("minimum_gas_price", block.minimum_gas_price) - |> Map.put("bitcoin_merged_mining_header", block.bitcoin_merged_mining_header) - |> Map.put("bitcoin_merged_mining_coinbase_transaction", block.bitcoin_merged_mining_coinbase_transaction) - |> Map.put("bitcoin_merged_mining_merkle_proof", block.bitcoin_merged_mining_merkle_proof) - |> Map.put("hash_for_merged_mining", block.hash_for_merged_mining) + |> Map.put("blob_gas_used", block.blob_gas_used) + |> Map.put("excess_blob_gas", block.excess_blob_gas) + |> Map.put("blob_gas_price", blob_gas_price) _ -> result diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index a3ac1335ea45..f40b7024117c 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -452,6 +452,20 @@ defmodule BlockScoutWeb.API.V2.TransactionView do "suave" -> suave_fields(transaction, result, single_tx?, conn, watchlist_names) + # TODO this will not preload blob fields if single_tx? is false. is it ok? there is no preload in block_controller.ex for such case as well + "ethereum" -> + beacon_blob_transaction = transaction.beacon_blob_transaction + + if !is_nil(beacon_blob_transaction) do + result + |> Map.put("max_fee_per_blob_gas", beacon_blob_transaction.max_fee_per_blob_gas) + |> Map.put("blob_versioned_hashes", beacon_blob_transaction.blob_versioned_hashes) + |> Map.put("blob_gas_used", beacon_blob_transaction.blob_gas_used) + |> Map.put("blob_gas_price", beacon_blob_transaction.blob_gas_price) + else + result + end + _ -> result end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex index a3a32ddc0c9c..964d3bfe6ab3 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex @@ -8,23 +8,34 @@ defmodule EthereumJSONRPC.Block do alias EthereumJSONRPC.{Transactions, Uncles, Withdrawals} - if Application.compile_env(:explorer, :chain_type) == "rsk" do - @rootstock_fields quote( - do: [ - bitcoin_merged_mining_header: EthereumJSONRPC.data(), - bitcoin_merged_mining_coinbase_transaction: EthereumJSONRPC.data(), - bitcoin_merged_mining_merkle_proof: EthereumJSONRPC.data(), - hash_for_merged_mining: EthereumJSONRPC.data(), - minimum_gas_price: non_neg_integer() - ] - ) - else - @rootstock_fields quote(do: []) + case Application.compile_env(:explorer, :chain_type) do + "rsk" -> + @chain_type_fields quote( + do: [ + bitcoin_merged_mining_header: EthereumJSONRPC.data(), + bitcoin_merged_mining_coinbase_transaction: EthereumJSONRPC.data(), + bitcoin_merged_mining_merkle_proof: EthereumJSONRPC.data(), + hash_for_merged_mining: EthereumJSONRPC.data(), + minimum_gas_price: non_neg_integer() + ] + ) + + "ethereum" -> + @chain_type_fields quote( + do: [ + withdrawals_root: EthereumJSONRPC.hash(), + blob_gas_used: non_neg_integer(), + excess_blob_gas: non_neg_integer() + ] + ) + + _ -> + @chain_type_fields quote(do: []) end @type elixir :: %{String.t() => non_neg_integer | DateTime.t() | String.t() | nil} @type params :: %{ - unquote_splicing(@rootstock_fields), + unquote_splicing(@chain_type_fields), difficulty: pos_integer(), extra_data: EthereumJSONRPC.hash(), gas_limit: non_neg_integer(), @@ -44,8 +55,7 @@ defmodule EthereumJSONRPC.Block do total_difficulty: non_neg_integer(), transactions_root: EthereumJSONRPC.hash(), uncles: [EthereumJSONRPC.hash()], - base_fee_per_gas: non_neg_integer(), - withdrawals_root: EthereumJSONRPC.hash() + base_fee_per_gas: non_neg_integer() } @typedoc """ @@ -83,15 +93,19 @@ defmodule EthereumJSONRPC.Block do [uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block) `t:EthereumJSONRPC.hash/0`. * `"baseFeePerGas"` - `t:EthereumJSONRPC.quantity/0` of wei to denote amount of fee burnt per unit gas used. Introduced in [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) - * `"withdrawalsRoot"` - `t:EthereumJSONRPC.hash/0` of the root of the withdrawals. - #{if Application.compile_env(:explorer, :chain_type) == "rsk" do - """ - * `"minimumGasPrice"` - `t:EthereumJSONRPC.quantity/0` of the minimum gas price for this block. - * `"bitcoinMergedMiningHeader"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining header. - * `"bitcoinMergedMiningCoinbaseTransaction"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining coinbase transaction. - * `"bitcoinMergedMiningMerkleProof"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining merkle proof. - * `"hashForMergedMining"` - `t:EthereumJSONRPC.data/0` of the hash for merged mining. - """ + #{case Application.compile_env(:explorer, :chain_type) do + "rsk" -> """ + * `"minimumGasPrice"` - `t:EthereumJSONRPC.quantity/0` of the minimum gas price for this block. + * `"bitcoinMergedMiningHeader"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining header. + * `"bitcoinMergedMiningCoinbaseTransaction"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining coinbase transaction. + * `"bitcoinMergedMiningMerkleProof"` - `t:EthereumJSONRPC.data/0` of the Bitcoin merged mining merkle proof. + * `"hashForMergedMining"` - `t:EthereumJSONRPC.data/0` of the hash for merged mining. + """ + "ethereum" -> """ + * `"withdrawalsRoot"` - `t:EthereumJSONRPC.hash/0` of the root of the withdrawals. + * `"blobGasUsed"` - `t:EthereumJSONRPC.quantity/0` of the total amount of blob gas consumed by the transactions within the block. + * `"excessBlobGas"` - `t:EthereumJSONRPC.quantity/0` of the running total of blob gas consumed in excess of the target, prior to the block. + """ end} """ @type t :: %{String.t() => EthereumJSONRPC.data() | EthereumJSONRPC.hash() | EthereumJSONRPC.quantity() | nil} @@ -144,15 +158,19 @@ defmodule EthereumJSONRPC.Block do ...> "totalDifficulty" => 340282366920938463463374607431465668165, ...> "transactions" => [], ...> "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",\ - #{if Application.compile_env(:explorer, :chain_type) == "rsk" do - """ - - ...> "minimumGasPrice" => 345786, - ...> "bitcoinMergedMiningHeader" => "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9", - ...> "bitcoinMergedMiningCoinbaseTransaction" => "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000", - ...> "bitcoinMergedMiningMerkleProof" => "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7", - ...> "hashForMergedMining" => "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40",\ - """ + #{case Application.compile_env(:explorer, :chain_type) do + "rsk" -> """ + "minimumGasPrice" => 345786,\ + "bitcoinMergedMiningHeader" => "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9",\ + "bitcoinMergedMiningCoinbaseTransaction" => "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000",\ + "bitcoinMergedMiningMerkleProof" => "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7",\ + "hashForMergedMining" => "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40",\ + """ + "ethereum" -> """ + "withdrawalsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",\ + "blobGasUsed" => 262144,\ + "excessBlobGas" => 79429632,\ + """ end} ...> "uncles" => [] ...> } @@ -175,19 +193,23 @@ defmodule EthereumJSONRPC.Block do state_root: "0xc196ad59d867542ef20b29df5f418d07dc7234f4bc3d25260526620b7958a8fb", timestamp: Timex.parse!("2017-12-15T21:03:30Z", "{ISO:Extended:Z}"), total_difficulty: 340282366920938463463374607431465668165, - transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - uncles: [],\ - #{if Application.compile_env(:explorer, :chain_type) == "rsk" do - """ - - bitcoin_merged_mining_coinbase_transaction: "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000", - bitcoin_merged_mining_header: "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9", - bitcoin_merged_mining_merkle_proof: "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7", - hash_for_merged_mining: "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40", - minimum_gas_price: 345786,\ - """ + transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",\ + #{case Application.compile_env(:explorer, :chain_type) do + "rsk" -> """ + + bitcoin_merged_mining_coinbase_transaction: "0x00000000000000805bf0dc9203da49a3b4e3ec913806e43102cc07db991272dc8b7018da57eb5abe59a32d070000ffffffff03449a4d26000000001976a914536ffa992491508dca0354e52f32a3a7a679a53a88ac00000000000000002b6a2952534b424c4f434b3ad2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a400000000000000000266a24aa21a9ed4ae42ea6dca2687aaed665714bf58b055c4e11f2fb038605930d630b49ad7b9d00000000",\ + bitcoin_merged_mining_header: "0x00006d20ffd048280094a6ea0851d854036aacaa25ee0f23f0040200000000000000000078d2638fe0b4477c54601e6449051afba8228e0a88ff06b0c91f091fd34d5da57487c76402610517372c2fe9",\ + bitcoin_merged_mining_merkle_proof: "0x8e5a4ba74eb4eb2f9ad4cabc2913aeed380a5becf7cd4d513341617efb798002bd83a783c31c66a8a8f6cc56c071c2d471cb610e3dc13054b9d216021d8c7e9112f622564449ebedcedf7d4ccb6fe0ffac861b7ed1446c310813cdf712e1e6add28b1fe1c0ae5e916194ba4f285a9340aba41e91bf847bf31acf37a9623a04a2348a37ab9faa5908122db45596bbc03e9c3644b0d4589471c4ff30fc139f3ba50506e9136fa0df799b487494de3e2b3dec937338f1a2e18da057c1f60590a9723672a4355b9914b1d01af9f582d9e856f6e1744be00f268b0b01d559329f7e0685aa63ffeb7c28486d7462292021d1345cddbf7c920ca34bb7aa4c6cdbe068806e35d0db662e7fcda03cb4d779594638c62a1fdd7ec98d1fb6d240d853958abe57561d9b9d0465cf8b9d6ee3c58b0d8b07d6c4c5d8f348e43fe3c06011b6a0008db4e0b16c77ececc3981f9008201cea5939869d648e59a09bd2094b1196ff61126bffb626153deed2563e1745436247c94a85d2947756b606d67633781c99d7",\ + hash_for_merged_mining: "0xd2508d21d28c8f89d495923c0758ec3f64bd6755b4ec416f5601312600542a40",\ + minimum_gas_price: 345786,\ + """ + "ethereum" -> """ + withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",\ + blob_gas_used: 262144,\ + excess_blob_gas: 79429632,\ + """ end} - withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + uncles: [] } [Geth] `elixir` can be converted to params @@ -234,19 +256,22 @@ defmodule EthereumJSONRPC.Block do state_root: "0x6fd0a5d82ca77d9f38c3ebbde11b11d304a5fcf3854f291df64395ab38ed43ba", timestamp: Timex.parse!("2015-07-30T15:32:07Z", "{ISO:Extended:Z}"), total_difficulty: 1039309006117, - transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - uncles: [],\ - #{if Application.compile_env(:explorer, :chain_type) == "rsk" do - """ - - bitcoin_merged_mining_coinbase_transaction: nil, - bitcoin_merged_mining_header: nil, - bitcoin_merged_mining_merkle_proof: nil, - hash_for_merged_mining: nil, - minimum_gas_price: nil,\ - """ + transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",\ + #{case Application.compile_env(:explorer, :chain_type) do + "rsk" -> """ + bitcoin_merged_mining_coinbase_transaction: nil,\ + bitcoin_merged_mining_header: nil,\ + bitcoin_merged_mining_merkle_proof: nil,\ + hash_for_merged_mining: nil,\ + minimum_gas_price: nil,\ + """ + "ethereum" -> """ + withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",\ + blob_gas_used: 0,\ + excess_blob_gas: 0,\ + """ end} - withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + uncles: [] } """ @spec elixir_to_params(elixir) :: params @@ -298,9 +323,7 @@ defmodule EthereumJSONRPC.Block do total_difficulty: total_difficulty, transactions_root: transactions_root, uncles: uncles, - base_fee_per_gas: base_fee_per_gas, - withdrawals_root: - Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + base_fee_per_gas: base_fee_per_gas } end @@ -344,9 +367,7 @@ defmodule EthereumJSONRPC.Block do timestamp: timestamp, transactions_root: transactions_root, uncles: uncles, - base_fee_per_gas: base_fee_per_gas, - withdrawals_root: - Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + base_fee_per_gas: base_fee_per_gas } end @@ -390,9 +411,7 @@ defmodule EthereumJSONRPC.Block do timestamp: timestamp, total_difficulty: total_difficulty, transactions_root: transactions_root, - uncles: uncles, - withdrawals_root: - Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + uncles: uncles } end @@ -435,9 +454,7 @@ defmodule EthereumJSONRPC.Block do state_root: state_root, timestamp: timestamp, transactions_root: transactions_root, - uncles: uncles, - withdrawals_root: - Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + uncles: uncles } end @@ -453,6 +470,15 @@ defmodule EthereumJSONRPC.Block do hash_for_merged_mining: Map.get(elixir, "hashForMergedMining") }) + "ethereum" -> + params + |> Map.merge(%{ + withdrawals_root: + Map.get(elixir, "withdrawalsRoot", "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), + blob_gas_used: Map.get(elixir, "blobGasUsed", 0), + excess_blob_gas: Map.get(elixir, "excessBlobGas", 0) + }) + _ -> params end @@ -761,16 +787,11 @@ defmodule EthereumJSONRPC.Block do end defp entry_to_elixir({key, quantity}, _block) - when key in ~w(difficulty gasLimit gasUsed minimumGasPrice baseFeePerGas number size cumulativeDifficulty totalDifficulty paidFees minimumGasPrice) and + when key in ~w(difficulty gasLimit gasUsed minimumGasPrice baseFeePerGas number size cumulativeDifficulty totalDifficulty paidFees minimumGasPrice blobGasUsed excessBlobGas) and not is_nil(quantity) do {key, quantity_to_integer(quantity)} end - # to be merged with clause above ^ - defp entry_to_elixir({key, _quantity}, _block) when key in ~w(blobGasUsed excessBlobGas) do - {:ignore, :ignore} - end - # Size and totalDifficulty may be `nil` for uncle blocks defp entry_to_elixir({key, nil}, _block) when key in ~w(size totalDifficulty) do {key, nil} diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex index d9a697c1acbd..6c91104f0aa2 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex @@ -115,19 +115,22 @@ defmodule EthereumJSONRPC.Blocks do state_root: "0xfad4af258fd11939fae0c6c6eec9d340b1caac0b0196fd9a1bc3f489c5bf00b3", timestamp: Timex.parse!("1970-01-01T00:00:00Z", "{ISO:Extended:Z}"), total_difficulty: 131072, - transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - uncles: ["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273311"],\ - #{if Application.compile_env(:explorer, :chain_type) == "rsk" do - """ - - bitcoin_merged_mining_coinbase_transaction: nil, - bitcoin_merged_mining_header: nil, - bitcoin_merged_mining_merkle_proof: nil, - hash_for_merged_mining: nil, - minimum_gas_price: nil,\ - """ + transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",\ + #{case Application.compile_env(:explorer, :chain_type) do + "rsk" -> """ + bitcoin_merged_mining_coinbase_transaction: nil,\ + bitcoin_merged_mining_header: nil,\ + bitcoin_merged_mining_merkle_proof: nil,\ + hash_for_merged_mining: nil,\ + minimum_gas_price: nil,\ + """ + "ethereum" -> """ + withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",\ + blob_gas_used: 0,\ + excess_blob_gas: 0,\ + """ end} - withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + uncles: ["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273311"] } ] diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex index 08744cd2cd39..1c947c25521e 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex @@ -74,7 +74,13 @@ defmodule EthereumJSONRPC.Receipt do gas_used: 269607, status: :ok, transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", - transaction_index: 0 + transaction_index: 0,\ + #{case Application.compile_env(:explorer, :chain_type) do + "ethereum" -> """ + blob_gas_price: 0,\ + blob_gas_used: 0\ + """ + end} } Geth, when showing pre-[Byzantium](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-609.md) does not include @@ -107,7 +113,13 @@ defmodule EthereumJSONRPC.Receipt do gas_used: 21001, status: nil, transaction_hash: "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060", - transaction_index: 0 + transaction_index: 0,\ + #{case Application.compile_env(:explorer, :chain_type) do + "ethereum" -> """ + blob_gas_price: 0,\ + blob_gas_used: 0\ + """ + end} } """ @@ -119,7 +131,13 @@ defmodule EthereumJSONRPC.Receipt do transaction_hash: String.t(), transaction_index: non_neg_integer() } - def elixir_to_params( + def elixir_to_params(elixir) do + elixir + |> do_elixir_to_params() + |> chain_type_fields(elixir) + end + + def do_elixir_to_params( %{ "cumulativeGasUsed" => cumulative_gas_used, "gasUsed" => gas_used, @@ -140,6 +158,20 @@ defmodule EthereumJSONRPC.Receipt do } end + defp chain_type_fields(params, elixir) do + case Application.get_env(:explorer, :chain_type) do + "ethereum" -> + params + |> Map.merge(%{ + blob_gas_price: Map.get(elixir, "blobGasPrice", 0), + blob_gas_used: Map.get(elixir, "blobGasUsed", 0) + }) + + _ -> + params + end + end + @doc """ Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. @@ -257,7 +289,7 @@ defmodule EthereumJSONRPC.Receipt do do: {:ok, entry} defp entry_to_elixir({key, quantity}) - when key in ~w(blockNumber cumulativeGasUsed gasUsed transactionIndex) do + when key in ~w(blockNumber cumulativeGasUsed gasUsed transactionIndex blobGasUsed blobGasPrice) do result = if is_nil(quantity) do nil @@ -319,11 +351,6 @@ defmodule EthereumJSONRPC.Receipt do :ignore end - # EIP-4844 transaction receipt fields - defp entry_to_elixir({key, _}) when key in ~w(blobGasUsed blobGasPrice) do - :ignore - end - defp entry_to_elixir({key, value}) do {:error, {:unknown_key, %{key: key, value: value}}} end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex index 37cc0a963c38..1b133561d5c2 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex @@ -99,7 +99,13 @@ defmodule EthereumJSONRPC.Receipts do gas_used: 50450, status: :ok, transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - transaction_index: 0 + transaction_index: 0,\ + #{case Application.compile_env(:explorer, :chain_type) do + "ethereum" -> """ + blob_gas_price: 0,\ + blob_gas_used: 0\ + """ + end} } ] diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex index 2f11da852095..d9a0b809e207 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex @@ -11,6 +11,39 @@ defmodule EthereumJSONRPC.Transaction do alias EthereumJSONRPC + case Application.compile_env(:explorer, :chain_type) do + "ethereum" -> + @chain_type_fields quote( + do: [ + max_fee_per_blob_gas: non_neg_integer(), + blob_versioned_hashes: [EthereumJSONRPC.hash()] + ] + ) + + "suave" -> + @chain_type_fields quote( + do: [ + execution_node_hash: EthereumJSONRPC.address(), + wrapped_type: non_neg_integer(), + wrapped_nonce: non_neg_integer(), + wrapped_to_address_hash: EthereumJSONRPC.address(), + wrapped_gas: non_neg_integer(), + wrapped_gas_price: non_neg_integer(), + wrapped_max_priority_fee_per_gas: non_neg_integer(), + wrapped_max_fee_per_gas: non_neg_integer(), + wrapped_value: non_neg_integer(), + wrapped_input: String.t(), + wrapped_v: non_neg_integer(), + wrapped_r: non_neg_integer(), + wrapped_s: non_neg_integer(), + wrapped_hash: EthereumJSONRPC.hash() + ] + ) + + _ -> + @chain_type_fields quote(do: []) + end + @type elixir :: %{ String.t() => EthereumJSONRPC.address() | EthereumJSONRPC.hash() | String.t() | non_neg_integer() | nil } @@ -42,8 +75,16 @@ defmodule EthereumJSONRPC.Transaction do * `"maxPriorityFeePerGas"` - `t:EthereumJSONRPC.quantity/0` of wei to denote max priority fee per unit of gas used. Introduced in [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) * `"maxFeePerGas"` - `t:EthereumJSONRPC.quantity/0` of wei to denote max fee per unit of gas used. Introduced in [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) * `"type"` - `t:EthereumJSONRPC.quantity/0` denotes transaction type. Introduced in [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) - * `"executionNode"` - `t:EthereumJSONRPC.address/0` of execution node (used by Suave). - * `"requestRecord"` - map of wrapped transaction data (used by Suave). + #{case Application.compile_env(:explorer, :chain_type) do + "ethereum" -> """ + * `"maxFeePerBlobGas"` - `t:EthereumJSONRPC.quantity/0` of wei to denote max fee per unit of blob gas used. Introduced in [EIP-4844](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4844.md) + * `"blobVersionedHashes"` - `t:list/0` of `t:EthereumJSONRPC.hash/0` of included data blobs hashes. Introduced in [EIP-4844](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4844.md) + """ + "suave" -> """ + * `"executionNode"` - `t:EthereumJSONRPC.address/0` of execution node (used by Suave). + * `"requestRecord"` - map of wrapped transaction data (used by Suave). + """ + end} """ @type t :: %{ String.t() => @@ -51,6 +92,7 @@ defmodule EthereumJSONRPC.Transaction do } @type params :: %{ + unquote_splicing(@chain_type_fields), block_hash: EthereumJSONRPC.hash(), block_number: non_neg_integer(), from_address_hash: EthereumJSONRPC.address(), @@ -68,21 +110,7 @@ defmodule EthereumJSONRPC.Transaction do transaction_index: non_neg_integer(), max_priority_fee_per_gas: non_neg_integer(), max_fee_per_gas: non_neg_integer(), - type: non_neg_integer(), - execution_node_hash: EthereumJSONRPC.address(), - wrapped_type: non_neg_integer(), - wrapped_nonce: non_neg_integer(), - wrapped_to_address_hash: EthereumJSONRPC.address(), - wrapped_gas: non_neg_integer(), - wrapped_gas_price: non_neg_integer(), - wrapped_max_priority_fee_per_gas: non_neg_integer(), - wrapped_max_fee_per_gas: non_neg_integer(), - wrapped_value: non_neg_integer(), - wrapped_input: String.t(), - wrapped_v: non_neg_integer(), - wrapped_r: non_neg_integer(), - wrapped_s: non_neg_integer(), - wrapped_hash: EthereumJSONRPC.hash() + type: non_neg_integer() } @doc """ @@ -168,82 +196,13 @@ defmodule EthereumJSONRPC.Transaction do } """ @spec elixir_to_params(elixir) :: params - - # this is for Suave chain (handles `executionNode` and `requestRecord` fields along with EIP-1559 fields) - def elixir_to_params( - %{ - "blockHash" => block_hash, - "blockNumber" => block_number, - "from" => from_address_hash, - "gas" => gas, - "gasPrice" => gas_price, - "hash" => hash, - "input" => input, - "nonce" => nonce, - "r" => r, - "s" => s, - "to" => to_address_hash, - "transactionIndex" => index, - "v" => v, - "value" => value, - "type" => type, - "maxPriorityFeePerGas" => max_priority_fee_per_gas, - "maxFeePerGas" => max_fee_per_gas, - "executionNode" => execution_node_hash, - "requestRecord" => wrapped - } = transaction - ) do - result = %{ - block_hash: block_hash, - block_number: block_number, - from_address_hash: from_address_hash, - gas: gas, - gas_price: gas_price, - hash: hash, - index: index, - input: input, - nonce: nonce, - r: r, - s: s, - to_address_hash: to_address_hash, - v: v, - value: value, - transaction_index: index, - type: type, - max_priority_fee_per_gas: max_priority_fee_per_gas, - max_fee_per_gas: max_fee_per_gas - } - - # credo:disable-for-next-line - result = - if Application.get_env(:explorer, :chain_type) == "suave" do - Map.merge(result, %{ - execution_node_hash: execution_node_hash, - wrapped_type: quantity_to_integer(Map.get(wrapped, "type")), - wrapped_nonce: quantity_to_integer(Map.get(wrapped, "nonce")), - wrapped_to_address_hash: Map.get(wrapped, "to"), - wrapped_gas: quantity_to_integer(Map.get(wrapped, "gas")), - wrapped_gas_price: quantity_to_integer(Map.get(wrapped, "gasPrice")), - wrapped_max_priority_fee_per_gas: quantity_to_integer(Map.get(wrapped, "maxPriorityFeePerGas")), - wrapped_max_fee_per_gas: quantity_to_integer(Map.get(wrapped, "maxFeePerGas")), - wrapped_value: quantity_to_integer(Map.get(wrapped, "value")), - wrapped_input: Map.get(wrapped, "input"), - wrapped_v: quantity_to_integer(Map.get(wrapped, "v")), - wrapped_r: quantity_to_integer(Map.get(wrapped, "r")), - wrapped_s: quantity_to_integer(Map.get(wrapped, "s")), - wrapped_hash: Map.get(wrapped, "hash") - }) - else - result - end - - put_if_present(transaction, result, [ - {"creates", :created_contract_address_hash}, - {"block_timestamp", :block_timestamp} - ]) + def elixir_to_params(elixir) do + elixir + |> do_elixir_to_params() + |> chain_type_fields(elixir) end - def elixir_to_params( + def do_elixir_to_params( %{ "blockHash" => block_hash, "blockNumber" => block_number, @@ -293,7 +252,7 @@ defmodule EthereumJSONRPC.Transaction do # txpool_content method on Erigon node returns tx data # without gas price - def elixir_to_params( + def do_elixir_to_params( %{ "blockHash" => block_hash, "blockNumber" => block_number, @@ -340,77 +299,7 @@ defmodule EthereumJSONRPC.Transaction do ]) end - # this is for Suave chain (handles `executionNode` and `requestRecord` fields without EIP-1559 fields) - def elixir_to_params( - %{ - "blockHash" => block_hash, - "blockNumber" => block_number, - "from" => from_address_hash, - "gas" => gas, - "gasPrice" => gas_price, - "hash" => hash, - "input" => input, - "nonce" => nonce, - "r" => r, - "s" => s, - "to" => to_address_hash, - "transactionIndex" => index, - "v" => v, - "value" => value, - "type" => type, - "executionNode" => execution_node_hash, - "requestRecord" => wrapped - } = transaction - ) do - result = %{ - block_hash: block_hash, - block_number: block_number, - from_address_hash: from_address_hash, - gas: gas, - gas_price: gas_price, - hash: hash, - index: index, - input: input, - nonce: nonce, - r: r, - s: s, - to_address_hash: to_address_hash, - v: v, - value: value, - transaction_index: index, - type: type - } - - # credo:disable-for-next-line - result = - if Application.get_env(:explorer, :chain_type) == "suave" do - Map.merge(result, %{ - execution_node_hash: execution_node_hash, - wrapped_type: quantity_to_integer(Map.get(wrapped, "type")), - wrapped_nonce: quantity_to_integer(Map.get(wrapped, "nonce")), - wrapped_to_address_hash: Map.get(wrapped, "to"), - wrapped_gas: quantity_to_integer(Map.get(wrapped, "gas")), - wrapped_gas_price: quantity_to_integer(Map.get(wrapped, "gasPrice")), - wrapped_max_priority_fee_per_gas: quantity_to_integer(Map.get(wrapped, "maxPriorityFeePerGas")), - wrapped_max_fee_per_gas: quantity_to_integer(Map.get(wrapped, "maxFeePerGas")), - wrapped_value: quantity_to_integer(Map.get(wrapped, "value")), - wrapped_input: Map.get(wrapped, "input"), - wrapped_v: quantity_to_integer(Map.get(wrapped, "v")), - wrapped_r: quantity_to_integer(Map.get(wrapped, "r")), - wrapped_s: quantity_to_integer(Map.get(wrapped, "s")), - wrapped_hash: Map.get(wrapped, "hash") - }) - else - result - end - - put_if_present(transaction, result, [ - {"creates", :created_contract_address_hash}, - {"block_timestamp", :block_timestamp} - ]) - end - - def elixir_to_params( + def do_elixir_to_params( %{ "blockHash" => block_hash, "blockNumber" => block_number, @@ -454,7 +343,7 @@ defmodule EthereumJSONRPC.Transaction do ]) end - def elixir_to_params( + def do_elixir_to_params( %{ "blockHash" => block_hash, "blockNumber" => block_number, @@ -496,6 +385,44 @@ defmodule EthereumJSONRPC.Transaction do ]) end + defp chain_type_fields(params, elixir) do + case Application.get_env(:explorer, :chain_type) do + "ethereum" -> + put_if_present(elixir, params, [ + {"blobVersionedHashes", :blob_versioned_hashes}, + {"maxFeePerBlobGas", :max_fee_per_blob_gas} + ]) + + "suave" -> + wrapped = Map.get(elixir, "requestRecord") + + if is_nil(wrapped) do + params + else + params + |> Map.merge(%{ + execution_node_hash: Map.get(elixir, "executionNode"), + wrapped_type: quantity_to_integer(Map.get(wrapped, "type")), + wrapped_nonce: quantity_to_integer(Map.get(wrapped, "nonce")), + wrapped_to_address_hash: Map.get(wrapped, "to"), + wrapped_gas: quantity_to_integer(Map.get(wrapped, "gas")), + wrapped_gas_price: quantity_to_integer(Map.get(wrapped, "gasPrice")), + wrapped_max_priority_fee_per_gas: quantity_to_integer(Map.get(wrapped, "maxPriorityFeePerGas")), + wrapped_max_fee_per_gas: quantity_to_integer(Map.get(wrapped, "maxFeePerGas")), + wrapped_value: quantity_to_integer(Map.get(wrapped, "value")), + wrapped_input: Map.get(wrapped, "input"), + wrapped_v: quantity_to_integer(Map.get(wrapped, "v")), + wrapped_r: quantity_to_integer(Map.get(wrapped, "r")), + wrapped_s: quantity_to_integer(Map.get(wrapped, "s")), + wrapped_hash: Map.get(wrapped, "hash") + }) + end + + _ -> + params + end + end + @doc """ Extracts `t:EthereumJSONRPC.hash/0` from transaction `params` @@ -605,7 +532,7 @@ defmodule EthereumJSONRPC.Transaction do # # "txType": to avoid FunctionClauseError when indexing Wanchain defp entry_to_elixir({key, value}) - when key in ~w(blockHash condition creates from hash input jsonrpc publicKey raw to txType executionNode requestRecord), + when key in ~w(blockHash condition creates from hash input jsonrpc publicKey raw to txType executionNode requestRecord blobVersionedHashes), do: {key, value} # specific to Nethermind client @@ -613,21 +540,11 @@ defmodule EthereumJSONRPC.Transaction do do: {"input", value} defp entry_to_elixir({key, quantity}) - when key in ~w(gas gasPrice nonce r s standardV v value type maxPriorityFeePerGas maxFeePerGas) and + when key in ~w(gas gasPrice nonce r s standardV v value type maxPriorityFeePerGas maxFeePerGas maxFeePerBlobGas) and quantity != nil do {key, quantity_to_integer(quantity)} end - # to be merged with the clause above ^ - defp entry_to_elixir({"maxFeePerBlobGas", _value}) do - {nil, nil} - end - - # EIP-4844 specific field with value of type of list of hashes - defp entry_to_elixir({"blobVersionedHashes", _value}) do - {nil, nil} - end - # as always ganache has it's own vision on JSON RPC standard defp entry_to_elixir({key, nil}) when key in ~w(r s v) do {key, 0} diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs index f8d4ecd00172..100ed3756e3e 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs @@ -53,8 +53,7 @@ defmodule EthereumJSONRPC.BlockTest do timestamp: Timex.parse!("2015-07-30T15:32:07Z", "{ISO:Extended:Z}"), total_difficulty: nil, transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - uncles: [], - withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + uncles: [] } |> (&if(Application.get_env(:explorer, :chain_type) == "rsk", do: @@ -70,6 +69,18 @@ defmodule EthereumJSONRPC.BlockTest do ), else: &1 )).() + |> (&if(Application.get_env(:explorer, :chain_type) == "ethereum", + do: + Map.merge( + &1, + %{ + withdrawals_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + blob_gas_used: 0, + excess_blob_gas: 0 + } + ), + else: &1 + )).() end end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs index b5ee4ae2efec..0c7feed17f29 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs @@ -15,7 +15,7 @@ defmodule EthereumJSONRPC.ReceiptTest do %{"new_key" => "new_value", "transactionHash" => "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060"} Errors: - {:unknown_key, %{key: "new_key", value: "new_value"}} + {:unknown_key, %{value: "new_value", key: "new_key"}} """, fn -> Receipt.to_elixir(%{ diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 52b196489d07..47b508db86a3 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -138,7 +138,7 @@ defmodule Explorer.Application do defp repos_by_chain_type do if Mix.env() == :test do - [Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Suave] + [Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Suave, Explorer.Repo.Beacon] else [] end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index e61f9cf6ca22..79cdda47d9bb 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -66,6 +66,7 @@ defmodule Explorer.Chain do Withdrawal } + alias Explorer.Chain.Beacon.{BlobTransaction, Blob} alias Explorer.Chain.Block.{EmissionReward, Reward} alias Explorer.Chain.Cache.{ @@ -2896,6 +2897,43 @@ defmodule Explorer.Chain do |> select_repo(options).all() end + @doc """ + Finds all `t:Explorer.Chain.Beacon.Blob.t/0`s for `t:Explorer.Chain.Transaction.t/0`. + + ## Options + + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + `:required`, and the `t:Explorer.Chain.Log.t/0` has no associated record for that association, then the + `t:Explorer.Chain.Beacon.Blob.t/0` will not be included in the page `entries`. + + """ + @spec transaction_to_blobs(Hash.Full.t(), [necessity_by_association_option | api?]) :: [Blob.t()] + def transaction_to_blobs(transaction_hash, options \\ []) when is_list(options) do + query = + from( + transaction_blob in subquery( + from( + blob_transaction in BlobTransaction, + select: %{ + hash: fragment("unnest(blob_versioned_hashes)") + }, + where: blob_transaction.hash == ^transaction_hash + ) + ), + left_join: blob in Blob, + on: blob.hash == transaction_blob.hash, + select: %{ + hash: type(transaction_blob.hash, Hash.Full), + blob_data: blob.blob_data, + kzg_commitment: blob.kzg_commitment, + kzg_proof: blob.kzg_proof + } + ) + + query + |> select_repo(options).all() + end + @doc """ Converts `transaction` to the status of the `t:Explorer.Chain.Transaction.t/0` whether pending or collated. diff --git a/apps/explorer/lib/explorer/chain/beacon/blob.ex b/apps/explorer/lib/explorer/chain/beacon/blob.ex new file mode 100644 index 000000000000..f016d3ca3b2f --- /dev/null +++ b/apps/explorer/lib/explorer/chain/beacon/blob.ex @@ -0,0 +1,36 @@ +defmodule Explorer.Chain.Beacon.Blob do + @moduledoc "Models a data blob broadcasted using eip4844 blob transactions." + + use Explorer.Schema + + alias Explorer.Chain.Hash + + @required_attrs ~w(hash blob_data kzg_commitment kzg_proof)a + + @type t :: %__MODULE__{ + hash: Hash.t(), + blob_data: binary(), + kzg_commitment: binary(), + kzg_proof: binary() + } + + @primary_key {:hash, Hash.Full, autogenerate: false} + schema "beacon_blobs" do + field(:blob_data, :binary) + field(:kzg_commitment, :binary) + field(:kzg_proof, :binary) + + timestamps(updated_at: false) + end + + @doc """ + Validates that the `attrs` are valid. + """ + @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() + def changeset(%__MODULE__{} = struct, attrs \\ %{}) do + struct + |> cast(attrs, @required_attrs) + |> validate_required(@required_attrs) + |> unique_constraint(:hash) + end +end diff --git a/apps/explorer/lib/explorer/chain/beacon/blob_transaction.ex b/apps/explorer/lib/explorer/chain/beacon/blob_transaction.ex new file mode 100644 index 000000000000..59e3d400fefb --- /dev/null +++ b/apps/explorer/lib/explorer/chain/beacon/blob_transaction.ex @@ -0,0 +1,47 @@ +defmodule Explorer.Chain.Beacon.BlobTransaction do + @moduledoc "Models a transaction extension with extra fields from eip4844 blob transactions." + + use Explorer.Schema + + alias Explorer.Chain.{Hash, Transaction} + + @required_attrs ~w(hash max_fee_per_blob_gas blob_gas_price blob_gas_used blob_versioned_hashes)a + + @type t :: %__MODULE__{ + hash: Hash.t(), + max_fee_per_blob_gas: Decimal.t(), + blob_gas_price: Decimal.t(), + blob_gas_used: Decimal.t(), + blob_versioned_hashes: [Hash.t()] + } + + @primary_key {:hash, Hash.Full, autogenerate: false} + schema "beacon_blobs_transactions" do + field(:max_fee_per_blob_gas, :decimal) + field(:blob_gas_price, :decimal) + field(:blob_gas_used, :decimal) + field(:blob_versioned_hashes, {:array, Hash.Full}) + + belongs_to(:transaction, Transaction, + foreign_key: :hash, + primary_key: true, + references: :hash, + type: Hash.Full, + define_field: false + ) + + timestamps() + end + + @doc """ + Validates that the `attrs` are valid. + """ + @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() + def changeset(%__MODULE__{} = struct, attrs \\ %{}) do + struct + |> cast(attrs, @required_attrs) + |> validate_required(@required_attrs) + |> foreign_key_constraint(:hash) + |> unique_constraint(:hash) + end +end diff --git a/apps/explorer/lib/explorer/chain/beacon/reader.ex b/apps/explorer/lib/explorer/chain/beacon/reader.ex new file mode 100644 index 000000000000..43d760e18b88 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/beacon/reader.ex @@ -0,0 +1,69 @@ +defmodule Explorer.Chain.Beacon.Reader do + @moduledoc "Contains read functions for beacon chain related modules." + + import Ecto.Query, + only: [ + from: 2, + limit: 2, + order_by: 3, + where: 2, + where: 3, + join: 5, + select: 3 + ] + + import Explorer.Chain, only: [select_repo: 1] + + alias Explorer.Chain.Beacon.{BlobTransaction, Blob} + alias Explorer.{Chain, PagingOptions, Repo} + alias Explorer.Chain.{Transaction, Hash} + + def blob(hash, options) when is_list(options) do + Blob + |> where(hash: ^hash) + |> select_repo(options).one() + |> case do + nil -> {:error, :not_found} + batch -> {:ok, batch} + end + end + + def blob_hash_to_transactions(hash, options) when is_list(options) do + BlobTransaction + |> where(type(^hash, Hash.Full) == fragment("any(blob_versioned_hashes)")) + |> join(:inner, [bt], transaction in Transaction, on: bt.hash == transaction.hash) + |> order_by([bt, transaction], desc: transaction.block_consensus, desc: transaction.block_number) + |> limit(10) + |> select([bt, transaction], %{ + block_consensus: transaction.block_consensus, + transaction_hash: transaction.hash + }) + |> select_repo(options).all() + end + + def blobs_transactions(options) when is_list(options) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + + BlobTransaction + |> join(:inner, [bt], transaction in Transaction, on: bt.hash == transaction.hash) + |> where([bt, transaction], transaction.type == 3) + |> order_by([bt, transaction], desc: transaction.block_number, desc: transaction.index) + |> page_blobs_transactions(paging_options) + |> limit(^paging_options.page_size) + |> select([bt, transaction], %{ + block_number: transaction.block_number, + index: transaction.index, + blob_hashes: bt.blob_versioned_hashes, + transaction_hash: bt.hash + }) + |> select_repo(options).all() + end + + defp page_blobs_transactions(query, %PagingOptions{key: nil}), do: query + + defp page_blobs_transactions(query, %PagingOptions{key: {block_number, index}}) do + from([bt, transaction] in query, + where: fragment("(?, ?) <= (?, ?)", transaction.block_number, transaction.index, ^block_number, ^index) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index fae356f3bcfa..3b373e8f6d8a 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -17,6 +17,10 @@ defmodule Explorer.Chain.Block do &1 ++ ~w(minimum_gas_price bitcoin_merged_mining_header bitcoin_merged_mining_coinbase_transaction bitcoin_merged_mining_merkle_proof hash_for_merged_mining)a + "ethereum" -> + &1 ++ + ~w(blob_gas_used excess_blob_gas)a + _ -> &1 end)).() @@ -35,18 +39,28 @@ defmodule Explorer.Chain.Block do """ @type block_number :: non_neg_integer() - if Application.compile_env(:explorer, :chain_type) == "rsk" do - @rootstock_fields quote( - do: [ - bitcoin_merged_mining_header: binary(), - bitcoin_merged_mining_coinbase_transaction: binary(), - bitcoin_merged_mining_merkle_proof: binary(), - hash_for_merged_mining: binary(), - minimum_gas_price: Decimal.t() - ] - ) - else - @rootstock_fields quote(do: []) + case Application.compile_env(:explorer, :chain_type) do + "rsk" -> + @chain_type_fields quote( + do: [ + bitcoin_merged_mining_header: binary(), + bitcoin_merged_mining_coinbase_transaction: binary(), + bitcoin_merged_mining_merkle_proof: binary(), + hash_for_merged_mining: binary(), + minimum_gas_price: Decimal.t() + ] + ) + + "ethereum" -> + @chain_type_fields quote( + do: [ + blob_gas_used: Decimal.t(), + excess_blob_gas: Decimal.t() + ] + ) + + _ -> + @chain_type_fields quote(do: []) end @typedoc """ @@ -70,18 +84,22 @@ defmodule Explorer.Chain.Block do * `total_difficulty` - the total `difficulty` of the chain until this block. * `transactions` - the `t:Explorer.Chain.Transaction.t/0` in this block. * `base_fee_per_gas` - Minimum fee required per unit of gas. Fee adjusts based on network congestion. - #{if Application.compile_env(:explorer, :chain_type) == "rsk" do - """ - * `bitcoin_merged_mining_header` - Bitcoin merged mining header on Rootstock chains. - * `bitcoin_merged_mining_coinbase_transaction` - Bitcoin merged mining coinbase transaction on Rootstock chains. - * `bitcoin_merged_mining_merkle_proof` - Bitcoin merged mining merkle proof on Rootstock chains. - * `hash_for_merged_mining` - Hash for merged mining on Rootstock chains. - * `minimum_gas_price` - Minimum block gas price on Rootstock chains. - """ + #{case Application.compile_env(:explorer, :chain_type) do + "rsk" -> """ + * `bitcoin_merged_mining_header` - Bitcoin merged mining header on Rootstock chains. + * `bitcoin_merged_mining_coinbase_transaction` - Bitcoin merged mining coinbase transaction on Rootstock chains. + * `bitcoin_merged_mining_merkle_proof` - Bitcoin merged mining merkle proof on Rootstock chains. + * `hash_for_merged_mining` - Hash for merged mining on Rootstock chains. + * `minimum_gas_price` - Minimum block gas price on Rootstock chains. + """ + "ethereum" -> """ + * `blob_gas_used` - The total amount of blob gas consumed by the transactions within the block. + * `excess_blob_gas` - The running total of blob gas consumed in excess of the target, prior to the block. + """ end} """ @type t :: %__MODULE__{ - unquote_splicing(@rootstock_fields), + unquote_splicing(@chain_type_fields), consensus: boolean(), difficulty: difficulty(), gas_limit: Gas.t(), @@ -116,12 +134,17 @@ defmodule Explorer.Chain.Block do field(:base_fee_per_gas, Wei) field(:is_empty, :boolean) - if Application.compile_env(:explorer, :chain_type) == "rsk" do - field(:bitcoin_merged_mining_header, :binary) - field(:bitcoin_merged_mining_coinbase_transaction, :binary) - field(:bitcoin_merged_mining_merkle_proof, :binary) - field(:hash_for_merged_mining, :binary) - field(:minimum_gas_price, :decimal) + case Application.compile_env(:explorer, :chain_type) do + "rsk" -> + field(:bitcoin_merged_mining_header, :binary) + field(:bitcoin_merged_mining_coinbase_transaction, :binary) + field(:bitcoin_merged_mining_merkle_proof, :binary) + field(:hash_for_merged_mining, :binary) + field(:minimum_gas_price, :decimal) + + "ethereum" -> + field(:blob_gas_used, :decimal) + field(:excess_blob_gas, :decimal) end timestamps() @@ -238,6 +261,23 @@ defmodule Explorer.Chain.Block do end) end + @doc """ + Calculates blob transaction fees (gas price * gas used) for the list of transactions (from a single block) + """ + @spec blob_transaction_fees([Transaction.t()]) :: Decimal.t() + def blob_transaction_fees(transactions) do + Enum.reduce(transactions, Decimal.new(0), fn %{beacon_blob_transaction: beacon_blob_transaction}, acc -> + if !is_nil(beacon_blob_transaction) do + beacon_blob_transaction.blob_gas_used + |> Decimal.new() + |> Decimal.mult(gas_price_to_decimal(beacon_blob_transaction.blob_gas_price)) + |> Decimal.add(acc) + else + acc + end + end) + end + defp gas_price_to_decimal(nil), do: nil defp gas_price_to_decimal(%Wei{} = wei), do: wei.value defp gas_price_to_decimal(gas_price), do: Decimal.new(gas_price) @@ -245,26 +285,21 @@ defmodule Explorer.Chain.Block do @doc """ Calculates burnt fees for the list of transactions (from a single block) """ - @spec burnt_fees(list(), Wei.t() | nil) :: Wei.t() | nil + @spec burnt_fees(list(), Decimal.t() | nil) :: Decimal.t() def burnt_fees(transactions, base_fee_per_gas) do - total_gas_used = + if is_nil(base_fee_per_gas) do + Decimal.new(0) + else transactions |> Enum.reduce(Decimal.new(0), fn %{gas_used: gas_used}, acc -> gas_used |> Decimal.new() |> Decimal.add(acc) end) - - if is_nil(base_fee_per_gas) do - nil - else - Wei.mult(base_fee_per_gas_to_wei(base_fee_per_gas), total_gas_used) + |> Decimal.add(gas_price_to_decimal(base_fee_per_gas)) end end - defp base_fee_per_gas_to_wei(%Wei{} = wei), do: wei - defp base_fee_per_gas_to_wei(base_fee_per_gas), do: %Wei{value: Decimal.new(base_fee_per_gas)} - @uncle_reward_coef 1 / 32 @spec block_reward_by_parts(Block.t(), [Transaction.t()]) :: %{ block_number: block_number(), @@ -295,13 +330,14 @@ defmodule Explorer.Chain.Block do burnt_fees = burnt_fees(transactions, base_fee_per_gas) uncle_reward = (has_uncles? && Wei.mult(static_reward, Decimal.from_float(@uncle_reward_coef))) || nil + # eip4844 blob transactions don't impact validator rewards, so we don't count them here as part of transaction_fees and burnt_fees %{ block_number: block_number, block_hash: block_hash, miner_hash: block.miner_hash, static_reward: static_reward, transaction_fees: %Wei{value: transaction_fees}, - burnt_fees: burnt_fees || %Wei{value: Decimal.new(0)}, + burnt_fees: %Wei{value: burnt_fees}, uncle_reward: uncle_reward || %Wei{value: Decimal.new(0)} } end diff --git a/apps/explorer/lib/explorer/chain/import/runner/beacon/blob_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/beacon/blob_transactions.ex new file mode 100644 index 000000000000..48c4b70bf4c8 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/beacon/blob_transactions.ex @@ -0,0 +1,115 @@ +defmodule Explorer.Chain.Import.Runner.Beacon.BlobTransactions do + @moduledoc """ + Bulk imports `t:Explorer.Chain.Beacon.BlobTransaction.t/0`. + """ + + require Ecto.Query + + import Ecto.Query, only: [from: 2] + + alias Explorer.Chain.Beacon.BlobTransaction + alias Ecto.{Multi, Repo} + alias Explorer.Chain.{Block, Hash, Import} + alias Explorer.Prometheus.Instrumenter + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [Hash.Full.t()] + + @impl Import.Runner + def ecto_schema_module, do: BlobTransaction + + @impl Import.Runner + def option_key, do: :beacon_blob_transactions + + @impl Import.Runner + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + # Enforce ShareLocks tables order (see docs: sharelocks.md) + multi + |> Multi.run(:beacon_blob_transactions, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :beacon_blob_transactions, + :beacon_blob_transactions + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{ + optional(:on_conflict) => Import.Runner.on_conflict(), + required(:timeout) => timeout, + required(:timestamps) => Import.timestamps() + }) :: {:ok, [Hash.t()]} + defp insert( + repo, + changes_list, + %{ + timeout: timeout, + timestamps: timestamps + } = options + ) + when is_list(changes_list) do + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) + + # Enforce Transaction ShareLocks order (see docs: sharelocks.md) + ordered_changes_list = Enum.sort_by(changes_list, & &1.hash) + + Import.insert_changes_list( + repo, + ordered_changes_list, + conflict_target: :hash, + on_conflict: on_conflict, + for: BlobTransaction, + returning: true, + timeout: timeout, + timestamps: timestamps + ) + end + + defp default_on_conflict do + from( + blob_transaction in BlobTransaction, + update: [ + set: [ + max_fee_per_blob_gas: fragment("EXCLUDED.max_fee_per_blob_gas"), + blob_versioned_hashes: fragment("EXCLUDED.blob_versioned_hashes"), + blob_gas_used: fragment("EXCLUDED.blob_gas_used"), + blob_gas_price: fragment("EXCLUDED.blob_gas_price"), + # Don't update `hash` as it is part of the primary key and used for the conflict target + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", blob_transaction.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", blob_transaction.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.max_fee_per_blob_gas, EXCLUDED.blob_versioned_hashes, EXCLUDED.blob_gas_used, EXCLUDED.blob_gas_price) IS DISTINCT FROM (?, ?, ?, ?)", + blob_transaction.max_fee_per_blob_gas, + blob_transaction.blob_versioned_hashes, + blob_transaction.blob_gas_used, + blob_transaction.blob_gas_price + ) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex index 142a2326545a..890a2cfdb89e 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex @@ -38,6 +38,12 @@ defmodule Explorer.Chain.Import.Stage.BlockReferencing do Runner.Zkevm.BatchTransactions ] + "ethereum" -> + @default_runners ++ + [ + Runner.Beacon.BlobTransactions + ] + _ -> @default_runners end diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 10b730d6bcd7..6bafaabb3554 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -30,6 +30,7 @@ defmodule Explorer.Chain.Transaction do Wei } + alias Explorer.Chain.Beacon.BlobTransaction alias Explorer.Chain.Block.Reward alias Explorer.Chain.SmartContract.Proxy alias Explorer.Chain.Transaction.{Fork, Status} @@ -208,30 +209,37 @@ defmodule Explorer.Chain.Transaction do transaction_fee_log: any(), transaction_fee_token: any() }, - suave + chain_type_fields ) - if Application.compile_env(:explorer, :chain_type) == "suave" do - @type suave :: %{ - execution_node: %Ecto.Association.NotLoaded{} | Address.t() | nil, - execution_node_hash: Hash.Address.t() | nil, - wrapped_type: non_neg_integer() | nil, - wrapped_nonce: non_neg_integer() | nil, - wrapped_to_address: %Ecto.Association.NotLoaded{} | Address.t() | nil, - wrapped_to_address_hash: Hash.Address.t() | nil, - wrapped_gas: Gas.t() | nil, - wrapped_gas_price: wei_per_gas | nil, - wrapped_max_priority_fee_per_gas: wei_per_gas | nil, - wrapped_max_fee_per_gas: wei_per_gas | nil, - wrapped_value: Wei.t() | nil, - wrapped_input: Data.t() | nil, - wrapped_v: v() | nil, - wrapped_r: r() | nil, - wrapped_s: s() | nil, - wrapped_hash: Hash.t() | nil - } - else - @type suave :: %{} + case Application.compile_env(:explorer, :chain_type) do + "suave" -> + @type chain_type_fields :: %{ + execution_node: %Ecto.Association.NotLoaded{} | Address.t() | nil, + execution_node_hash: Hash.Address.t() | nil, + wrapped_type: non_neg_integer() | nil, + wrapped_nonce: non_neg_integer() | nil, + wrapped_to_address: %Ecto.Association.NotLoaded{} | Address.t() | nil, + wrapped_to_address_hash: Hash.Address.t() | nil, + wrapped_gas: Gas.t() | nil, + wrapped_gas_price: wei_per_gas | nil, + wrapped_max_priority_fee_per_gas: wei_per_gas | nil, + wrapped_max_fee_per_gas: wei_per_gas | nil, + wrapped_value: Wei.t() | nil, + wrapped_input: Data.t() | nil, + wrapped_v: v() | nil, + wrapped_r: r() | nil, + wrapped_s: s() | nil, + wrapped_hash: Hash.t() | nil + } + + "ethereum" -> + @type chain_type_fields :: %{ + beacon_blob_transaction: %Ecto.Association.NotLoaded{} | BlobTransaction.t() + } + + _ -> + @type chain_type_fields :: %{} end @derive {Poison.Encoder, @@ -344,6 +352,7 @@ defmodule Explorer.Chain.Transaction do has_one(:zkevm_batch, through: [:zkevm_batch_transaction, :batch]) has_one(:zkevm_sequence_transaction, through: [:zkevm_batch, :sequence_transaction]) has_one(:zkevm_verify_transaction, through: [:zkevm_batch, :verify_transaction]) + has_one(:beacon_blob_transaction, BlobTransaction, foreign_key: :hash) belongs_to( :created_contract_address, diff --git a/apps/explorer/lib/explorer/repo.ex b/apps/explorer/lib/explorer/repo.ex index c4d0c8f3f489..2edc6696d689 100644 --- a/apps/explorer/lib/explorer/repo.ex +++ b/apps/explorer/lib/explorer/repo.ex @@ -274,4 +274,28 @@ defmodule Explorer.Repo do {:ok, Keyword.put(opts, :url, db_url)} end end + + defmodule Beacon do + use Ecto.Repo, + otp_app: :explorer, + adapter: Ecto.Adapters.Postgres + + def init(_, opts) do + db_url = Application.get_env(:explorer, __MODULE__)[:url] + repo_conf = Application.get_env(:explorer, __MODULE__) + + merged = + %{url: db_url} + |> ConfigHelper.get_db_config() + |> Keyword.merge(repo_conf, fn + _key, v1, nil -> v1 + _key, nil, v2 -> v2 + _, _, v2 -> v2 + end) + + Application.put_env(:explorer, __MODULE__, merged) + + {:ok, Keyword.put(opts, :url, db_url)} + end + end end diff --git a/apps/explorer/lib/explorer/sorting_helper.ex b/apps/explorer/lib/explorer/sorting_helper.ex index f6a478d85d78..1c0e064b2e13 100644 --- a/apps/explorer/lib/explorer/sorting_helper.ex +++ b/apps/explorer/lib/explorer/sorting_helper.ex @@ -9,8 +9,6 @@ defmodule Explorer.SortingHelper do specifies name of a key in paging_options and arbitrary dynamic that will be used in ordering and pagination, third entry specifies own column name to order by and paginate. """ - require Explorer.SortingHelper - alias Explorer.PagingOptions import Ecto.Query diff --git a/apps/explorer/priv/beacon/migrations/20240109102458_create_blobs_tables.exs b/apps/explorer/priv/beacon/migrations/20240109102458_create_blobs_tables.exs new file mode 100644 index 000000000000..93e6343460b0 --- /dev/null +++ b/apps/explorer/priv/beacon/migrations/20240109102458_create_blobs_tables.exs @@ -0,0 +1,33 @@ +defmodule Explorer.Repo.Beacon.Migrations.CreateBlobsTables do + use Ecto.Migration + + def change do + create table(:beacon_blobs_transactions, primary_key: false) do + add(:hash, :bytea, null: false, primary_key: true) + add(:max_fee_per_blob_gas, :numeric, precision: 100, null: false) + add(:blob_gas_price, :numeric, precision: 100, null: false) + add(:blob_gas_used, :numeric, precision: 100, null: false) + add(:blob_versioned_hashes, {:array, :bytea}, null: false) + + timestamps(null: false, type: :utc_datetime_usec) + end + + alter table(:blocks) do + add(:blob_gas_used, :numeric, precision: 100, null: false) + add(:excess_blob_gas, :numeric, precision: 100, null: false) + end + + create table(:beacon_blobs, primary_key: false) do + add(:hash, references(:transactions, column: :hash, on_delete: :delete_all, type: :bytea), + null: false, + primary_key: true + ) + + add(:blob_data, :bytea, null: true) + add(:kzg_commitment, :bytea, null: true) + add(:kzg_proof, :bytea, null: true) + + timestamps(updated_at: false, null: false, type: :utc_datetime_usec) + end + end +end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 5fdd286ee056..2a8cf968026c 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -65,8 +65,8 @@ defmodule Explorer.Factory do end def auth_factory do - %Auth{ - info: %Info{ + %{ + info: %{ birthday: nil, description: nil, email: sequence(:email, &"test_user-#{&1}@blockscout.com"), diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 11261231247d..e91ed140f770 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -196,12 +196,20 @@ defmodule Indexer.Block.Fetcher do token_instances: %{params: token_instances} }, import_options = - (if Application.get_env(:explorer, :chain_type) == "polygon_edge" do - basic_import_options - |> Map.put_new(:polygon_edge_withdrawals, %{params: polygon_edge_withdrawals}) - |> Map.put_new(:polygon_edge_deposit_executes, %{params: polygon_edge_deposit_executes}) - else - basic_import_options + (case Application.get_env(:explorer, :chain_type) do + "polygon_edge" -> + basic_import_options + |> Map.put_new(:polygon_edge_withdrawals, %{params: polygon_edge_withdrawals}) + |> Map.put_new(:polygon_edge_deposit_executes, %{params: polygon_edge_deposit_executes}) + + "ethereum" -> + basic_import_options + |> Map.put_new(:beacon_blob_transactions, %{ + params: transactions_with_receipts |> Enum.filter(&Map.has_key?(&1, :max_fee_per_blob_gas)) + }) + + _ -> + basic_import_options end), {:ok, inserted} <- __MODULE__.import( diff --git a/config/config_helper.exs b/config/config_helper.exs index d5f843bc3c70..cac87c611f46 100644 --- a/config/config_helper.exs +++ b/config/config_helper.exs @@ -10,6 +10,7 @@ defmodule ConfigHelper do case System.get_env("CHAIN_TYPE") do "polygon_edge" -> base_repos ++ [Explorer.Repo.PolygonEdge] "polygon_zkevm" -> base_repos ++ [Explorer.Repo.PolygonZkevm] + "ethereum" -> base_repos ++ [Explorer.Repo.Beacon] "rsk" -> base_repos ++ [Explorer.Repo.RSK] "suave" -> base_repos ++ [Explorer.Repo.Suave] _ -> base_repos diff --git a/config/runtime/dev.exs b/config/runtime/dev.exs index 3e4df28fb71e..8818a8a82df1 100644 --- a/config/runtime/dev.exs +++ b/config/runtime/dev.exs @@ -101,6 +101,15 @@ config :explorer, Explorer.Repo.RSK, # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type pool_size: 1 +# Configure Beacon Chain database +config :explorer, Explorer.Repo.Beacon, + database: database, + hostname: hostname, + url: System.get_env("DATABASE_URL"), + # actually this repo is not started, and its pool size remains unused. + # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type + pool_size: 1 + # Configure Suave database config :explorer, Explorer.Repo.Suave, database: database, diff --git a/config/runtime/prod.exs b/config/runtime/prod.exs index ce4602522a1d..62b3bbfebcf0 100644 --- a/config/runtime/prod.exs +++ b/config/runtime/prod.exs @@ -75,6 +75,14 @@ config :explorer, Explorer.Repo.RSK, pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() +# Configure Beacon Chain database +config :explorer, Explorer.Repo.Beacon, + url: System.get_env("DATABASE_URL"), + # actually this repo is not started, and its pool size remains unused. + # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type + pool_size: 1, + ssl: ExplorerConfigHelper.ssl_enabled?() + # Configures Suave database config :explorer, Explorer.Repo.Suave, url: ExplorerConfigHelper.get_suave_db_url(), From 00a1dfb0d30d7b378074fb03a1e09a9eb985da87 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 16 Jan 2024 00:48:43 +0400 Subject: [PATCH 295/607] feat: add blobs fetcher --- .../lib/block_scout_web/api_router.ex | 1 - .../lib/block_scout_web/chain.ex | 5 - .../controllers/api/v2/blob_controller.ex | 24 +-- .../api/v2/transaction_controller.ex | 2 +- .../lib/block_scout_web/paging_helper.ex | 23 ++- .../block_scout_web/views/api/v2/blob_view.ex | 22 +-- .../views/api/v2/block_view.ex | 10 +- .../views/api/v2/transaction_view.ex | 31 ++-- apps/explorer/lib/explorer/chain.ex | 11 +- .../lib/explorer/chain/beacon/reader.ex | 65 ++++--- apps/explorer/lib/explorer/chain/block.ex | 6 +- .../chain/import/stage/block_referencing.ex | 1 + .../20240109102458_create_blobs_tables.exs | 13 +- .../lib/indexer/block/catchup/fetcher.ex | 2 + apps/indexer/lib/indexer/block/fetcher.ex | 10 ++ .../lib/indexer/block/realtime/fetcher.ex | 2 + .../lib/indexer/fetcher/beacon/blob.ex | 160 ++++++++++++++++++ .../lib/indexer/fetcher/beacon/client.ex | 77 +++++++++ apps/indexer/lib/indexer/supervisor.ex | 1 + config/runtime.exs | 15 ++ 20 files changed, 387 insertions(+), 94 deletions(-) create mode 100644 apps/indexer/lib/indexer/fetcher/beacon/blob.ex create mode 100644 apps/indexer/lib/indexer/fetcher/beacon/client.ex diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 35463fc9f1fb..277c8457bb33 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -315,7 +315,6 @@ defmodule BlockScoutWeb.ApiRouter do scope "/blobs" do if System.get_env("CHAIN_TYPE") == "ethereum" do - get("/", V2.BlobController, :blobs) get("/:blob_hash_param", V2.BlobController, :blob) end end diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index 81b212859123..8bb9d4d0eb2c 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -649,11 +649,6 @@ defmodule BlockScoutWeb.Chain do %{"id" => msg_id} end - # Beacon blob transactions - defp paging_params(%{block_number: block_number, index: index}) do - %{"block_number" => block_number, "index" => index} - end - @spec paging_params_with_fiat_value(CurrentTokenBalance.t()) :: %{ required(String.t()) => Decimal.t() | non_neg_integer() | nil } diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex index 67b2db241d34..a4817dbf2808 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex @@ -8,8 +8,8 @@ defmodule BlockScoutWeb.API.V2.BlobController do split_list_by_page: 1 ] - alias Explorer.Chain.Beacon.Reader alias Explorer.Chain + alias Explorer.Chain.Beacon.Reader action_fallback(BlockScoutWeb.API.V2.FallbackController) @@ -34,26 +34,4 @@ defmodule BlockScoutWeb.API.V2.BlobController do end end end - - @doc """ - Function to handle GET requests to `/api/v2/blobs` endpoint. - """ - @spec blobs(Plug.Conn.t(), map()) :: Plug.Conn.t() - def blobs(conn, params) do - {blobs_transactions, next_page} = - params - |> paging_options() - |> Keyword.put(:api?, true) - |> Reader.blobs_transactions() - |> split_list_by_page() - - next_page_params = next_page_params(next_page, blobs_transactions, params) - - conn - |> put_status(200) - |> render(:blobs_transactions, %{ - blobs_transactions: blobs_transactions, - next_page_params: next_page_params - }) - end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex index 6ace85a4890a..ed249d58aa29 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -2,7 +2,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do use BlockScoutWeb, :controller import BlockScoutWeb.Account.AuthController, only: [current_user: 1] - alias BlockScoutWeb.API.V2.{BlobView} + alias BlockScoutWeb.API.V2.BlobView import BlockScoutWeb.Chain, only: [ diff --git a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex index 406f47b6c761..7f8d1970f000 100644 --- a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex @@ -9,7 +9,28 @@ defmodule BlockScoutWeb.PagingHelper do @page_size 50 @default_paging_options %PagingOptions{page_size: @page_size + 1} @allowed_filter_labels ["validated", "pending"] - @allowed_type_labels ["coin_transfer", "contract_call", "contract_creation", "token_transfer", "token_creation"] + + case Application.compile_env(:explorer, :chain_type) do + "ethereum" -> + @allowed_type_labels [ + "coin_transfer", + "contract_call", + "contract_creation", + "token_transfer", + "token_creation", + "blob_transaction" + ] + + _ -> + @allowed_type_labels [ + "coin_transfer", + "contract_call", + "contract_creation", + "token_transfer", + "token_creation" + ] + end + @allowed_token_transfer_type_labels ["ERC-20", "ERC-721", "ERC-1155"] @allowed_nft_token_type_labels ["ERC-721", "ERC-1155"] diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/blob_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/blob_view.ex index 9f0b508ff4a0..16595206a6a2 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/blob_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/blob_view.ex @@ -5,7 +5,7 @@ defmodule BlockScoutWeb.API.V2.BlobView do alias Explorer.Chain.Beacon.Blob def render("blob.json", %{blob: blob, transaction_hashes: transaction_hashes}) do - prepare_blob(blob) |> Map.put("transaction_hashes", transaction_hashes) + blob |> prepare_blob() |> Map.put("transaction_hashes", transaction_hashes) end def render("blob.json", %{transaction_hashes: transaction_hashes}) do @@ -16,27 +16,17 @@ defmodule BlockScoutWeb.API.V2.BlobView do %{"items" => Enum.map(blobs, &prepare_blob(&1))} end - def render("blobs_transactions.json", %{blobs_transactions: blobs_transactions, next_page_params: next_page_params}) do - %{"items" => Enum.map(blobs_transactions, &prepare_blob_transaction(&1)), "next_page_params" => next_page_params} - end - @spec prepare_blob(Blob.t()) :: map() def prepare_blob(blob) do %{ "hash" => blob.hash, - "blob_data" => blob.blob_data, - "kzg_commitment" => blob.kzg_commitment, - "kzg_proof" => blob.kzg_proof + "blob_data" => encode_binary(blob.blob_data), + "kzg_commitment" => encode_binary(blob.kzg_commitment), + "kzg_proof" => encode_binary(blob.kzg_proof) } end - @spec prepare_blob_transaction(%{block_number: non_neg_integer(), blob_hashes: [Hash.t()], transaction_hash: Hash.t()}) :: - map() - def prepare_blob_transaction(blob_transaction) do - %{ - "block_number" => blob_transaction.block_number, - "blob_hashes" => blob_transaction.blob_hashes, - "transaction_hash" => blob_transaction.transaction_hash - } + defp encode_binary(binary) do + "0x" <> Base.encode16(binary, case: :lower) end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex index 217b7c85514a..238e1719b6e5 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex @@ -28,22 +28,22 @@ defmodule BlockScoutWeb.API.V2.BlockView do end def prepare_block(block, _conn, single_block? \\ false) do - burnt_fees = Block.burnt_fees(block.transactions, block.base_fee_per_gas) + burnt_fees_execution = Block.burnt_fees(block.transactions, block.base_fee_per_gas) priority_fee = block.base_fee_per_gas && BlockPriorityFeeCounter.fetch(block.hash) - transaction_fees = Block.transaction_fees(block.transactions) + transaction_fees_execution = Block.transaction_fees(block.transactions) {transaction_fees, burnt_fees, blob_gas_price} = if Application.get_env(:explorer, :chain_type) == "ethereum" do blob_transaction_fees = Block.blob_transaction_fees(block.transactions) { - transaction_fees |> Decimal.add(blob_transaction_fees), - burnt_fees |> Decimal.add(blob_transaction_fees), + transaction_fees_execution |> Decimal.add(blob_transaction_fees), + burnt_fees_execution |> Decimal.add(blob_transaction_fees), blob_transaction_fees |> Decimal.div(block.blob_gas_used) } else - {transaction_fees, burnt_fees, nil} + {transaction_fees_execution, burnt_fees_execution, nil} end %{ diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index f40b7024117c..4078ac8996e2 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -434,13 +434,13 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end defp chain_type_fields(result, transaction, single_tx?, conn, watchlist_names) do - case single_tx? && Application.get_env(:explorer, :chain_type) do - "polygon_edge" -> + case {single_tx?, Application.get_env(:explorer, :chain_type)} do + {true, "polygon_edge"} -> result |> Map.put("polygon_edge_deposit", polygon_edge_deposit(transaction.hash, conn)) |> Map.put("polygon_edge_withdrawal", polygon_edge_withdrawal(transaction.hash, conn)) - "polygon_zkevm" -> + {true, "polygon_zkevm"} -> extended_result = result |> add_optional_transaction_field(transaction, "zkevm_batch_number", :zkevm_batch, :number) @@ -449,21 +449,20 @@ defmodule BlockScoutWeb.API.V2.TransactionView do Map.put(extended_result, "zkevm_status", zkevm_status(extended_result)) - "suave" -> + {true, "suave"} -> suave_fields(transaction, result, single_tx?, conn, watchlist_names) - # TODO this will not preload blob fields if single_tx? is false. is it ok? there is no preload in block_controller.ex for such case as well - "ethereum" -> + {_, "ethereum"} -> beacon_blob_transaction = transaction.beacon_blob_transaction - if !is_nil(beacon_blob_transaction) do + if is_nil(beacon_blob_transaction) or beacon_blob_transaction == %Ecto.Association.NotLoaded{} do + result + else result |> Map.put("max_fee_per_blob_gas", beacon_blob_transaction.max_fee_per_blob_gas) |> Map.put("blob_versioned_hashes", beacon_blob_transaction.blob_versioned_hashes) |> Map.put("blob_gas_used", beacon_blob_transaction.blob_gas_used) |> Map.put("blob_gas_price", beacon_blob_transaction.blob_gas_price) - else - result end _ -> @@ -777,7 +776,19 @@ defmodule BlockScoutWeb.API.V2.TransactionView do | :rootstock_remasc | :token_creation | :token_transfer - def tx_types(tx, types \\ [], stage \\ :token_transfer) + | :blob_transaction + def tx_types(tx, types \\ [], stage \\ :blob_transaction) + + def tx_types(%Transaction{type: type} = tx, types, :blob_transaction) do + types = + if type == 3 do + [:blob_transaction | types] + else + types + end + + tx_types(tx, types, :token_transfer) + end def tx_types(%Transaction{token_transfers: token_transfers} = tx, types, :token_transfer) do types = diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 79cdda47d9bb..ea1bde90f67f 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -66,7 +66,7 @@ defmodule Explorer.Chain do Withdrawal } - alias Explorer.Chain.Beacon.{BlobTransaction, Blob} + alias Explorer.Chain.Beacon.{Blob, BlobTransaction} alias Explorer.Chain.Block.{EmissionReward, Reward} alias Explorer.Chain.Cache.{ @@ -5028,6 +5028,11 @@ defmodule Explorer.Chain do as: :created_token ) ) + + :blob_transaction -> + dynamic + |> filter_blob_transaction_dynamic() + |> apply_filter_by_tx_type_to_transactions_inner(remain, query) end end @@ -5061,6 +5066,10 @@ defmodule Explorer.Chain do dynamic([tx, created_token: created_token], ^dynamic or not is_nil(created_token)) end + def filter_blob_transaction_dynamic(dynamic) do + dynamic([tx], ^dynamic or tx.type == 3) + end + def count_verified_contracts do Repo.aggregate(SmartContract, :count, timeout: :infinity) end diff --git a/apps/explorer/lib/explorer/chain/beacon/reader.ex b/apps/explorer/lib/explorer/chain/beacon/reader.ex index 43d760e18b88..477239f22fd9 100644 --- a/apps/explorer/lib/explorer/chain/beacon/reader.ex +++ b/apps/explorer/lib/explorer/chain/beacon/reader.ex @@ -3,6 +3,8 @@ defmodule Explorer.Chain.Beacon.Reader do import Ecto.Query, only: [ + subquery: 1, + preload: 2, from: 2, limit: 2, order_by: 3, @@ -14,9 +16,9 @@ defmodule Explorer.Chain.Beacon.Reader do import Explorer.Chain, only: [select_repo: 1] - alias Explorer.Chain.Beacon.{BlobTransaction, Blob} + alias Explorer.Chain.Beacon.{Blob, BlobTransaction} alias Explorer.{Chain, PagingOptions, Repo} - alias Explorer.Chain.{Transaction, Hash} + alias Explorer.Chain.{Hash, Transaction} def blob(hash, options) when is_list(options) do Blob @@ -41,29 +43,48 @@ defmodule Explorer.Chain.Beacon.Reader do |> select_repo(options).all() end - def blobs_transactions(options) when is_list(options) do - paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + def stream_missed_blob_transactions_timestamps(min_block, max_block, initial, reducer, options \\ []) + when is_list(options) do + query = + from( + transaction_blob in subquery( + from( + blob_transaction in BlobTransaction, + select: %{ + transaction_hash: blob_transaction.hash, + blob_hash: fragment("unnest(blob_versioned_hashes)") + } + ) + ), + inner_join: transaction in Transaction, + on: transaction_blob.transaction_hash == transaction.hash, + where: transaction.type == 3, + left_join: blob in Blob, + on: blob.hash == transaction_blob.blob_hash, + where: is_nil(blob.hash), + distinct: transaction.block_timestamp, + select: transaction.block_timestamp + ) - BlobTransaction - |> join(:inner, [bt], transaction in Transaction, on: bt.hash == transaction.hash) - |> where([bt, transaction], transaction.type == 3) - |> order_by([bt, transaction], desc: transaction.block_number, desc: transaction.index) - |> page_blobs_transactions(paging_options) - |> limit(^paging_options.page_size) - |> select([bt, transaction], %{ - block_number: transaction.block_number, - index: transaction.index, - blob_hashes: bt.blob_versioned_hashes, - transaction_hash: bt.hash - }) - |> select_repo(options).all() + query + |> add_min_block_filter(min_block) + |> add_max_block_filter(min_block) + |> Repo.stream_reduce(initial, reducer) end - defp page_blobs_transactions(query, %PagingOptions{key: nil}), do: query + defp add_min_block_filter(query, block_number) do + if is_integer(block_number) do + query |> where([_, transaction], transaction.block_number <= ^block_number) + else + query + end + end - defp page_blobs_transactions(query, %PagingOptions{key: {block_number, index}}) do - from([bt, transaction] in query, - where: fragment("(?, ?) <= (?, ?)", transaction.block_number, transaction.index, ^block_number, ^index) - ) + defp add_max_block_filter(query, block_number) do + if is_integer(block_number) and block_number > 0 do + query |> where([_, transaction], transaction.block_number >= ^block_number) + else + query + end end end diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index 3b373e8f6d8a..9049cd576d27 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -267,13 +267,13 @@ defmodule Explorer.Chain.Block do @spec blob_transaction_fees([Transaction.t()]) :: Decimal.t() def blob_transaction_fees(transactions) do Enum.reduce(transactions, Decimal.new(0), fn %{beacon_blob_transaction: beacon_blob_transaction}, acc -> - if !is_nil(beacon_blob_transaction) do + if is_nil(beacon_blob_transaction) do + acc + else beacon_blob_transaction.blob_gas_used |> Decimal.new() |> Decimal.mult(gas_price_to_decimal(beacon_blob_transaction.blob_gas_price)) |> Decimal.add(acc) - else - acc end end) end diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex index 890a2cfdb89e..8bad68a55d78 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex @@ -39,6 +39,7 @@ defmodule Explorer.Chain.Import.Stage.BlockReferencing do ] "ethereum" -> + # credo:disable-for-next-line @default_runners ++ [ Runner.Beacon.BlobTransactions diff --git a/apps/explorer/priv/beacon/migrations/20240109102458_create_blobs_tables.exs b/apps/explorer/priv/beacon/migrations/20240109102458_create_blobs_tables.exs index 93e6343460b0..e1f70098da99 100644 --- a/apps/explorer/priv/beacon/migrations/20240109102458_create_blobs_tables.exs +++ b/apps/explorer/priv/beacon/migrations/20240109102458_create_blobs_tables.exs @@ -3,7 +3,11 @@ defmodule Explorer.Repo.Beacon.Migrations.CreateBlobsTables do def change do create table(:beacon_blobs_transactions, primary_key: false) do - add(:hash, :bytea, null: false, primary_key: true) + add(:hash, references(:transactions, column: :hash, on_delete: :delete_all, type: :bytea), + null: false, + primary_key: true + ) + add(:max_fee_per_blob_gas, :numeric, precision: 100, null: false) add(:blob_gas_price, :numeric, precision: 100, null: false) add(:blob_gas_used, :numeric, precision: 100, null: false) @@ -18,16 +22,13 @@ defmodule Explorer.Repo.Beacon.Migrations.CreateBlobsTables do end create table(:beacon_blobs, primary_key: false) do - add(:hash, references(:transactions, column: :hash, on_delete: :delete_all, type: :bytea), - null: false, - primary_key: true - ) + add(:hash, :bytea, null: false, primary_key: true) add(:blob_data, :bytea, null: true) add(:kzg_commitment, :bytea, null: true) add(:kzg_proof, :bytea, null: true) - timestamps(updated_at: false, null: false, type: :utc_datetime_usec) + timestamps(updated_at: false, null: false, type: :utc_datetime_usec, default: fragment("now()")) end end end diff --git a/apps/indexer/lib/indexer/block/catchup/fetcher.ex b/apps/indexer/lib/indexer/block/catchup/fetcher.ex index 3b5fcbbc31fa..f0486eacfd9b 100644 --- a/apps/indexer/lib/indexer/block/catchup/fetcher.ex +++ b/apps/indexer/lib/indexer/block/catchup/fetcher.ex @@ -9,6 +9,7 @@ defmodule Indexer.Block.Catchup.Fetcher do import Indexer.Block.Fetcher, only: [ + async_import_blobs: 1, async_import_block_rewards: 1, async_import_coin_balances: 2, async_import_created_contract_codes: 1, @@ -163,6 +164,7 @@ defmodule Indexer.Block.Catchup.Fetcher do async_import_uncles(imported) async_import_replaced_transactions(imported) async_import_token_instances(imported) + async_import_blobs(imported) end defp stream_fetch_and_import(state, sequence) diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index e91ed140f770..d7177c83b8ee 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -19,6 +19,7 @@ defmodule Indexer.Block.Fetcher do alias Indexer.Fetcher.TokenInstance.Realtime, as: TokenInstanceRealtime alias Indexer.Fetcher.{ + Beacon.Blob, BlockReward, CoinBalance, ContractCode, @@ -384,6 +385,15 @@ defmodule Indexer.Block.Fetcher do def async_import_replaced_transactions(_), do: :ok + def async_import_blobs(%{blocks: blocks}) do + timestamps = + blocks + |> Enum.filter(fn %{blob_gas_used: blob_gas_used} -> blob_gas_used > 0 end) + |> Enum.map(&Map.get(&1, :timestamp)) + + Blob.async_fetch(timestamps) + end + defp block_reward_errors_to_block_numbers(block_reward_errors) when is_list(block_reward_errors) do Enum.map(block_reward_errors, &block_reward_error_to_block_number/1) end diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index 9f989d017f96..cc934fabf23a 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -13,6 +13,7 @@ defmodule Indexer.Block.Realtime.Fetcher do import Indexer.Block.Fetcher, only: [ + async_import_blobs: 1, async_import_block_rewards: 1, async_import_created_contract_codes: 1, async_import_internal_transactions: 1, @@ -429,6 +430,7 @@ defmodule Indexer.Block.Realtime.Fetcher do async_import_token_instances(imported) async_import_uncles(imported) async_import_replaced_transactions(imported) + async_import_blobs(imported) end defp balances( diff --git a/apps/indexer/lib/indexer/fetcher/beacon/blob.ex b/apps/indexer/lib/indexer/fetcher/beacon/blob.ex new file mode 100644 index 000000000000..b33b4ea6e0b3 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/beacon/blob.ex @@ -0,0 +1,160 @@ +defmodule Indexer.Fetcher.Beacon.Blob do + @moduledoc """ + Fills beacon_blobs DB table. + """ + + use Indexer.Fetcher, restart: :permanent + use Spandex.Decorators + + require Logger + + alias Explorer.Repo + alias Explorer.Chain.{Data, Hash} + alias Explorer.Chain.Beacon.{Blob, Reader} + alias Indexer.{BufferedTask, Tracer} + alias Indexer.Fetcher.Beacon.Blob.Supervisor, as: BlobSupervisor + alias Indexer.Fetcher.Beacon.Client + + @behaviour BufferedTask + + @default_max_batch_size 10 + @default_max_concurrency 1 + + @doc """ + Asynchronously fetches blobs for given `block_timestamp`. + """ + def async_fetch(block_timestamps) do + if BlobSupervisor.disabled?() do + :ok + else + BufferedTask.buffer(__MODULE__, block_timestamps |> Enum.map(&entry/1)) + end + end + + @spec child_spec([...]) :: %{ + :id => any(), + :start => {atom(), atom(), list()}, + optional(:modules) => :dynamic | [atom()], + optional(:restart) => :permanent | :temporary | :transient, + optional(:shutdown) => :brutal_kill | :infinity | non_neg_integer(), + optional(:significant) => boolean(), + optional(:type) => :supervisor | :worker + } + @doc false + # credo:disable-for-next-line Credo.Check.Design.DuplicatedCode + def child_spec([init_options, gen_server_options]) do + state = + :indexer + |> Application.get_env(__MODULE__) + |> Keyword.take([:start_block, :end_block, :reference_slot, :reference_timestamp, :slot_duration]) + |> Enum.into(%{}) + + merged_init_options = + defaults() + |> Keyword.merge(init_options) + |> Keyword.put(:state, state) + + Supervisor.child_spec({BufferedTask, [{__MODULE__, merged_init_options}, gen_server_options]}, id: __MODULE__) + end + + @impl BufferedTask + def init(initial, reducer, state) do + {:ok, final} = + Reader.stream_missed_blob_transactions_timestamps( + state.start_block, + state.end_block, + initial, + fn fields, acc -> + fields + |> entry() + |> reducer.(acc) + end + ) + + final + end + + @impl BufferedTask + @decorate trace( + name: "fetch", + resource: "Indexer.Fetcher.Beacon.Blob.run/2", + service: :indexer, + tracer: Tracer + ) + def run(entries, state) do + entry_count = Enum.count(entries) + Logger.metadata(count: entry_count) + + Logger.debug(fn -> "fetching" end) + + entries + |> Enum.map(×tamp_to_slot(&1, state)) + |> Client.get_blob_sidecars() + |> case do + {:ok, fetched_blobs, retries} -> + run_fetched_blobs(fetched_blobs) + + if Enum.empty?(retries) do + :ok + else + {:retry, retries |> Enum.map(&Enum.at(entries, &1))} + end + end + end + + defp entry(block_timestamp) do + DateTime.to_unix(block_timestamp) + end + + defp timestamp_to_slot(block_timestamp, %{ + reference_timestamp: reference_timestamp, + reference_slot: reference_slot, + slot_duration: slot_duration + }) do + ((block_timestamp - reference_timestamp) |> div(slot_duration)) + reference_slot + end + + defp run_fetched_blobs(fetched_blobs) do + blobs = + fetched_blobs + |> Enum.flat_map(fn %{"data" => blobs} -> blobs end) + |> Enum.map(&blob_entry/1) + + Repo.insert_all(Blob, blobs, on_conflict: :nothing, conflict_target: [:hash]) + end + + def blob_entry(%{ + "blob" => blob, + "kzg_commitment" => kzg_commitment, + "kzg_proof" => kzg_proof + }) do + {:ok, kzg_commitment} = Data.cast(kzg_commitment) + {:ok, blob} = Data.cast(blob) + {:ok, kzg_proof} = Data.cast(kzg_proof) + + %{ + hash: blob_hash(kzg_commitment.bytes), + blob_data: blob.bytes, + kzg_commitment: kzg_commitment.bytes, + kzg_proof: kzg_proof.bytes + } + end + + def blob_hash(kzg_commitment) do + raw_hash = :crypto.hash(:sha256, kzg_commitment) + <<_::size(8), rest::binary>> = raw_hash + {:ok, hash} = Hash.Full.cast(<<1>> <> rest) + hash + end + + defp defaults do + [ + poll: false, + flush_interval: :timer.seconds(3), + max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size, + max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency, + task_supervisor: Indexer.Fetcher.Beacon.Blob.TaskSupervisor, + metadata: [fetcher: :beacon_blobs_sanitize] + ] + end +end diff --git a/apps/indexer/lib/indexer/fetcher/beacon/client.ex b/apps/indexer/lib/indexer/fetcher/beacon/client.ex new file mode 100644 index 000000000000..b443ee6d7a47 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/beacon/client.ex @@ -0,0 +1,77 @@ +defmodule Indexer.Fetcher.Beacon.Client do + @moduledoc """ + HTTP Client for Beacon Chain RPC + """ + alias HTTPoison.Response + require Logger + + @request_error_msg "Error while sending request to beacon rpc" + + def http_get_request(url) do + case HTTPoison.get(url) do + {:ok, %Response{body: body, status_code: 200}} -> + Jason.decode(body) + + {:ok, %Response{body: body, status_code: _}} -> + {:error, body} + + {:error, error} -> + old_truncate = Application.get_env(:logger, :truncate) + Logger.configure(truncate: :infinity) + + Logger.error(fn -> + [ + "Error while sending request to beacon rpc: #{url}: ", + inspect(error, limit: :infinity, printable_limit: :infinity) + ] + end) + + Logger.configure(truncate: old_truncate) + {:error, @request_error_msg} + end + end + + def get_blob_sidecars(slots) when is_list(slots) do + {oks, errors_with_retries} = + slots + |> Enum.map(&get_blob_sidecars/1) + |> Enum.with_index() + |> Enum.map(&first_if_ok/1) + |> Enum.split_with(&successful?/1) + + {errors, retries} = errors_with_retries |> Enum.unzip() + + if !Enum.empty?(errors) do + Logger.error(fn -> + [ + "Errors while fetching blob sidecars (failed for #{Enum.count(errors)}/#{Enum.count(slots)}) from beacon rpc: ", + inspect(Enum.take(errors, 3), limit: :infinity, printable_limit: :infinity) + ] + end) + end + + {:ok, oks |> Enum.map(fn {_, blob} -> blob end), retries} + end + + def get_blob_sidecars(slot) do + http_get_request(blob_sidecars_url(slot)) + end + + defp first_if_ok({{:ok, _} = first, _}), do: first + defp first_if_ok(res), do: res + + defp successful?({:ok, _}), do: true + defp successful?(_), do: false + + def get_header(slot) do + http_get_request(header_url(slot)) + end + + def blob_sidecars_url(slot), do: "#{base_url()}" <> "/eth/v1/beacon/blob_sidecars/" <> to_string(slot) + + def header_url(slot), do: "#{base_url()}" <> "/eth/v1/beacon/headers/" <> to_string(slot) + + def base_url do + Application.get_env(:indexer, Indexer.Fetcher.Beacon)[:beacon_rpc] + end +end diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index 10a745512bd0..c212ddbc6bba 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -139,6 +139,7 @@ defmodule Indexer.Supervisor do configure(TransactionBatch.Supervisor, [ [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), + {Indexer.Fetcher.Beacon.Blob.Supervisor, [[memory_monitor: memory_monitor]]}, # Out-of-band fetchers {EmptyBlocksSanitizer.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments]]}, diff --git a/config/runtime.exs b/config/runtime.exs index c81ceeafbbc4..3659b1d7be62 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -679,6 +679,21 @@ config :indexer, Indexer.Fetcher.RootstockData, max_concurrency: ConfigHelper.parse_integer_env_var("INDEXER_ROOTSTOCK_DATA_FETCHER_CONCURRENCY", 5), db_batch_size: ConfigHelper.parse_integer_env_var("INDEXER_ROOTSTOCK_DATA_FETCHER_DB_BATCH_SIZE", 300) +config :indexer, Indexer.Fetcher.Beacon, beacon_rpc: System.get_env("INDEXER_BEACON_RPC_URL") + +config :indexer, Indexer.Fetcher.Beacon.Blob.Supervisor, + disabled?: + ConfigHelper.chain_type() != "ethereum" || + ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_BEACON_BLOB_SANITIZE_FETCHER") + +config :indexer, Indexer.Fetcher.Beacon.Blob, + slot_duration: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_SANITIZE_FETCHER_SLOT_DURATION", 12), + reference_slot: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_SANITIZE_FETCHER_REFERENCE_SLOT", 8_206_822), + reference_timestamp: + ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_SANITIZE_FETCHER_REFERENCE_TIMESTAMP", 1_705_305_887), + start_block: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_SANITIZE_FETCHER_START_BLOCK", 8_206_822), + end_block: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_SANITIZE_FETCHER_END_BLOCK", 0) + Code.require_file("#{config_env()}.exs", "config/runtime") for config <- "../apps/*/config/runtime/#{config_env()}.exs" |> Path.expand(__DIR__) |> Path.wildcard() do From 9083380c48f3806c8645c98717824721c1cc2292 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 16 Jan 2024 22:35:50 +0400 Subject: [PATCH 296/607] fix: format --- apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex | 4 ++++ apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex | 1 + apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex | 2 ++ apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex | 1 + apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex | 1 + apps/explorer/lib/explorer/application.ex | 8 +++++++- apps/explorer/lib/explorer/chain/block.ex | 4 ++++ 7 files changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex index 964d3bfe6ab3..85eef70faf75 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex @@ -106,6 +106,7 @@ defmodule EthereumJSONRPC.Block do * `"blobGasUsed"` - `t:EthereumJSONRPC.quantity/0` of the total amount of blob gas consumed by the transactions within the block. * `"excessBlobGas"` - `t:EthereumJSONRPC.quantity/0` of the running total of blob gas consumed in excess of the target, prior to the block. """ + _ -> "" end} """ @type t :: %{String.t() => EthereumJSONRPC.data() | EthereumJSONRPC.hash() | EthereumJSONRPC.quantity() | nil} @@ -171,6 +172,7 @@ defmodule EthereumJSONRPC.Block do "blobGasUsed" => 262144,\ "excessBlobGas" => 79429632,\ """ + _ -> "" end} ...> "uncles" => [] ...> } @@ -208,6 +210,7 @@ defmodule EthereumJSONRPC.Block do blob_gas_used: 262144,\ excess_blob_gas: 79429632,\ """ + _ -> "" end} uncles: [] } @@ -270,6 +273,7 @@ defmodule EthereumJSONRPC.Block do blob_gas_used: 0,\ excess_blob_gas: 0,\ """ + _ -> "" end} uncles: [] } diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex index 6c91104f0aa2..76ffbe6b5cc4 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex @@ -129,6 +129,7 @@ defmodule EthereumJSONRPC.Blocks do blob_gas_used: 0,\ excess_blob_gas: 0,\ """ + _ -> "" end} uncles: ["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273311"] } diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex index 1c947c25521e..f82762f547a0 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex @@ -80,6 +80,7 @@ defmodule EthereumJSONRPC.Receipt do blob_gas_price: 0,\ blob_gas_used: 0\ """ + _ -> "" end} } @@ -119,6 +120,7 @@ defmodule EthereumJSONRPC.Receipt do blob_gas_price: 0,\ blob_gas_used: 0\ """ + _ -> "" end} } diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex index 1b133561d5c2..7ceb79b872ed 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex @@ -105,6 +105,7 @@ defmodule EthereumJSONRPC.Receipts do blob_gas_price: 0,\ blob_gas_used: 0\ """ + _ -> "" end} } ] diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex index d9a0b809e207..03cd3a52b8ce 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex @@ -84,6 +84,7 @@ defmodule EthereumJSONRPC.Transaction do * `"executionNode"` - `t:EthereumJSONRPC.address/0` of execution node (used by Suave). * `"requestRecord"` - map of wrapped transaction data (used by Suave). """ + _ -> "" end} """ @type t :: %{ diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 47b508db86a3..7a8aa8d979ad 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -138,7 +138,13 @@ defmodule Explorer.Application do defp repos_by_chain_type do if Mix.env() == :test do - [Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Suave, Explorer.Repo.Beacon] + [ + Explorer.Repo.PolygonEdge, + Explorer.Repo.PolygonZkevm, + Explorer.Repo.RSK, + Explorer.Repo.Suave, + Explorer.Repo.Beacon + ] else [] end diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index 9049cd576d27..13632ad24dc4 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -96,6 +96,7 @@ defmodule Explorer.Chain.Block do * `blob_gas_used` - The total amount of blob gas consumed by the transactions within the block. * `excess_blob_gas` - The running total of blob gas consumed in excess of the target, prior to the block. """ + _ -> "" end} """ @type t :: %__MODULE__{ @@ -145,6 +146,9 @@ defmodule Explorer.Chain.Block do "ethereum" -> field(:blob_gas_used, :decimal) field(:excess_blob_gas, :decimal) + + _ -> + nil end timestamps() From 37e968f76ca255552ba019414913a70ea7cd76e7 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 16 Jan 2024 22:52:28 +0400 Subject: [PATCH 297/607] fix: tests --- apps/block_scout_web/test/test_helper.exs | 1 + apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs | 2 +- apps/explorer/config/dev.exs | 2 ++ apps/explorer/config/prod.exs | 4 ++++ apps/explorer/config/test.exs | 2 +- apps/explorer/test/support/data_case.ex | 2 ++ apps/explorer/test/test_helper.exs | 1 + 7 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/block_scout_web/test/test_helper.exs b/apps/block_scout_web/test/test_helper.exs index b92840d3a20c..913ffe0422df 100644 --- a/apps/block_scout_web/test/test_helper.exs +++ b/apps/block_scout_web/test/test_helper.exs @@ -30,6 +30,7 @@ Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonEdge, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonZkevm, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.RSK, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, :manual) +Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Beacon, :manual) Absinthe.Test.prime(BlockScoutWeb.Schema) diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs index 0c7feed17f29..b5ee4ae2efec 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs @@ -15,7 +15,7 @@ defmodule EthereumJSONRPC.ReceiptTest do %{"new_key" => "new_value", "transactionHash" => "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060"} Errors: - {:unknown_key, %{value: "new_value", key: "new_key"}} + {:unknown_key, %{key: "new_key", value: "new_value"}} """, fn -> Receipt.to_elixir(%{ diff --git a/apps/explorer/config/dev.exs b/apps/explorer/config/dev.exs index 0aa303a2bfab..a3534c5422c3 100644 --- a/apps/explorer/config/dev.exs +++ b/apps/explorer/config/dev.exs @@ -21,6 +21,8 @@ config :explorer, Explorer.Repo.RSK, timeout: :timer.seconds(80) config :explorer, Explorer.Repo.Suave, timeout: :timer.seconds(80) +config :explorer, Explorer.Repo.Beacon, timeout: :timer.seconds(80) + config :explorer, Explorer.Tracer, env: "dev", disabled?: true config :logger, :explorer, diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs index e14afe322c04..8b048a35f0d6 100644 --- a/apps/explorer/config/prod.exs +++ b/apps/explorer/config/prod.exs @@ -32,6 +32,10 @@ config :explorer, Explorer.Repo.Suave, prepare: :unnamed, timeout: :timer.seconds(60) +config :explorer, Explorer.Repo.Beacon, + prepare: :unnamed, + timeout: :timer.seconds(60) + config :explorer, Explorer.Tracer, env: "production", disabled?: true config :logger, :explorer, diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index b292598e17ff..70d457c6c356 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -43,7 +43,7 @@ config :explorer, Explorer.Repo.Account, queue_target: 1000, log: false -for repo <- [Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Suave] do +for repo <- [Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Suave, Explorer.Repo.Beacon] do config :explorer, repo, database: "explorer_test", hostname: "localhost", diff --git a/apps/explorer/test/support/data_case.ex b/apps/explorer/test/support/data_case.ex index f93e1bcf7aa7..ae0c938180cf 100644 --- a/apps/explorer/test/support/data_case.ex +++ b/apps/explorer/test/support/data_case.ex @@ -39,6 +39,7 @@ defmodule Explorer.DataCase do :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.PolygonZkevm) :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.RSK) :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.Suave) + :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.Beacon) unless tags[:async] do Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()}) @@ -47,6 +48,7 @@ defmodule Explorer.DataCase do Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonZkevm, {:shared, self()}) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.RSK, {:shared, self()}) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, {:shared, self()}) + Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Beacon, {:shared, self()}) end Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.BlockNumber.child_id()) diff --git a/apps/explorer/test/test_helper.exs b/apps/explorer/test/test_helper.exs index 938420e729a0..6547402d51b1 100644 --- a/apps/explorer/test/test_helper.exs +++ b/apps/explorer/test/test_helper.exs @@ -17,6 +17,7 @@ Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonEdge, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonZkevm, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.RSK, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, :auto) +Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Beacon, :auto) Mox.defmock(Explorer.ExchangeRates.Source.TestSource, for: Explorer.ExchangeRates.Source) Mox.defmock(Explorer.Market.History.Source.Price.TestSource, for: Explorer.Market.History.Source.Price) From c174201ccbcbea1d95f9669eb97b334c20725b6c Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 16 Jan 2024 22:05:41 +0300 Subject: [PATCH 298/607] Add wallaby to plt_add_apps --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index cd6740114bf6..ac067de5bd85 100644 --- a/mix.exs +++ b/mix.exs @@ -53,7 +53,7 @@ defmodule BlockScout.Mixfile do defp dialyzer() do [ plt_add_deps: :app_tree, - plt_add_apps: ~w(ex_unit mix)a, + plt_add_apps: ~w(ex_unit mix wallaby)a, ignore_warnings: ".dialyzer-ignore", plt_core_path: "priv/plts", plt_file: {:no_warn, "priv/plts/dialyzer.plt"} From dafa09b59c2628b4c522a75f4ef0aadedced30d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 18:07:57 +0000 Subject: [PATCH 299/607] Bump briefly from `51dfe7f` to `4836ba3` Bumps [briefly](https://github.com/CargoSense/briefly) from `51dfe7f` to `4836ba3`. - [Release notes](https://github.com/CargoSense/briefly/releases) - [Commits](https://github.com/CargoSense/briefly/compare/51dfe7fbe0f897ea2a921d9af120762392aca6a1...4836ba322ffb504a102a15cc6e35d928ef97120e) --- updated-dependencies: - dependency-name: briefly dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 026b1cfa7b29..eff0c9efd4a6 100644 --- a/mix.lock +++ b/mix.lock @@ -8,7 +8,7 @@ "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.1.0", "0b110a9a6c619b19a7f73fa3004aa11d6e719a67e672d1633dc36b6b2290a0f7", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2ad2acb5a8bc049e8d5aa267802631912bb80d5f4110a178ae7999e69dca1bf7"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "benchee_csv": {:hex, :benchee_csv, "1.0.0", "0b3b9223290bfcb8003552705bec9bcf1a89b4a83b70bd686e45295c264f3d16", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:csv, "~> 2.0", [hex: :csv, repo: "hexpm", optional: false]}], "hexpm", "cdefb804c021dcf7a99199492026584be9b5a21d6644ac0d01c81c5d97c520d5"}, - "briefly": {:git, "https://github.com/CargoSense/briefly.git", "51dfe7fbe0f897ea2a921d9af120762392aca6a1", []}, + "briefly": {:git, "https://github.com/CargoSense/briefly.git", "4836ba322ffb504a102a15cc6e35d928ef97120e", []}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, "bureaucrat": {:hex, :bureaucrat, "0.2.9", "d98e4d2b9bdbf22e4a45c2113ce8b38b5b63278506c6ff918e3b943a4355d85b", [:mix], [{:inflex, ">= 1.10.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.2.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, ">= 1.0.0", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "111c8dd84382a62e1026ae011d592ceee918553e5203fe8448d9ba6ccbdfff7d"}, "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, From 9e12b731e2afab92afb02a9151e4d1a7b64a8632 Mon Sep 17 00:00:00 2001 From: bap2pecs <111917526+bap2pecs@users.noreply.github.com> Date: Tue, 16 Jan 2024 17:37:24 -0500 Subject: [PATCH 300/607] Fix common-blockscout.env --- docker-compose/envs/common-blockscout.env | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index ce0c19d13c5c..b12e37cac135 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -1,4 +1,3 @@ -# DOCKER_TAG= ETHEREUM_JSONRPC_VARIANT=geth ETHEREUM_JSONRPC_HTTP_URL=http://host.docker.internal:8545/ # ETHEREUM_JSONRPC_FALLBACK_HTTP_URL= From e91b2c7ac01e8760fe666b9ee7e1551c0bb3d5fc Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Wed, 17 Jan 2024 11:33:37 +0400 Subject: [PATCH 301/607] fix: fmt and test config --- apps/explorer/config/test.exs | 8 +++++++- apps/indexer/lib/indexer/block/fetcher.ex | 4 +++- config/config_helper.exs | 2 +- config/runtime.exs | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index 70d457c6c356..2894a2052b02 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -43,7 +43,13 @@ config :explorer, Explorer.Repo.Account, queue_target: 1000, log: false -for repo <- [Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Suave, Explorer.Repo.Beacon] do +for repo <- [ + Explorer.Repo.PolygonEdge, + Explorer.Repo.PolygonZkevm, + Explorer.Repo.RSK, + Explorer.Repo.Suave, + Explorer.Repo.Beacon + ] do config :explorer, repo, database: "explorer_test", hostname: "localhost", diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index d7177c83b8ee..bf91091f8bc0 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -388,12 +388,14 @@ defmodule Indexer.Block.Fetcher do def async_import_blobs(%{blocks: blocks}) do timestamps = blocks - |> Enum.filter(fn %{blob_gas_used: blob_gas_used} -> blob_gas_used > 0 end) + |> Enum.filter(fn block -> block |> Map.get(:blob_gas_used, 0) > 0 end) |> Enum.map(&Map.get(&1, :timestamp)) Blob.async_fetch(timestamps) end + def async_import_blobs(_), do: :ok + defp block_reward_errors_to_block_numbers(block_reward_errors) when is_list(block_reward_errors) do Enum.map(block_reward_errors, &block_reward_error_to_block_number/1) end diff --git a/config/config_helper.exs b/config/config_helper.exs index cac87c611f46..392ca74a2e49 100644 --- a/config/config_helper.exs +++ b/config/config_helper.exs @@ -182,7 +182,7 @@ defmodule ConfigHelper do end @spec chain_type() :: String.t() - def chain_type, do: System.get_env("CHAIN_TYPE") || "ethereum" + def chain_type, do: System.get_env("CHAIN_TYPE") || "default" @spec eth_call_url(String.t() | nil) :: String.t() | nil def eth_call_url(default \\ nil) do diff --git a/config/runtime.exs b/config/runtime.exs index 3659b1d7be62..7ef79e36d9ff 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -666,7 +666,7 @@ config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch, config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch.Supervisor, enabled: - System.get_env("CHAIN_TYPE", "ethereum") == "polygon_zkevm" && + ConfigHelper.chain_type() == "polygon_zkevm" && ConfigHelper.parse_bool_env_var("INDEXER_ZKEVM_BATCHES_ENABLED") config :indexer, Indexer.Fetcher.RootstockData.Supervisor, From b177528be3c69c26a43205e633c4148bfcd45978 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Wed, 17 Jan 2024 13:01:03 +0400 Subject: [PATCH 302/607] chore: refactor --- .../controllers/api/v2/block_controller.ex | 24 +++++++----- .../views/api/v2/block_view.ex | 39 +++++++++---------- .../views/api/v2/transaction_view.ex | 23 ++++++----- apps/explorer/lib/explorer/chain/block.ex | 16 ++++---- .../20240109102458_create_blobs_tables.exs | 4 +- 5 files changed, 55 insertions(+), 51 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex index d4879184ca83..ddb798f43209 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex @@ -18,24 +18,30 @@ defmodule BlockScoutWeb.API.V2.BlockController do case Application.compile_env(:explorer, :chain_type) do "ethereum" -> + @chain_type_transaction_necessity_by_association %{ + :beacon_blob_transaction => :optional + } @chain_type_block_necessity_by_association %{ [transactions: :beacon_blob_transaction] => :optional } _ -> + @chain_type_transaction_necessity_by_association %{} @chain_type_block_necessity_by_association %{} end @transaction_necessity_by_association [ - necessity_by_association: %{ - [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 - } + necessity_by_association: + %{ + [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 + } + |> Map.merge(@chain_type_transaction_necessity_by_association) ] @api_true [api?: true] diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex index 238e1719b6e5..60eaab5943de 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex @@ -28,23 +28,10 @@ defmodule BlockScoutWeb.API.V2.BlockView do end def prepare_block(block, _conn, single_block? \\ false) do - burnt_fees_execution = Block.burnt_fees(block.transactions, block.base_fee_per_gas) + burnt_fees = Block.burnt_fees(block.transactions, block.base_fee_per_gas) priority_fee = block.base_fee_per_gas && BlockPriorityFeeCounter.fetch(block.hash) - transaction_fees_execution = Block.transaction_fees(block.transactions) - - {transaction_fees, burnt_fees, blob_gas_price} = - if Application.get_env(:explorer, :chain_type) == "ethereum" do - blob_transaction_fees = Block.blob_transaction_fees(block.transactions) - - { - transaction_fees_execution |> Decimal.add(blob_transaction_fees), - burnt_fees_execution |> Decimal.add(blob_transaction_fees), - blob_transaction_fees |> Decimal.div(block.blob_gas_used) - } - else - {transaction_fees_execution, burnt_fees_execution, nil} - end + transaction_fees = Block.transaction_fees(block.transactions) %{ "height" => block.number, @@ -73,7 +60,7 @@ defmodule BlockScoutWeb.API.V2.BlockView do "tx_fees" => transaction_fees, "withdrawals_count" => count_withdrawals(block) } - |> chain_type_fields(block, blob_gas_price, single_block?) + |> chain_type_fields(block, single_block?) end def prepare_rewards(rewards, block, single_block?) do @@ -130,7 +117,7 @@ defmodule BlockScoutWeb.API.V2.BlockView do def count_withdrawals(%Block{withdrawals: withdrawals}) when is_list(withdrawals), do: Enum.count(withdrawals) def count_withdrawals(_), do: nil - defp chain_type_fields(result, block, blob_gas_price, single_block?) do + defp chain_type_fields(result, block, single_block?) do case Application.get_env(:explorer, :chain_type) do "rsk" -> if single_block? do @@ -145,10 +132,20 @@ defmodule BlockScoutWeb.API.V2.BlockView do end "ethereum" -> - result - |> Map.put("blob_gas_used", block.blob_gas_used) - |> Map.put("excess_blob_gas", block.excess_blob_gas) - |> Map.put("blob_gas_price", blob_gas_price) + if single_block? do + blob_gas_price = Block.transaction_blob_gas_price(block.transactions) + burnt_blob_transaction_fees = block |> Map.get(:blob_gas_used, 0) |> Decimal.mult(blob_gas_price || 0) + + result + |> Map.put("blob_gas_used", block.blob_gas_used) + |> Map.put("excess_blob_gas", block.excess_blob_gas) + |> Map.put("blob_gas_price", blob_gas_price) + |> Map.put("burnt_blob_fees", burnt_blob_transaction_fees) + else + result + |> Map.put("blob_gas_used", block.blob_gas_used) + |> Map.put("excess_blob_gas", block.excess_blob_gas) + end _ -> result diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index 4078ac8996e2..562edf13b8e6 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -453,16 +453,19 @@ defmodule BlockScoutWeb.API.V2.TransactionView do suave_fields(transaction, result, single_tx?, conn, watchlist_names) {_, "ethereum"} -> - beacon_blob_transaction = transaction.beacon_blob_transaction - - if is_nil(beacon_blob_transaction) or beacon_blob_transaction == %Ecto.Association.NotLoaded{} do - result - else - result - |> Map.put("max_fee_per_blob_gas", beacon_blob_transaction.max_fee_per_blob_gas) - |> Map.put("blob_versioned_hashes", beacon_blob_transaction.blob_versioned_hashes) - |> Map.put("blob_gas_used", beacon_blob_transaction.blob_gas_used) - |> Map.put("blob_gas_price", beacon_blob_transaction.blob_gas_price) + case Map.get(transaction, :beacon_blob_transaction) do + nil -> + result + + %Ecto.Association.NotLoaded{} -> + result + + item -> + result + |> Map.put("max_fee_per_blob_gas", item.max_fee_per_blob_gas) + |> Map.put("blob_versioned_hashes", item.blob_versioned_hashes) + |> Map.put("blob_gas_used", item.blob_gas_used) + |> Map.put("blob_gas_price", item.blob_gas_price) end _ -> diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index 13632ad24dc4..79fe7ee8788b 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -266,18 +266,16 @@ defmodule Explorer.Chain.Block do end @doc """ - Calculates blob transaction fees (gas price * gas used) for the list of transactions (from a single block) + Finds blob transaction gas price for the list of transactions (from a single block) """ - @spec blob_transaction_fees([Transaction.t()]) :: Decimal.t() - def blob_transaction_fees(transactions) do - Enum.reduce(transactions, Decimal.new(0), fn %{beacon_blob_transaction: beacon_blob_transaction}, acc -> + @spec transaction_blob_gas_price([Transaction.t()]) :: Decimal.t() | nil + def transaction_blob_gas_price(transactions) do + transactions + |> Enum.find_value(fn %{beacon_blob_transaction: beacon_blob_transaction} -> if is_nil(beacon_blob_transaction) do - acc + nil else - beacon_blob_transaction.blob_gas_used - |> Decimal.new() - |> Decimal.mult(gas_price_to_decimal(beacon_blob_transaction.blob_gas_price)) - |> Decimal.add(acc) + gas_price_to_decimal(beacon_blob_transaction.blob_gas_price) end end) end diff --git a/apps/explorer/priv/beacon/migrations/20240109102458_create_blobs_tables.exs b/apps/explorer/priv/beacon/migrations/20240109102458_create_blobs_tables.exs index e1f70098da99..e67cf501babc 100644 --- a/apps/explorer/priv/beacon/migrations/20240109102458_create_blobs_tables.exs +++ b/apps/explorer/priv/beacon/migrations/20240109102458_create_blobs_tables.exs @@ -17,8 +17,8 @@ defmodule Explorer.Repo.Beacon.Migrations.CreateBlobsTables do end alter table(:blocks) do - add(:blob_gas_used, :numeric, precision: 100, null: false) - add(:excess_blob_gas, :numeric, precision: 100, null: false) + add(:blob_gas_used, :numeric, precision: 100) + add(:excess_blob_gas, :numeric, precision: 100) end create table(:beacon_blobs, primary_key: false) do From beea6e5e34f26d400dfdaf285458ce478a60ddae Mon Sep 17 00:00:00 2001 From: cristiantroy <154241727+cristiantroy@users.noreply.github.com> Date: Wed, 17 Jan 2024 22:58:55 +0800 Subject: [PATCH 303/607] Fix typos --- .../fixture/smart_contract/large_smart_contract.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol b/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol index 2b42daeb972d..44f5c8f60721 100644 --- a/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol +++ b/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol @@ -2886,7 +2886,7 @@ contract HomeWork is IHomeWork, ERC721Enumerable, IERC721Metadata, IERC1412 { * * data:application/json,{ * "name":"Home%20Address%20-%200x********************", - * "description":"< ... HomeWork NFT desription ... >", + * "description":"< ... HomeWork NFT description ... >", * "image":"data:image/svg+xml;charset=utf-8;base64,< ... Image ... >"} * * where ******************** represents the checksummed home address that the @@ -3063,7 +3063,7 @@ contract HomeWork is IHomeWork, ERC721Enumerable, IERC721Metadata, IERC1412 { /** * @notice Internal function for deploying arbitrary contract code to the home - * address corresponding to a suppied key via metamorphic initialization code. + * address corresponding to a supplied key via metamorphic initialization code. * @return The home address and the hash of the deployed runtime code. * @dev This deployment method uses the "metamorphic delegator" pattern, where * it will retrieve the address of the contract that contains the target @@ -3793,7 +3793,7 @@ contract HomeWorkDeployer { /** * @notice Internal function for deploying arbitrary contract code to the home - * address corresponding to a suppied key via metamorphic initialization code. + * address corresponding to a supplied key via metamorphic initialization code. * @dev This deployment method uses the "metamorphic delegator" pattern, where * it will retrieve the address of the contract that contains the target * initialization code, then delegatecall into it, which executes the @@ -3871,4 +3871,4 @@ contract HomeWorkDeployer { require(!_disabled, "Contract is disabled."); _; } -} \ No newline at end of file +} From 544f9024f6e590e6d0b267ced8e238aa4363efd9 Mon Sep 17 00:00:00 2001 From: cristiantroy <154241727+cristiantroy@users.noreply.github.com> Date: Wed, 17 Jan 2024 23:02:03 +0800 Subject: [PATCH 304/607] Fix typo --- .../fixture/smart_contract/issue_with_constructor_args.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/test/support/fixture/smart_contract/issue_with_constructor_args.sol b/apps/explorer/test/support/fixture/smart_contract/issue_with_constructor_args.sol index a561dfc1b739..0b094acf52bf 100644 --- a/apps/explorer/test/support/fixture/smart_contract/issue_with_constructor_args.sol +++ b/apps/explorer/test/support/fixture/smart_contract/issue_with_constructor_args.sol @@ -557,7 +557,7 @@ abstract contract ERC1967Upgrade { * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`. * * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded - * function call, and allows initializating the storage of the proxy like a Solidity constructor. + * function call, and allows initializing the storage of the proxy like a Solidity constructor. */ constructor(address _logic, bytes memory _data) payable { assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)); @@ -570,4 +570,4 @@ abstract contract ERC1967Upgrade { function _implementation() internal view virtual override returns (address impl) { return ERC1967Upgrade._getImplementation(); } -} \ No newline at end of file +} From b20970c16a981d5d10d805a9182309d108f38dfc Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Fri, 19 Jan 2024 11:44:37 +0400 Subject: [PATCH 305/607] chore: equalize elixir stack versions --- .github/workflows/config.yml | 2 +- .github/workflows/prerelease.yml | 2 +- .github/workflows/publish-docker-image-every-push.yml | 2 +- .github/workflows/publish-docker-image-for-stability.yml | 2 +- .github/workflows/publish-docker-image-for-suave.yml | 2 +- .github/workflows/publish-docker-image-staging-on-demand.yml | 2 +- .github/workflows/release-additional.yml | 2 +- .github/workflows/release.yml | 2 +- .tool-versions | 2 +- docker/Dockerfile | 4 ++-- docker/README.md | 4 +--- 11 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 2e71299d83c1..f07143f50d66 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -35,7 +35,7 @@ on: env: MIX_ENV: test - OTP_VERSION: "25.2.1" + OTP_VERSION: "25.3.2.8" ELIXIR_VERSION: "1.14.5" ACCOUNT_AUTH0_DOMAIN: "blockscoutcom.us.auth0.com" diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 2d09956875c9..65ea0100a720 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -8,7 +8,7 @@ on: required: true env: - OTP_VERSION: '25.2.1' + OTP_VERSION: '25.3.2.8' ELIXIR_VERSION: '1.14.5' jobs: diff --git a/.github/workflows/publish-docker-image-every-push.yml b/.github/workflows/publish-docker-image-every-push.yml index 25d9c3fa9960..9f8ca4ef7fe3 100644 --- a/.github/workflows/publish-docker-image-every-push.yml +++ b/.github/workflows/publish-docker-image-every-push.yml @@ -9,7 +9,7 @@ on: - '**/README.md' - 'docker-compose/*' env: - OTP_VERSION: '25.2.1' + OTP_VERSION: '25.3.2.8' ELIXIR_VERSION: '1.14.5' RELEASE_VERSION: 6.0.0 diff --git a/.github/workflows/publish-docker-image-for-stability.yml b/.github/workflows/publish-docker-image-for-stability.yml index c14e5a6a6ac9..2807badc6599 100644 --- a/.github/workflows/publish-docker-image-for-stability.yml +++ b/.github/workflows/publish-docker-image-for-stability.yml @@ -11,7 +11,7 @@ on: branches: - production-stability env: - OTP_VERSION: '25.2.1' + OTP_VERSION: '25.3.2.8' ELIXIR_VERSION: '1.14.5' jobs: push_to_registry: diff --git a/.github/workflows/publish-docker-image-for-suave.yml b/.github/workflows/publish-docker-image-for-suave.yml index 0f3c4ad43982..41171e878d29 100644 --- a/.github/workflows/publish-docker-image-for-suave.yml +++ b/.github/workflows/publish-docker-image-for-suave.yml @@ -11,7 +11,7 @@ on: branches: - production-suave env: - OTP_VERSION: '25.2.1' + OTP_VERSION: '25.3.2.8' ELIXIR_VERSION: '1.14.5' jobs: push_to_registry: diff --git a/.github/workflows/publish-docker-image-staging-on-demand.yml b/.github/workflows/publish-docker-image-staging-on-demand.yml index 090a6be9d782..1f5b957c3900 100644 --- a/.github/workflows/publish-docker-image-staging-on-demand.yml +++ b/.github/workflows/publish-docker-image-staging-on-demand.yml @@ -10,7 +10,7 @@ on: - '**/README.md' - 'docker-compose/*' env: - OTP_VERSION: '25.2.1' + OTP_VERSION: '25.3.2.8' ELIXIR_VERSION: '1.14.5' RELEASE_VERSION: 6.0.0 diff --git a/.github/workflows/release-additional.yml b/.github/workflows/release-additional.yml index f0912540dc69..fdb24253ecbe 100644 --- a/.github/workflows/release-additional.yml +++ b/.github/workflows/release-additional.yml @@ -10,7 +10,7 @@ on: types: [published] env: - OTP_VERSION: '25.2.1' + OTP_VERSION: '25.3.2.8' ELIXIR_VERSION: '1.14.5' jobs: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ded48ce497f3..979b1931d3d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ on: types: [published] env: - OTP_VERSION: '25.2.1' + OTP_VERSION: '25.3.2.8' ELIXIR_VERSION: '1.14.5' jobs: diff --git a/.tool-versions b/.tool-versions index 13dabe9920d1..32fecf31ec4f 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ elixir 1.14.5-otp-25 -erlang 25.3.2.6 +erlang 25.3.2.8 nodejs 18.17.1 diff --git a/docker/Dockerfile b/docker/Dockerfile index bc8bb581b4b8..205a0cdfe4c6 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM hexpm/elixir:1.14.5-erlang-24.2.2-alpine-3.18.2 AS builder +FROM hexpm/elixir:1.14.5-erlang-25.3.2.8-alpine-3.18.4 AS builder WORKDIR /app @@ -64,7 +64,7 @@ RUN mkdir -p /opt/release \ && mv _build/${MIX_ENV}/rel/blockscout /opt/release ############################################################## -FROM hexpm/elixir:1.14.5-erlang-24.2.2-alpine-3.18.2 +FROM hexpm/elixir:1.14.5-erlang-25.3.2.8-alpine-3.18.4 ARG RELEASE_VERSION ENV RELEASE_VERSION=${RELEASE_VERSION} diff --git a/docker/README.md b/docker/README.md index 03df272c4a12..a1078f86a28f 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,5 +1,3 @@ # BlockScout Docker Integration -This integration is not production ready, and should be used for local BlockScout deployment only. - -For usage instructions and ENV variables, see the [docker integration documentation](https://docs.blockscout.com/for-developers/information-and-settings/docker-integration-local-use-only). \ No newline at end of file +For usage instructions and ENV variables, see the [docker integration documentation](https://docs.blockscout.com/for-developers/deployment/docker-compose-deployment). \ No newline at end of file From fd406e6b51a37e76ce8ea8aeed945b5c42d106df Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 19 Jan 2024 11:49:07 +0300 Subject: [PATCH 306/607] Update CHANGELOG entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bef7d59c8500..14f19e6792b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ ### Chore +- [#9193](https://github.com/blockscout/blockscout/pull/9193) - Equalize elixir stack versions +
Dependencies version bumps From c31924aa9c64e8995e2462d9e52dd3bb5c9762ef Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 19 Jan 2024 13:06:19 +0300 Subject: [PATCH 307/607] Compatibility with docker-compose 2.24 --- CHANGELOG.md | 1 + docker-compose/services/db.yml | 3 ++- docker-compose/services/nginx.yml | 9 ++++++--- docker-compose/services/stats.yml | 3 ++- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14f19e6792b3..34d0140233b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ ### Chore +- [#9196](https://github.com/blockscout/blockscout/pull/9196) - Compatibility with docker-compose 2.24 - [#9193](https://github.com/blockscout/blockscout/pull/9193) - Equalize elixir stack versions
diff --git a/docker-compose/services/db.yml b/docker-compose/services/db.yml index b58aba6e0e35..edea5693d66c 100644 --- a/docker-compose/services/db.yml +++ b/docker-compose/services/db.yml @@ -26,7 +26,8 @@ services: POSTGRES_USER: 'blockscout' POSTGRES_PASSWORD: 'ceWb1MeLBEeOIfk65gU8EjF8' ports: - - 7432:5432 + - target: 5432 + published: 7432 volumes: - ./blockscout-db-data:/var/lib/postgresql/data healthcheck: diff --git a/docker-compose/services/nginx.yml b/docker-compose/services/nginx.yml index 27c0a0a6a662..bc225c9082b6 100644 --- a/docker-compose/services/nginx.yml +++ b/docker-compose/services/nginx.yml @@ -12,6 +12,9 @@ services: BACK_PROXY_PASS: ${BACK_PROXY_PASS:-http://backend:4000} FRONT_PROXY_PASS: ${FRONT_PROXY_PASS:-http://frontend:3000} ports: - - 80:80 - - 8080:8080 - - 8081:8081 + - target: 80 + published: 80 + - target: 8080 + published: 8080 + - target: 8081 + published: 8081 diff --git a/docker-compose/services/stats.yml b/docker-compose/services/stats.yml index 85bee46b9909..77b9ad76ba39 100644 --- a/docker-compose/services/stats.yml +++ b/docker-compose/services/stats.yml @@ -26,7 +26,8 @@ services: POSTGRES_USER: 'stats' POSTGRES_PASSWORD: 'n0uejXPl61ci6ldCuE2gQU5Y' ports: - - 7433:5432 + - target: 5432 + published: 7433 volumes: - ./stats-db-data:/var/lib/postgresql/data healthcheck: From 9c41c6abb6ec33eeacef68bb49a51d3b1e866df8 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Fri, 19 Jan 2024 16:36:38 +0300 Subject: [PATCH 308/607] Proxy for Account abstraction microservice (#9145) * Proxy for Account abstraction microservice * Process reviewer comments * Fix reviewer comment * Fix response for endpoints with the list * Fix reviewer comment --- .github/workflows/config.yml | 26 +-- CHANGELOG.md | 1 + .../lib/block_scout_web/api_router.ex | 14 ++ .../proxy/account_abstraction_controller.ex | 176 +++++++++++++++ .../block_scout_web/views/api/v2/helper.ex | 14 +- .../account_abstraction.ex | 203 ++++++++++++++++++ config/runtime.exs | 12 +- docker-compose/envs/common-blockscout.env | 2 + 8 files changed, 426 insertions(+), 22 deletions(-) create mode 100644 apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex create mode 100644 apps/explorer/lib/explorer/microservice_interfaces/account_abstraction.ex diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index f07143f50d66..80ffe70c0bc0 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -75,7 +75,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_34-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_35-${{ 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_34-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_35-${{ 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_34-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_35-${{ 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_34-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_35-${{ 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_34-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_35-${{ 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_34-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_35-${{ 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_34-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_35-${{ 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_34-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_35-${{ 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_34-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_35-${{ 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_34-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_35-${{ 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_34-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_35-${{ 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_34-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_35-${{ 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_34-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_35-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d7412021c63..9bfe2885d511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - [#9155](https://github.com/blockscout/blockscout/pull/9155) - Allow bypassing avg block time in proxy implementation re-fetch ttl calculation +- [#9145](https://github.com/blockscout/blockscout/pull/9145) - Proxy for Account abstraction microservice - [#9131](https://github.com/blockscout/blockscout/pull/9131) - Merge addresses stage with address referencing - [#9072](https://github.com/blockscout/blockscout/pull/9072) - Add tracing by block logic for geth - [#9056](https://github.com/blockscout/blockscout/pull/9056) - Noves.fi API proxy diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 5f8415014aaa..d029916e2dcd 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -307,6 +307,20 @@ defmodule BlockScoutWeb.ApiRouter do get("/transactions/:transaction_hash_param/describe", V2.Proxy.NovesFiController, :describe_transaction) get("/addresses/:address_hash_param/transactions", V2.Proxy.NovesFiController, :address_transactions) end + + scope "/account-abstraction" do + get("/operations/:operation_hash_param", V2.Proxy.AccountAbstractionController, :operation) + get("/bundlers/:address_hash_param", V2.Proxy.AccountAbstractionController, :bundler) + get("/bundlers", V2.Proxy.AccountAbstractionController, :bundlers) + get("/factories/:address_hash_param", V2.Proxy.AccountAbstractionController, :factory) + get("/factories", V2.Proxy.AccountAbstractionController, :factories) + get("/paymasters/:address_hash_param", V2.Proxy.AccountAbstractionController, :paymaster) + get("/paymasters", V2.Proxy.AccountAbstractionController, :paymasters) + get("/accounts/:address_hash_param", V2.Proxy.AccountAbstractionController, :account) + get("/accounts", V2.Proxy.AccountAbstractionController, :accounts) + get("/bundles", V2.Proxy.AccountAbstractionController, :bundles) + get("/operations", V2.Proxy.AccountAbstractionController, :operations) + end end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex new file mode 100644 index 000000000000..89fb25900ecd --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex @@ -0,0 +1,176 @@ +defmodule BlockScoutWeb.API.V2.Proxy.AccountAbstractionController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.API.V2.Helper + + alias Explorer.Chain + alias Explorer.MicroserviceInterfaces.AccountAbstraction + + @address_fields ["bundler", "entry_point", "sender", "address", "factory", "paymaster"] + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + @doc """ + Function to handle GET requests to `/api/v2/proxy/account-abstraction/operations/:user_operation_hash_param` endpoint. + """ + @spec operation(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def operation(conn, %{"operation_hash_param" => operation_hash_string}) do + operation_hash_string + |> AccountAbstraction.get_user_ops_by_hash() + |> process_response(conn) + end + + @doc """ + Function to handle GET requests to `/api/v2/proxy/account-abstraction/bundlers/:address_hash_param` endpoint. + """ + @spec bundler(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def bundler(conn, %{"address_hash_param" => address_hash_string}) do + address_hash_string + |> AccountAbstraction.get_bundler_by_hash() + |> process_response(conn) + end + + @doc """ + Function to handle GET requests to `/api/v2/proxy/account-abstraction/bundlers` endpoint. + """ + @spec bundlers(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def bundlers(conn, query_string) do + query_string + |> AccountAbstraction.get_bundlers() + |> process_response(conn) + end + + @doc """ + Function to handle GET requests to `/api/v2/proxy/account-abstraction/factories/:address_hash_param` endpoint. + """ + @spec factory(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def factory(conn, %{"address_hash_param" => address_hash_string}) do + address_hash_string + |> AccountAbstraction.get_factory_by_hash() + |> process_response(conn) + end + + @doc """ + Function to handle GET requests to `/api/v2/proxy/account-abstraction/factories` endpoint. + """ + @spec factories(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def factories(conn, query_string) do + query_string + |> AccountAbstraction.get_factories() + |> process_response(conn) + end + + @doc """ + Function to handle GET requests to `/api/v2/proxy/account-abstraction/paymasters/:address_hash_param` endpoint. + """ + @spec paymaster(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def paymaster(conn, %{"address_hash_param" => address_hash_string}) do + address_hash_string + |> AccountAbstraction.get_paymaster_by_hash() + |> process_response(conn) + end + + @doc """ + Function to handle GET requests to `/api/v2/proxy/account-abstraction/paymasters` endpoint. + """ + @spec paymasters(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def paymasters(conn, query_string) do + query_string + |> AccountAbstraction.get_paymasters() + |> process_response(conn) + end + + @doc """ + Function to handle GET requests to `/api/v2/proxy/account-abstraction/accounts/:address_hash_param` endpoint. + """ + @spec account(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def account(conn, %{"address_hash_param" => address_hash_string}) do + address_hash_string + |> AccountAbstraction.get_account_by_hash() + |> process_response(conn) + end + + @doc """ + Function to handle GET requests to `/api/v2/proxy/account-abstraction/accounts` endpoint. + """ + @spec accounts(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def accounts(conn, query_string) do + query_string + |> AccountAbstraction.get_accounts() + |> process_response(conn) + end + + @doc """ + Function to handle GET requests to `/api/v2/proxy/account-abstraction/bundles` endpoint. + """ + @spec bundles(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def bundles(conn, query_string) do + query_string + |> AccountAbstraction.get_bundles() + |> process_response(conn) + end + + @doc """ + Function to handle GET requests to `/api/v2/proxy/account-abstraction/operations` endpoint. + """ + @spec operations(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def operations(conn, query_string) do + query_string + |> AccountAbstraction.get_operations() + |> process_response(conn) + end + + defp extended_info(response) do + case response do + %{"items" => items} -> + extended_items = + Enum.map(items, fn response_item -> + add_address_extended_info(response_item) + end) + + response + |> Map.put("items", extended_items) + + _ -> + add_address_extended_info(response) + end + end + + defp add_address_extended_info(response) do + @address_fields + |> Enum.reduce(response, fn address_output_field, output_response -> + if Map.has_key?(output_response, address_output_field) do + output_response + |> Map.replace( + address_output_field, + address_info_from_hash_string(Map.get(output_response, address_output_field)) + ) + else + output_response + end + end) + end + + defp address_info_from_hash_string(address_hash_string) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash, [], false) do + Helper.address_with_info(address, address_hash_string) + else + _ -> address_hash_string + end + end + + defp process_response(response, conn) do + case response do + {:error, :disabled} -> + conn + |> put_status(501) + |> json(extended_info(%{message: "Service is disabled"})) + + {status_code, response} -> + conn + |> put_status(status_code) + |> json(extended_info(response)) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex index 96a69840ee24..010171545d3b 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex @@ -48,7 +48,11 @@ defmodule BlockScoutWeb.API.V2.Helper do }) end - defp address_with_info(%Address{} = address, _address_hash) do + @doc """ + Gets address with the additional info for api v2 + """ + @spec address_with_info(any(), any()) :: nil | %{optional(<<_::32, _::_*8>>) => any()} + def address_with_info(%Address{} = address, _address_hash) do %{ "hash" => Address.checksum(address), "is_contract" => Address.is_smart_contract(address), @@ -59,21 +63,21 @@ defmodule BlockScoutWeb.API.V2.Helper do } end - defp address_with_info(%{ens_domain_name: name}, address_hash) do + def address_with_info(%{ens_domain_name: name}, address_hash) do nil |> address_with_info(address_hash) |> Map.put("ens_domain_name", name) end - defp address_with_info(%NotLoaded{}, address_hash) do + def address_with_info(%NotLoaded{}, address_hash) do address_with_info(nil, address_hash) end - defp address_with_info(nil, nil) do + def address_with_info(nil, nil) do nil end - defp address_with_info(_, address_hash) do + def address_with_info(_, address_hash) do %{ "hash" => Address.checksum(address_hash), "is_contract" => false, diff --git a/apps/explorer/lib/explorer/microservice_interfaces/account_abstraction.ex b/apps/explorer/lib/explorer/microservice_interfaces/account_abstraction.ex new file mode 100644 index 000000000000..5dbbf2d94527 --- /dev/null +++ b/apps/explorer/lib/explorer/microservice_interfaces/account_abstraction.ex @@ -0,0 +1,203 @@ +defmodule Explorer.MicroserviceInterfaces.AccountAbstraction do + @moduledoc """ + Interface to interact with Blockscout Account Abstraction (EIP-4337) microservice + """ + + alias Explorer.Utility.Microservice + alias HTTPoison.Response + require Logger + + @doc """ + Get user operation by hash via GET {{baseUrl}}/api/v1/userOps/:hash + """ + @spec get_user_ops_by_hash(binary()) :: {non_neg_integer(), map()} | {:error, :disabled} + def get_user_ops_by_hash(user_operation_hash_string) do + with :ok <- Microservice.check_enabled(__MODULE__) do + query_params = %{} + + http_get_request(operation_by_hash_url(user_operation_hash_string), query_params) + end + end + + @doc """ + Get operations list via GET {{baseUrl}}/api/v1/operations + """ + @spec get_operations(map()) :: {non_neg_integer(), map()} | {:error, :disabled} + def get_operations(query_params) do + with :ok <- Microservice.check_enabled(__MODULE__) do + http_get_request(operations_url(), query_params) + end + end + + @doc """ + Get bundler by address hash via GET {{baseUrl}}/api/v1/bundlers/:address + """ + @spec get_bundler_by_hash(binary()) :: {non_neg_integer(), map()} | {:error, :disabled} + def get_bundler_by_hash(address_hash_string) do + with :ok <- Microservice.check_enabled(__MODULE__) do + query_params = %{} + + http_get_request(bundler_by_hash_url(address_hash_string), query_params) + end + end + + @doc """ + Get bundlers list via GET {{baseUrl}}/api/v1/bundlers + """ + @spec get_bundlers(map()) :: {non_neg_integer(), map()} | {:error, :disabled} + def get_bundlers(query_params) do + with :ok <- Microservice.check_enabled(__MODULE__) do + http_get_request(bundlers_url(), query_params) + end + end + + @doc """ + Get factory by address hash via GET {{baseUrl}}/api/v1/factories/:address + """ + @spec get_factory_by_hash(binary()) :: {non_neg_integer(), map()} | {:error, :disabled} + def get_factory_by_hash(address_hash_string) do + with :ok <- Microservice.check_enabled(__MODULE__) do + query_params = %{} + + http_get_request(factory_by_hash_url(address_hash_string), query_params) + end + end + + @doc """ + Get factories list via GET {{baseUrl}}/api/v1/factories + """ + @spec get_factories(map()) :: {non_neg_integer(), map()} | {:error, :disabled} + def get_factories(query_params) do + with :ok <- Microservice.check_enabled(__MODULE__) do + http_get_request(factories_url(), query_params) + end + end + + @doc """ + Get paymaster by address hash via GET {{baseUrl}}/api/v1/paymasters/:address + """ + @spec get_paymaster_by_hash(binary()) :: {non_neg_integer(), map()} | {:error, :disabled} + def get_paymaster_by_hash(address_hash_string) do + with :ok <- Microservice.check_enabled(__MODULE__) do + query_params = %{} + + http_get_request(paymaster_by_hash_url(address_hash_string), query_params) + end + end + + @doc """ + Get paymasters list via GET {{baseUrl}}/api/v1/paymasters + """ + @spec get_paymasters(map()) :: {non_neg_integer(), map()} | {:error, :disabled} + def get_paymasters(query_params) do + with :ok <- Microservice.check_enabled(__MODULE__) do + http_get_request(paymasters_url(), query_params) + end + end + + @doc """ + Get account by address hash via GET {{baseUrl}}/api/v1/accounts/:address + """ + @spec get_account_by_hash(binary()) :: {non_neg_integer(), map()} | {:error, :disabled} + def get_account_by_hash(address_hash_string) do + with :ok <- Microservice.check_enabled(__MODULE__) do + query_params = %{} + + http_get_request(account_by_hash_url(address_hash_string), query_params) + end + end + + @doc """ + Get accounts list via GET {{baseUrl}}/api/v1/accounts + """ + @spec get_accounts(map()) :: {non_neg_integer(), map()} | {:error, :disabled} + def get_accounts(query_params) do + with :ok <- Microservice.check_enabled(__MODULE__) do + http_get_request(accounts_url(), query_params) + end + end + + @doc """ + Get bundles list via GET {{baseUrl}}/api/v1/bundles + """ + @spec get_bundles(map()) :: {non_neg_integer(), map()} | {:error, :disabled} + def get_bundles(query_params) do + with :ok <- Microservice.check_enabled(__MODULE__) do + http_get_request(bundles_url(), query_params) + end + end + + defp http_get_request(url, query_params) do + case HTTPoison.get(url, [], params: query_params) do + {:ok, %Response{body: body, status_code: 200}} -> + {:ok, response_json} = Jason.decode(body) + {200, response_json} + + {_, %Response{body: body, status_code: status_code} = error} -> + old_truncate = Application.get_env(:logger, :truncate) + Logger.configure(truncate: :infinity) + + Logger.error(fn -> + [ + "Error while sending request to Account Abstraction microservice url: #{url}: ", + inspect(error, limit: :infinity, printable_limit: :infinity) + ] + end) + + Logger.configure(truncate: old_truncate) + {:ok, response_json} = Jason.decode(body) + {status_code, response_json} + end + end + + @spec enabled?() :: boolean + def enabled?, do: Application.get_env(:explorer, __MODULE__)[:enabled] + + defp operation_by_hash_url(user_op_hash) do + "#{base_url()}/userOps/#{user_op_hash}" + end + + defp operations_url do + "#{base_url()}/userOps" + end + + defp bundler_by_hash_url(address_hash) do + "#{base_url()}/bundlers/#{address_hash}" + end + + defp bundlers_url do + "#{base_url()}/bundlers" + end + + defp factory_by_hash_url(address_hash) do + "#{base_url()}/factories/#{address_hash}" + end + + defp factories_url do + "#{base_url()}/factories" + end + + defp paymaster_by_hash_url(address_hash) do + "#{base_url()}/paymasters/#{address_hash}" + end + + defp paymasters_url do + "#{base_url()}/paymasters" + end + + defp account_by_hash_url(address_hash) do + "#{base_url()}/accounts/#{address_hash}" + end + + defp accounts_url do + "#{base_url()}/accounts" + end + + defp bundles_url do + "#{base_url()}/bundles" + end + + defp base_url do + "#{Microservice.base_url(__MODULE__)}/api/v1" + end +end diff --git a/config/runtime.exs b/config/runtime.exs index c81ceeafbbc4..624910593baf 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -405,6 +405,14 @@ config :explorer, Explorer.SmartContract.SigProviderInterface, service_url: System.get_env("MICROSERVICE_SIG_PROVIDER_URL"), enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_SIG_PROVIDER_ENABLED") +config :explorer, Explorer.MicroserviceInterfaces.BENS, + service_url: System.get_env("MICROSERVICE_BENS_URL"), + enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_BENS_ENABLED") + +config :explorer, Explorer.MicroserviceInterfaces.AccountAbstraction, + service_url: System.get_env("MICROSERVICE_ACCOUNT_ABSTRACTION_URL"), + enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_ACCOUNT_ABSTRACTION_ENABLED") + config :explorer, Explorer.ThirdPartyIntegrations.AirTable, table_url: System.get_env("ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL"), api_key: System.get_env("ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY") @@ -461,10 +469,6 @@ config :explorer, Explorer.Chain.Transaction, config :explorer, Explorer.Chain.Cache.AddressesTabsCounters, ttl: ConfigHelper.parse_time_env_var("ADDRESSES_TABS_COUNTERS_TTL", "10m") -config :explorer, Explorer.MicroserviceInterfaces.BENS, - service_url: System.get_env("MICROSERVICE_BENS_URL"), - enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_BENS_ENABLED") - config :explorer, Explorer.Migrator.TransactionsDenormalization, batch_size: ConfigHelper.parse_integer_env_var("DENORMALIZATION_MIGRATION_BATCH_SIZE", 500), concurrency: ConfigHelper.parse_integer_env_var("DENORMALIZATION_MIGRATION_CONCURRENCY", 10) diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index b12e37cac135..d355548e60ce 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -238,6 +238,8 @@ MICROSERVICE_SIG_PROVIDER_ENABLED=true MICROSERVICE_SIG_PROVIDER_URL=http://sig-provider:8050/ # MICROSERVICE_BENS_URL= # MICROSERVICE_BENS_ENABLED= +#MICROSERVICE_ACCOUNT_ABSTRACTION_ENABLED=true +#MICROSERVICE_ACCOUNT_ABSTRACTION_URL= DECODE_NOT_A_CONTRACT_CALLS=true # DATABASE_READ_ONLY_API_URL= # ACCOUNT_DATABASE_URL= From 7f0cdc843c442ed6a4e2ee17a32a4580ec6a3d9d Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Fri, 19 Jan 2024 17:49:13 +0300 Subject: [PATCH 309/607] New RPC API endpoints (#9068) * Done tokennfttx endpoint * Add solidity-single-file verification option to verifysourcecode endpoint * Fix tokentx bug * Add verifyproxycontract and checkproxyverification endpoints * Do not return twin contracts in getsourcecode RPC API endpoint * Process review comments * Update messages * Reset GA cache --- CHANGELOG.md | 1 + .../controllers/address_logs_controller.ex | 5 +- .../address_transaction_controller.ex | 5 +- .../controllers/api/rpc/address_controller.ex | 89 ++++-- .../api/rpc/contract_controller.ex | 183 +++++++++++- .../controllers/api/rpc/helper.ex | 20 +- .../decompiled_smart_contract_controller.ex | 7 +- .../v1/verified_smart_contract_controller.ex | 9 +- .../controllers/api/v2/address_controller.ex | 3 +- .../controllers/csv_export_controller.ex | 3 +- .../views/api/rpc/address_view.ex | 30 ++ .../views/api/rpc/contract_view.ex | 18 ++ .../api/rpc/address_controller_test.exs | 278 ++++++++++++++++++ .../api/rpc/contract_controller_test.exs | 57 ++++ apps/explorer/lib/explorer/chain.ex | 54 ---- apps/explorer/lib/explorer/chain/address.ex | 54 ++++ .../lib/explorer/chain/smart_contract.ex | 48 ++- .../explorer/chain/smart_contract/proxy.ex | 2 +- .../proxy/verification_status.ex | 125 ++++++++ .../lib/explorer/chain/token_transfer.ex | 20 +- apps/explorer/lib/explorer/etherscan.ex | 66 ++++- .../lib/explorer/etherscan/contracts.ex | 11 +- .../solidity/publisher_worker.ex | 25 +- .../lib/explorer/tags/address_to_tag.ex | 2 +- ...27170848_add_proxy_verification_status.exs | 17 ++ cspell.json | 23 +- 26 files changed, 1029 insertions(+), 126 deletions(-) create mode 100644 apps/explorer/lib/explorer/chain/smart_contract/proxy/verification_status.ex create mode 100644 apps/explorer/priv/repo/migrations/20231227170848_add_proxy_verification_status.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bfe2885d511..aa7cd8ba8b0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - [#9145](https://github.com/blockscout/blockscout/pull/9145) - Proxy for Account abstraction microservice - [#9131](https://github.com/blockscout/blockscout/pull/9131) - Merge addresses stage with address referencing - [#9072](https://github.com/blockscout/blockscout/pull/9072) - Add tracing by block logic for geth +- [#9068](https://github.com/blockscout/blockscout/pull/9068) - New RPC API v1 endpoints - [#9056](https://github.com/blockscout/blockscout/pull/9056) - Noves.fi API proxy - [#9158](https://github.com/blockscout/blockscout/pull/9158) - Increase shared memory for PostgreSQL containers diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex index fd7ee5a60970..b76d713d3927 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex @@ -11,6 +11,7 @@ defmodule BlockScoutWeb.AddressLogsController do alias BlockScoutWeb.{AccessHelper, AddressLogsView, Controller} alias Explorer.{Chain, Market} + alias Explorer.Chain.Address alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -18,7 +19,7 @@ defmodule BlockScoutWeb.AddressLogsController do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), - :ok <- Chain.check_address_exists(address_hash), + :ok <- Address.check_address_exists(address_hash), {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params) do logs_plus_one = Chain.address_to_logs(address_hash, false, paging_options(params)) {results, next_page} = split_list_by_page(logs_plus_one) @@ -78,7 +79,7 @@ defmodule BlockScoutWeb.AddressLogsController do def search_logs(conn, %{"topic" => topic, "address_id" => address_hash_string} = params) do with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), - :ok <- Chain.check_address_exists(address_hash) do + :ok <- Address.check_address_exists(address_hash) do topic = String.trim(topic) formatted_topic = if String.starts_with?(topic, "0x"), do: topic, else: "0x" <> topic diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex index 05ef0c98d7fe..a27e3153f118 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -12,6 +12,7 @@ defmodule BlockScoutWeb.AddressTransactionController do alias BlockScoutWeb.{AccessHelper, Controller, TransactionView} alias Explorer.{Chain, Market} + alias Explorer.Chain.Address alias Explorer.Chain.CSVExport.{ AddressInternalTransactionCsvExporter, @@ -190,7 +191,7 @@ defmodule BlockScoutWeb.AddressTransactionController do ) when is_binary(address_hash_string) do with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), - {:address_exists, true} <- {:address_exists, Chain.address_exists?(address_hash)}, + {:address_exists, true} <- {:address_exists, Address.address_exists?(address_hash)}, {:recaptcha, true} <- {:recaptcha, captcha_helper().recaptcha_passed?(recaptcha_response)} do filter_type = Map.get(params, "filter_type") filter_value = Map.get(params, "filter_value") @@ -229,7 +230,7 @@ defmodule BlockScoutWeb.AddressTransactionController do ) when is_binary(address_hash_string) do with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), - {:address_exists, true} <- {:address_exists, Chain.address_exists?(address_hash)}, + {:address_exists, true} <- {:address_exists, Address.address_exists?(address_hash)}, true <- Application.get_env(:block_scout_web, :recaptcha)[:is_disabled] do filter_type = Map.get(params, "filter_type") filter_value = Map.get(params, "filter_value") diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex index e3d33c1419ab..98d6d8d8f782 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex @@ -7,6 +7,12 @@ defmodule BlockScoutWeb.API.RPC.AddressController do alias Explorer.Etherscan.{Addresses, Blocks} alias Indexer.Fetcher.CoinBalanceOnDemand + @api_true [api?: true] + + @invalid_address_message "Invalid address format" + @invalid_contract_address_message "Invalid contract address format" + @no_token_transfers_message "No token transfers found" + def listaccounts(conn, params) do options = params @@ -88,7 +94,7 @@ defmodule BlockScoutWeb.API.RPC.AddressController do {:format, :error} -> conn |> put_status(200) - |> render(:error, error: "Invalid address format") + |> render(:error, error: @invalid_address_message) {:error, :not_found} -> render(conn, :error, error: "No transactions found", data: []) @@ -100,7 +106,7 @@ defmodule BlockScoutWeb.API.RPC.AddressController do with {:address_param, {:ok, address_param}} <- fetch_address(params), {:format, {:ok, address_hash}} <- to_address_hash(address_param), - {:address, :ok} <- {:address, Chain.check_address_exists(address_hash)}, + {:address, :ok} <- {:address, Address.check_address_exists(address_hash, @api_true)}, {:ok, transactions} <- list_transactions(address_hash, options) do render(conn, :txlist, %{transactions: transactions}) else @@ -112,7 +118,7 @@ defmodule BlockScoutWeb.API.RPC.AddressController do {:format, :error} -> conn |> put_status(200) - |> render(:error, error: "Invalid address format") + |> render(:error, error: @invalid_address_message) {_, :not_found} -> render(conn, :error, error: "No transactions found", data: []) @@ -149,12 +155,12 @@ defmodule BlockScoutWeb.API.RPC.AddressController do options = optional_params(params) with {:format, {:ok, address_hash}} <- to_address_hash(address_param), - {:address, :ok} <- {:address, Chain.check_address_exists(address_hash)}, + {:address, :ok} <- {:address, Address.check_address_exists(address_hash, @api_true)}, {:ok, internal_transactions} <- list_internal_transactions(address_hash, options) do render(conn, :txlistinternal, %{internal_transactions: internal_transactions}) else {:format, :error} -> - render(conn, :error, error: "Invalid address format") + render(conn, :error, error: @invalid_address_message) {_, :not_found} -> render(conn, :error, error: "No internal transactions found", data: []) @@ -166,8 +172,9 @@ defmodule BlockScoutWeb.API.RPC.AddressController do with {:address_param, {:ok, address_param}} <- fetch_address(params), {:format, {:ok, address_hash}} <- to_address_hash(address_param), - {:contract_address, {:ok, contract_address_hash}} <- to_contract_address_hash(params["contractaddress"]), - {:address, :ok} <- {:address, Chain.check_address_exists(address_hash)}, + {:contract_address, {:ok, contract_address_hash}} <- + {:contract_address, to_address_hash_optional(params["contractaddress"])}, + {:address, :ok} <- {:address, Address.check_address_exists(address_hash, @api_true)}, {:ok, token_transfers} <- list_token_transfers(address_hash, contract_address_hash, options) do render(conn, :tokentx, %{token_transfers: token_transfers}) else @@ -175,13 +182,38 @@ defmodule BlockScoutWeb.API.RPC.AddressController do render(conn, :error, error: "Query parameter address is required") {:format, :error} -> - render(conn, :error, error: "Invalid address format") + render(conn, :error, error: @invalid_address_message) + + {:contract_address, :error} -> + render(conn, :error, error: @invalid_contract_address_message) + + {_, :not_found} -> + render(conn, :error, error: @no_token_transfers_message, data: []) + end + end + + def tokennfttx(conn, params) do + options = optional_params(params) + + with {:address, {:ok, address_hash}} <- {:address, to_address_hash_optional(params["address"])}, + {:contract_address, {:ok, contract_address_hash}} <- + {:contract_address, to_address_hash_optional(params["contractaddress"])}, + true <- !is_nil(address_hash) or !is_nil(contract_address_hash), + {:ok, token_transfers, max_block_number} <- + list_nft_token_transfers(address_hash, contract_address_hash, options) do + render(conn, :tokennfttx, %{token_transfers: token_transfers, max_block_number: max_block_number}) + else + false -> + render(conn, :error, error: "Query parameter address or contractaddress is required") + + {:address, :error} -> + render(conn, :error, error: @invalid_address_message) {:contract_address, :error} -> - render(conn, :error, error: "Invalid contract address format") + render(conn, :error, error: @invalid_contract_address_message) {_, :not_found} -> - render(conn, :error, error: "No token transfers found", data: []) + render(conn, :error, error: @no_token_transfers_message, data: []) end end @@ -205,7 +237,7 @@ defmodule BlockScoutWeb.API.RPC.AddressController do def tokenlist(conn, params) do with {:address_param, {:ok, address_param}} <- fetch_address(params), {:format, {:ok, address_hash}} <- to_address_hash(address_param), - {:address, :ok} <- {:address, Chain.check_address_exists(address_hash)}, + {:address, :ok} <- {:address, Address.check_address_exists(address_hash, @api_true)}, {:ok, token_list} <- list_tokens(address_hash) do render(conn, :token_list, %{token_list: token_list}) else @@ -213,7 +245,7 @@ defmodule BlockScoutWeb.API.RPC.AddressController do render(conn, :error, error: "Query parameter address is required") {:format, :error} -> - render(conn, :error, error: "Invalid address format") + render(conn, :error, error: @invalid_address_message) {_, :not_found} -> render(conn, :error, error: "No tokens found", data: []) @@ -225,7 +257,7 @@ defmodule BlockScoutWeb.API.RPC.AddressController do with {:address_param, {:ok, address_param}} <- fetch_address(params), {:format, {:ok, address_hash}} <- to_address_hash(address_param), - {:address, :ok} <- {:address, Chain.check_address_exists(address_hash)}, + {:address, :ok} <- {:address, Address.check_address_exists(address_hash, @api_true)}, {:ok, blocks} <- list_blocks(address_hash, options) do render(conn, :getminedblocks, %{blocks: blocks}) else @@ -233,7 +265,7 @@ defmodule BlockScoutWeb.API.RPC.AddressController do render(conn, :error, error: "Query parameter 'address' is required") {:format, :error} -> - render(conn, :error, error: "Invalid address format") + render(conn, :error, error: @invalid_address_message) {_, :not_found} -> render(conn, :error, error: "No blocks found", data: []) @@ -395,11 +427,9 @@ defmodule BlockScoutWeb.API.RPC.AddressController do end) end - defp to_contract_address_hash(nil), do: {:contract_address, {:ok, nil}} + defp to_address_hash_optional(nil), do: {:ok, nil} - defp to_contract_address_hash(address_hash_string) do - {:contract_address, Chain.string_to_address_hash(address_hash_string)} - end + defp to_address_hash_optional(address_hash_string), do: Chain.string_to_address_hash(address_hash_string) defp to_address_hash(address_hash_string) do {:format, Chain.string_to_address_hash(address_hash_string)} @@ -501,6 +531,29 @@ defmodule BlockScoutWeb.API.RPC.AddressController do end end + defp list_nft_token_transfers(nil, contract_address_hash, options) do + with {:ok, max_block_number} <- Chain.max_consensus_block_number(), + token_transfers when token_transfers != [] <- + Etherscan.list_nft_token_transfers_by_token(contract_address_hash, options) do + {:ok, token_transfers, max_block_number} + else + _ -> + {:error, :not_found} + end + end + + defp list_nft_token_transfers(address_hash, contract_address_hash, options) do + with {:address, :ok} <- {:address, Address.check_address_exists(address_hash, @api_true)}, + {:ok, max_block_number} <- Chain.max_consensus_block_number(), + token_transfers when token_transfers != [] <- + Etherscan.list_nft_token_transfers(address_hash, contract_address_hash, options) do + {:ok, token_transfers, max_block_number} + else + _ -> + {:error, :not_found} + end + end + defp list_blocks(address_hash, options) do case Etherscan.list_blocks(address_hash, options) do [] -> {:error, :not_found} diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex index 5939df0d2569..c0f6a50ef190 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex @@ -6,6 +6,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do alias BlockScoutWeb.API.RPC.{AddressController, Helper} alias Explorer.Chain alias Explorer.Chain.{Address, Hash, SmartContract} + alias Explorer.Chain.SmartContract.Proxy.VerificationStatus, as: ProxyVerificationStatus alias Explorer.Chain.SmartContract.VerificationStatus alias Explorer.Etherscan.Contracts alias Explorer.SmartContract.Helper, as: SmartContractHelper @@ -13,12 +14,49 @@ defmodule BlockScoutWeb.API.RPC.ContractController do alias Explorer.SmartContract.Solidity.PublisherWorker, as: SolidityPublisherWorker alias Explorer.SmartContract.Vyper.Publisher, as: VyperPublisher alias Explorer.ThirdPartyIntegrations.Sourcify + import BlockScoutWeb.API.V2.AddressController, only: [validate_address: 2, validate_address: 3] @smth_went_wrong "Something went wrong while publishing the contract" @verified "Smart-contract already verified." @invalid_address "Invalid address hash" @invalid_args "Invalid args format" @address_required "Query parameter address is required" + @addresses_required "Query parameter contractaddresses is required" + @contract_not_found "Smart-contract not found or is not verified" + @restricted_access "Access to this address is restricted" + + @addresses_limit 10 + @api_true [api?: true] + + @doc """ + Function to handle getcontractcreation request + """ + @spec getcontractcreation(Plug.Conn.t(), map()) :: Plug.Conn.t() + def getcontractcreation(conn, %{"contractaddresses" => contract_address_hash_strings} = params) do + addresses = + contract_address_hash_strings + |> String.split(",") + |> Enum.take(@addresses_limit) + |> Enum.map(fn address_hash_string -> + case validate_address(address_hash_string, params) do + {:ok, _address_hash, address} -> + Address.maybe_preload_smart_contract_associations( + address, + [:contracts_creation_internal_transaction, :contracts_creation_transaction], + @api_true + ) + + _ -> + nil + end + end) + + render(conn, :getcontractcreation, %{addresses: addresses}) + end + + def getcontractcreation(conn, _params) do + render(conn, :error, error: @addresses_required, data: @addresses_required) + end def verify(conn, %{"addressHash" => address_hash} = params) do with {:params, {:ok, fetched_params}} <- {:params, fetch_verify_params(params)}, @@ -137,6 +175,38 @@ defmodule BlockScoutWeb.API.RPC.ContractController do render(conn, :error, error: "Missing sourceCode or contractaddress fields") end + def verifysourcecode( + conn, + %{ + "codeformat" => "solidity-single-file", + "contractaddress" => address_hash + } = params + ) do + with {:check_verified_status, false} <- + {:check_verified_status, SmartContract.verified_with_full_match?(address_hash)}, + {:format, {:ok, _casted_address_hash}} <- to_address_hash(address_hash), + {:params, {:ok, fetched_params}} <- {:params, fetch_verifysourcecode_solidity_single_file_params(params)}, + external_libraries <- fetch_external_libraries_for_verifysourcecode(params), + uid <- VerificationStatus.generate_uid(address_hash) do + Que.add(SolidityPublisherWorker, {"flattened_api", fetched_params, external_libraries, uid}) + + render(conn, :show, %{result: uid}) + else + {:check_verified_status, true} -> + render(conn, :error, error: @verified, data: @verified) + + {:format, :error} -> + render(conn, :error, error: @invalid_address, data: @invalid_address) + + {:params, {:error, error}} -> + render(conn, :error, error: error, data: error) + end + end + + def verifysourcecode(conn, _params) do + render(conn, :error, error: "Missing codeformat field") + end + def checkverifystatus(conn, %{"guid" => guid}) do case VerificationStatus.fetch_status(guid) do :pending -> @@ -153,6 +223,65 @@ defmodule BlockScoutWeb.API.RPC.ContractController do end end + def verifyproxycontract(conn, %{"address" => address_hash_string} = params) do + with {:ok, address_hash, %Address{smart_contract: smart_contract}} <- + validate_address(address_hash_string, params, + necessity_by_association: %{:smart_contract => :optional}, + api?: true + ), + {:not_found, false} <- {:not_found, is_nil(smart_contract)}, + {:time_interval, true} <- + {:time_interval, + SmartContract.check_implementation_refetch_necessity(smart_contract.implementation_fetched_at)}, + uid <- ProxyVerificationStatus.generate_uid(address_hash) do + ProxyVerificationStatus.insert_status(uid, :pending, address_hash) + + SmartContract.get_implementation_address_hash(smart_contract, + timeout: 0, + uid: uid, + callback: &ProxyVerificationStatus.set_proxy_verification_result/2 + ) + + render(conn, :show, %{result: uid}) + else + {:format, :error} -> + render(conn, :error, error: @invalid_address) + + {:not_found, _} -> + render(conn, :error, error: @contract_not_found) + + {:restricted_access, true} -> + render(conn, :error, error: @restricted_access) + + {:time_interval, false} -> + render(conn, :error, error: "Only one attempt in #{SmartContract.get_fresh_time_distance()}ms") + end + end + + def checkproxyverification(conn, %{"guid" => guid}) do + submission = ProxyVerificationStatus.fetch_status(guid) + + case submission && submission.status do + :pending -> + render(conn, :show, %{result: "Verification in progress"}) + + :pass -> + render(conn, :show, %{ + result: + "The proxy's (#{submission.contract_address_hash}) implementation contract is found at #{SmartContract.address_hash_to_smart_contract(submission.contract_address_hash).implementation_address_hash} and is successfully updated." + }) + + :fail -> + render(conn, :error, %{ + error: "NOTOK", + data: "A corresponding implementation contract was unfortunately not detected for the proxy address." + }) + + _ -> + render(conn, :show, %{result: "Unknown UID"}) + end + end + defp prepare_params(files) when is_struct(files) do {:error, @invalid_args} end @@ -352,7 +481,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do with {:address_param, {:ok, address_param}} <- fetch_address(params), {:format, {:ok, address_hash}} <- to_address_hash(address_param) do _ = PublishHelper.check_and_verify(address_param) - address = Contracts.address_hash_to_address_with_source_code(address_hash) + address = Contracts.address_hash_to_address_with_source_code(address_hash, false) render(conn, :getsourcecode, %{ contract: address || %Address{hash: address_hash, smart_contract: nil} @@ -497,7 +626,19 @@ defmodule BlockScoutWeb.API.RPC.ContractController do |> required_param(params, "contractname", "name") |> required_param(params, "compilerversion", "compiler_version") |> optional_param(params, "constructorArguments", "constructor_arguments") + end + + defp fetch_verifysourcecode_solidity_single_file_params(params) do + {:ok, %{}} + |> required_param(params, "contractaddress", "address_hash") + |> required_param(params, "contractname", "name") + |> required_param(params, "compilerversion", "compiler_version") + |> required_param(params, "optimizationUsed", "optimization") + |> required_param(params, "sourceCode", "contract_source_code") + |> optional_param(params, "runs", "optimization_runs") + |> optional_param(params, "evmversion", "evm_version") |> optional_param(params, "constructorArguments", "constructor_arguments") + |> prepare_optimization() end defp parse_optimization_runs({:ok, %{"optimization_runs" => runs} = opts}) when is_bitstring(runs) do @@ -521,10 +662,18 @@ defmodule BlockScoutWeb.API.RPC.ContractController do defp parse_optimization_runs(other), do: other defp fetch_external_libraries(params) do + fetch_external_libraries_general(&"library#{&1}Name", &"library#{&1}Address", params) + end + + defp fetch_external_libraries_for_verifysourcecode(params) do + fetch_external_libraries_general(&"libraryname#{&1}", &"libraryaddress#{&1}", params) + end + + defp fetch_external_libraries_general(number_to_library_name, number_to_library_address, params) do Enum.reduce(1..Application.get_env(:block_scout_web, :contract)[:verification_max_libraries], %{}, fn number, acc -> - case Map.fetch(params, "library#{number}Name") do + case Map.fetch(params, number_to_library_name.(number)) do {:ok, library_name} -> - library_address = Map.get(params, "library#{number}Address") + library_address = Map.get(params, number_to_library_address.(number)) acc |> Map.put("library#{number}_name", library_name) @@ -559,4 +708,32 @@ defmodule BlockScoutWeb.API.RPC.ContractController do {:ok, map} end end + + defp prepare_optimization({:ok, %{"optimization" => optimization} = params}) do + parsed = parse_optimization(optimization) + + case parsed do + :error -> + {:error, "optimizationUsed has invalid format"} + + _ -> + {:ok, Map.put(params, "optimization", parsed)} + end + end + + defp prepare_optimization(error), do: error + + defp parse_optimization("0"), do: false + defp parse_optimization(0), do: false + + defp parse_optimization("1"), do: true + defp parse_optimization(1), do: true + + defp parse_optimization("false"), do: false + defp parse_optimization(false), do: false + + defp parse_optimization("true"), do: true + defp parse_optimization(true), do: true + + defp parse_optimization(_), do: :error end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/helper.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/helper.ex index 46840eda2555..d19627e62ec5 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/helper.ex @@ -2,7 +2,7 @@ defmodule BlockScoutWeb.API.RPC.Helper do @moduledoc """ Small helpers for RPC api controllers. """ - alias Explorer.Etherscan + alias Explorer.{Chain, Etherscan} def put_pagination_options(options, params) do options @@ -39,4 +39,22 @@ defmodule BlockScoutWeb.API.RPC.Helper do defp validate_max_page_size(page_size) do if page_size <= Etherscan.page_size_max(), do: :ok, else: :error end + + @doc """ + Parses addresses list (delimiter is `,`) from a string and validates them. + """ + @spec parse_and_validate_addresses(binary(), integer()) :: list() + def parse_and_validate_addresses(string, limit) do + string + |> String.split(",") + |> Enum.take(limit) + |> Enum.uniq() + |> Enum.map(fn address -> + case Chain.string_to_address_hash(address) do + {:ok, address_hash} -> address_hash + _ -> nil + end + end) + |> Enum.reject(&is_nil/1) + end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex index cb16c6876961..bde1d1a54f88 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex @@ -2,12 +2,13 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do use BlockScoutWeb, :controller alias Explorer.Chain - alias Explorer.Chain.Hash.Address + alias Explorer.Chain.Address + alias Explorer.Chain.Hash.Address, as: AddressHash def create(conn, params) do if auth_token(conn) == actual_token() do with {:ok, hash} <- validate_address_hash(params["address_hash"]), - :ok <- Chain.check_address_exists(hash), + :ok <- Address.check_address_exists(hash), {:contract, :not_found} <- {:contract, Chain.check_decompiled_contract_exists(params["address_hash"], params["decompiler_version"])} do case Chain.create_decompiled_smart_contract(params) do @@ -46,7 +47,7 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do end defp validate_address_hash(address_hash) do - case Address.cast(address_hash) do + case AddressHash.cast(address_hash) do {:ok, hash} -> {:ok, hash} :error -> :invalid_address end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex index 2edc07e8dec8..6a93491f21eb 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex @@ -1,14 +1,13 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do use BlockScoutWeb, :controller - alias Explorer.Chain - alias Explorer.Chain.Hash.Address - alias Explorer.Chain.SmartContract + alias Explorer.Chain.Hash.Address, as: AddressHash + alias Explorer.Chain.{Address, SmartContract} alias Explorer.SmartContract.Solidity.Publisher def create(conn, params) do with {:ok, hash} <- validate_address_hash(params["address_hash"]), - :ok <- Chain.check_address_exists(hash), + :ok <- Address.check_address_exists(hash), {:contract, :not_found} <- {:contract, SmartContract.check_verified_smart_contract_exists(hash)} do external_libraries = fetch_external_libraries(params) @@ -42,7 +41,7 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do end defp validate_address_hash(address_hash) do - case Address.cast(address_hash) do + case AddressHash.cast(address_hash) do {:ok, hash} -> {:ok, hash} :error -> :invalid_address end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex index fe0ec5bee1a2..e77b2a0c1f00 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -498,7 +498,8 @@ defmodule BlockScoutWeb.API.V2.AddressController do end @doc """ - Checks if this valid address hash string, and this address is not prohibited address + Checks if this valid address hash string, and this address is not prohibited address. + Returns the `{:ok, address_hash, address}` if address hash passed all the checks. """ @spec validate_address(String.t(), any(), Keyword.t()) :: {:format, :error} diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/csv_export_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/csv_export_controller.ex index 1cb332b2a49e..aa04ec19dad2 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/csv_export_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/csv_export_controller.ex @@ -3,10 +3,11 @@ defmodule BlockScoutWeb.CsvExportController do alias BlockScoutWeb.AccessHelper alias Explorer.Chain + alias Explorer.Chain.Address def index(conn, %{"address" => address_hash_string, "type" => type} = params) do with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), - :ok <- Chain.check_address_exists(address_hash), + :ok <- Address.check_address_exists(address_hash), {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), true <- supported_export_type(type), filter_type <- Map.get(params, "filter_type"), diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex index 263462e9cc54..25ecdb2fc961 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex @@ -43,6 +43,11 @@ defmodule BlockScoutWeb.API.RPC.AddressView do RPCView.render("show.json", data: data) end + def render("tokennfttx.json", %{token_transfers: token_transfers, max_block_number: max_block_number}) do + data = Enum.map(token_transfers, &prepare_nft_token_transfer(&1, max_block_number)) + RPCView.render("show.json", data: data) + end + def render("tokenbalance.json", %{token_balance: token_balance}) do RPCView.render("show.json", data: to_string(token_balance)) end @@ -197,6 +202,31 @@ defmodule BlockScoutWeb.API.RPC.AddressView do prepare_common_token_transfer(token_transfer) end + defp prepare_nft_token_transfer(token_transfer, max_block_number) do + %{ + "blockNumber" => to_string(token_transfer.block_number), + "timeStamp" => to_string(DateTime.to_unix(token_transfer.block.timestamp)), + "hash" => to_string(token_transfer.transaction_hash), + "nonce" => to_string(token_transfer.transaction.nonce), + "blockHash" => to_string(token_transfer.block_hash), + "from" => to_string(token_transfer.from_address_hash), + "contractAddress" => to_string(token_transfer.token_contract_address_hash), + "to" => to_string(token_transfer.to_address_hash), + "tokenID" => to_string(List.first(token_transfer.token_ids)), + "logIndex" => to_string(token_transfer.log_index), + "tokenName" => token_transfer.token.name, + "tokenSymbol" => token_transfer.token.symbol, + "tokenDecimal" => to_string(token_transfer.token.decimals || 0), + "transactionIndex" => to_string(token_transfer.transaction.index), + "gas" => to_string(token_transfer.transaction.gas), + "gasPrice" => to_string(token_transfer.transaction.gas_price && token_transfer.transaction.gas_price.value), + "gasUsed" => to_string(token_transfer.transaction.gas_used), + "cumulativeGasUsed" => to_string(token_transfer.transaction.cumulative_gas_used), + "input" => "deprecated", + "confirmations" => to_string(max_block_number - token_transfer.block_number) + } + end + defp prepare_block(block) do %{ "blockNumber" => to_string(block.number), diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex index 5297bf50d1aa..24178233be63 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex @@ -8,6 +8,12 @@ defmodule BlockScoutWeb.API.RPC.ContractView do defguardp is_empty_string(input) when input == "" or input == nil + def render("getcontractcreation.json", %{addresses: addresses}) do + contracts = addresses |> Enum.map(&address_to_response/1) |> Enum.reject(&is_nil/1) + + RPCView.render("show.json", data: contracts) + end + def render("listcontracts.json", %{contracts: contracts}) do contracts = Enum.map(contracts, &prepare_contract/1) @@ -229,4 +235,16 @@ defmodule BlockScoutWeb.API.RPC.ContractView do defp decompiler_version(nil), do: "" defp decompiler_version(%DecompiledSmartContract{decompiler_version: decompiler_version}), do: decompiler_version + + defp address_to_response(address) do + creator_hash = AddressView.from_address_hash(address) + creation_tx = creator_hash && AddressView.transaction_hash(address) + + creation_tx && + %{ + "contractAddress" => to_string(address.hash), + "contractCreator" => to_string(creator_hash), + "txHash" => to_string(creation_tx) + } + end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs index f872c8928fd9..7eb0463a04fe 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs @@ -2368,6 +2368,284 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do end end + describe "tokennfttx" do + setup do + %{params: %{"module" => "account", "action" => "tokennfttx"}} + end + + test "with missing address and contract address hash", %{conn: conn, params: params} do + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] == "Query parameter address or contractaddress is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) + end + + test "with an invalid address hash", %{conn: conn, params: params} do + params = Map.merge(params, %{"address" => "badhash"}) + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid address format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) + end + + test "with an address that doesn't exist", %{conn: conn, params: params} do + params = Map.merge(params, %{"address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"}) + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == [] + assert response["status"] == "0" + assert response["message"] == "No token transfers found" + assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) + end + + test "has correct value for ERC-721", %{conn: conn, params: params} do + transaction = + :transaction + |> insert() + |> with_block() + + token_address = insert(:contract_address) + insert(:token, %{contract_address: token_address, type: "ERC-721"}) + + token_transfer = + insert(:token_transfer, %{ + token_contract_address: token_address, + token_ids: [666], + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number + }) + + {:ok, _} = Chain.token_from_address_hash(token_transfer.token_contract_address_hash) + + params = Map.merge(params, %{"address" => to_string(token_transfer.from_address.hash)}) + + assert response = + %{"result" => [result]} = + conn + |> get("/api", params) + |> json_response(200) + + assert result["tokenID"] == to_string(List.first(token_transfer.token_ids)) + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) + end + + test "returns all the required fields", %{conn: conn, params: params} do + transaction = + %{block: block} = + :transaction + |> insert() + |> with_block() + + token_address = insert(:contract_address) + token = insert(:token, contract_address: token_address, type: "ERC-721") + + token_transfer = + insert(:token_transfer, + block: transaction.block, + transaction: transaction, + block_number: block.number, + token_ids: [1010], + token_contract_address: token_address + ) + + params = Map.merge(params, %{"address" => to_string(token_transfer.from_address.hash)}) + + expected_result = [ + %{ + "blockNumber" => to_string(transaction.block_number), + "timeStamp" => to_string(DateTime.to_unix(block.timestamp)), + "hash" => to_string(token_transfer.transaction_hash), + "nonce" => to_string(transaction.nonce), + "blockHash" => to_string(block.hash), + "from" => to_string(token_transfer.from_address_hash), + "contractAddress" => to_string(token_transfer.token_contract_address_hash), + "to" => to_string(token_transfer.to_address_hash), + "tokenName" => token.name, + "tokenSymbol" => token.symbol, + "tokenDecimal" => to_string(token.decimals), + "transactionIndex" => to_string(transaction.index), + "gas" => to_string(transaction.gas), + "gasPrice" => to_string(transaction.gas_price.value), + "gasUsed" => to_string(transaction.gas_used), + "cumulativeGasUsed" => to_string(transaction.cumulative_gas_used), + "logIndex" => to_string(token_transfer.log_index), + "input" => "deprecated", + "confirmations" => "0", + "tokenID" => "1010" + } + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) + end + + test "with an invalid contract address", %{conn: conn, params: params} do + params = + Map.merge(params, %{"address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", "contractaddress" => "invalid"}) + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid contract address format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) + end + + test "filters results by contract address", %{conn: conn, params: params} do + address = insert(:address) + + contract_address = insert(:contract_address) + + insert(:token, contract_address: contract_address, type: "ERC-721") + + transaction = + :transaction + |> insert() + |> with_block() + + insert(:token_transfer, + from_address: address, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number + ) + + insert(:token_transfer, + from_address: address, + token_contract_address: contract_address, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, + token_ids: [123] + ) + + params = + Map.merge(params, %{"address" => to_string(address.hash), "contractaddress" => to_string(contract_address.hash)}) + + assert response = + %{"result" => [result]} = + conn + |> get("/api", params) + |> json_response(200) + + assert result["contractAddress"] == to_string(contract_address.hash) + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) + end + + test "Check pagination and ordering (page, offset, sort parameters)", %{conn: conn, params: params} do + address = insert(:address) + + erc_721_token = insert(:token, type: "ERC-721") + + erc_721_tt = + for x <- 0..50 do + tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + + insert(:token_transfer, + transaction: tx, + block: tx.block, + block_number: tx.block_number, + from_address: address, + token_contract_address: erc_721_token.contract_address, + token_ids: [x] + ) + end + + # sort: asc + params = Map.merge(params, %{"address" => to_string(address.hash), "offset" => 25, "page" => 1, "sort" => "asc"}) + + assert %{"result" => token_transfers_1} = + conn + |> get("/api", params) + |> json_response(200) + + params = Map.merge(params, %{"address" => to_string(address.hash), "offset" => 25, "page" => 2, "sort" => "asc"}) + + assert %{"result" => token_transfers_2} = + conn + |> get("/api", params) + |> json_response(200) + + params = Map.merge(params, %{"address" => to_string(address.hash), "offset" => 25, "page" => 3, "sort" => "asc"}) + + assert %{"result" => token_transfers_3} = + conn + |> get("/api", params) + |> json_response(200) + + assert Enum.at(token_transfers_1, 0)["hash"] == to_string(Enum.at(erc_721_tt, 0).transaction_hash) + assert Enum.at(token_transfers_1, 24)["hash"] == to_string(Enum.at(erc_721_tt, 24).transaction_hash) + assert Enum.at(token_transfers_2, 0)["hash"] == to_string(Enum.at(erc_721_tt, 25).transaction_hash) + assert Enum.at(token_transfers_2, 24)["hash"] == to_string(Enum.at(erc_721_tt, 49).transaction_hash) + assert Enum.at(token_transfers_3, 0)["hash"] == to_string(Enum.at(erc_721_tt, 50).transaction_hash) + assert Enum.count(token_transfers_3) == 1 + + # sort: desc + erc_721_tt_reversed = Enum.reverse(erc_721_tt) + + params = Map.merge(params, %{"address" => to_string(address.hash), "offset" => 25, "page" => 1, "sort" => "desc"}) + + assert %{"result" => token_transfers_1} = + conn + |> get("/api", params) + |> json_response(200) + + params = Map.merge(params, %{"address" => to_string(address.hash), "offset" => 25, "page" => 2, "sort" => "desc"}) + + assert %{"result" => token_transfers_2} = + conn + |> get("/api", params) + |> json_response(200) + + params = Map.merge(params, %{"address" => to_string(address.hash), "offset" => 25, "page" => 3, "sort" => "desc"}) + + assert %{"result" => token_transfers_3} = + conn + |> get("/api", params) + |> json_response(200) + + assert Enum.at(token_transfers_1, 0)["hash"] == to_string(Enum.at(erc_721_tt_reversed, 0).transaction_hash) + assert Enum.at(token_transfers_1, 24)["hash"] == to_string(Enum.at(erc_721_tt_reversed, 24).transaction_hash) + assert Enum.at(token_transfers_2, 0)["hash"] == to_string(Enum.at(erc_721_tt_reversed, 25).transaction_hash) + assert Enum.at(token_transfers_2, 24)["hash"] == to_string(Enum.at(erc_721_tt_reversed, 49).transaction_hash) + assert Enum.at(token_transfers_3, 0)["hash"] == to_string(Enum.at(erc_721_tt_reversed, 50).transaction_hash) + assert Enum.count(token_transfers_3) == 1 + end + end + describe "tokenbalance" do test "without required params", %{conn: conn} do params = %{ diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs index 89122b8369b4..f03df2b1932a 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs @@ -860,6 +860,63 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do # end end + describe "getcontractcreation" do + setup do + %{params: %{"module" => "contract", "action" => "getcontractcreation"}} + end + + test "return error", %{conn: conn, params: params} do + %{ + "status" => "0", + "message" => "Query parameter contractaddresses is required", + "result" => "Query parameter contractaddresses is required" + } = + conn + |> get("/api", params) + |> json_response(200) + end + + test "get empty list", %{conn: conn, params: params} do + address = build(:address) + address_1 = insert(:address) + + %{ + "status" => "1", + "message" => "OK", + "result" => [] + } = + conn + |> get("/api", Map.put(params, "contractaddresses", "#{to_string(address)},#{to_string(address_1)}")) + |> json_response(200) + end + + test "get not empty list", %{conn: conn, params: params} do + address_1 = build(:address) + address = insert(:contract_address) + + transaction = insert(:transaction, created_contract_address: address) + + %{ + "status" => "1", + "message" => "OK", + "result" => [ + %{ + "contractAddress" => contract_address, + "contractCreator" => contract_creator, + "txHash" => tx_hash + } + ] + } = + conn + |> get("/api", Map.put(params, "contractaddresses", "#{to_string(address)},#{to_string(address_1)}")) + |> json_response(200) + + assert contract_address == to_string(address.hash) + assert contract_creator == to_string(transaction.from_address_hash) + assert tx_hash == to_string(transaction.hash) + end + end + defp listcontracts_schema do resolve_schema(%{ "type" => ["array", "null"], diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index f735fce9a73d..93f37e813910 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -4381,60 +4381,6 @@ defmodule Explorer.Chain do end end - @doc """ - Checks if an `t:Explorer.Chain.Address.t/0` with the given `hash` exists. - - Returns `:ok` if found - - iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address( - ...> %{hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"} - ...> ) - iex> Explorer.Chain.check_address_exists(hash) - :ok - - Returns `:not_found` if not found - - iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") - iex> Explorer.Chain.check_address_exists(hash) - :not_found - - """ - @spec check_address_exists(Hash.Address.t()) :: :ok | :not_found - def check_address_exists(address_hash) do - address_hash - |> address_exists?() - |> boolean_to_check_result() - end - - @doc """ - Checks if an `t:Explorer.Chain.Address.t/0` with the given `hash` exists. - - Returns `true` if found - - iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address( - ...> %{hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"} - ...> ) - iex> Explorer.Chain.address_exists?(hash) - true - - Returns `false` if not found - - iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") - iex> Explorer.Chain.address_exists?(hash) - false - - """ - @spec address_exists?(Hash.Address.t()) :: boolean() - def address_exists?(address_hash) do - query = - from( - address in Address, - where: address.hash == ^address_hash - ) - - Repo.exists?(query) - end - @doc """ Checks if it exists an `t:Explorer.Chain.Address.t/0` that has the provided `t:Explorer.Chain.Address.t/0` `hash` and a contract. diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index 9390063859c3..f256f7ade126 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -382,4 +382,58 @@ defmodule Explorer.Chain.Address do address.fetched_coin_balance < ^coin_balance ) end + + @doc """ + Checks if an `t:Explorer.Chain.Address.t/0` with the given `hash` exists. + + Returns `:ok` if found + + iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address( + ...> %{hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"} + ...> ) + iex> Explorer.Address.check_address_exists(hash) + :ok + + Returns `:not_found` if not found + + iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") + iex> Explorer.Address.check_address_exists(hash) + :not_found + + """ + @spec check_address_exists(Hash.Address.t(), [Chain.api?()]) :: :ok | :not_found + def check_address_exists(address_hash, options \\ []) do + address_hash + |> address_exists?(options) + |> Chain.boolean_to_check_result() + end + + @doc """ + Checks if an `t:Explorer.Chain.Address.t/0` with the given `hash` exists. + + Returns `true` if found + + iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address( + ...> %{hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"} + ...> ) + iex> Explorer.Chain.Address.address_exists?(hash) + true + + Returns `false` if not found + + iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") + iex> Explorer.Chain.Address.address_exists?(hash) + false + + """ + @spec address_exists?(Hash.Address.t(), [Chain.api?()]) :: boolean() + def address_exists?(address_hash, options \\ []) do + query = + from( + address in Address, + where: address.hash == ^address_hash + ) + + Chain.select_repo(options).exists?(query) + end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index aa41122dabc5..d8d9eb79b355 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -528,7 +528,7 @@ defmodule Explorer.Chain.SmartContract do smart_contract end - get_implementation_address_hash({:updated, updated_smart_contract}) + get_implementation_address_hash({:updated, updated_smart_contract}, options) end def get_implementation_address_hash( @@ -546,10 +546,17 @@ defmodule Explorer.Chain.SmartContract do if check_implementation_refetch_necessity(implementation_fetched_at) do get_implementation_address_hash_task = Task.async(fn -> - Proxy.fetch_implementation_address_hash(address_hash, abi, metadata_from_verified_twin, options) + result = Proxy.fetch_implementation_address_hash(address_hash, abi, metadata_from_verified_twin, options) + callback = Keyword.get(options, :callback, nil) + uid = Keyword.get(options, :uid) + + callback && callback.(result, uid) + + result end) - timeout = Application.get_env(:explorer, :proxy)[:implementation_data_fetching_timeout] + timeout = + Keyword.get(options, :timeout, Application.get_env(:explorer, :proxy)[:implementation_data_fetching_timeout]) case Task.yield(get_implementation_address_hash_task, timeout) || Task.ignore(get_implementation_address_hash_task) do @@ -1184,22 +1191,17 @@ defmodule Explorer.Chain.SmartContract do defp db_implementation_data_converter(string) when is_binary(string), do: string defp db_implementation_data_converter(other), do: to_string(other) - defp check_implementation_refetch_necessity(nil), do: true + @doc """ + Function checks by timestamp if new implementation fetching needed + """ + @spec check_implementation_refetch_necessity(Calendar.datetime() | nil) :: boolean() + def check_implementation_refetch_necessity(nil), do: true - defp check_implementation_refetch_necessity(timestamp) do + def check_implementation_refetch_necessity(timestamp) do if Application.get_env(:explorer, :proxy)[:caching_implementation_data_enabled] do now = DateTime.utc_now() - average_block_time = get_average_block_time_for_implementation_refetch() - - fresh_time_distance = - case average_block_time do - 0 -> - Application.get_env(:explorer, :proxy)[:fallback_cached_implementation_data_ttl] - - time -> - round(time) - end + fresh_time_distance = get_fresh_time_distance() timestamp |> DateTime.add(fresh_time_distance, :millisecond) @@ -1209,6 +1211,22 @@ defmodule Explorer.Chain.SmartContract do end end + @doc """ + Returns time interval in milliseconds in which fetched proxy info is not needed to be refetched + """ + @spec get_fresh_time_distance() :: integer() + def get_fresh_time_distance do + average_block_time = get_average_block_time_for_implementation_refetch() + + case average_block_time do + 0 -> + Application.get_env(:explorer, :proxy)[:fallback_cached_implementation_data_ttl] + + time -> + round(time) + end + end + defp get_average_block_time_for_implementation_refetch do if Application.get_env(:explorer, :proxy)[:implementation_data_ttl_via_avg_block_time] do case AverageBlockTime.average_block_time() do diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex index cbd6dd4ee55f..845f8f6e08e8 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -31,7 +31,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do Fetches into DB proxy contract implementation's address and name from different proxy patterns """ @spec fetch_implementation_address_hash(Hash.Address.t(), list(), boolean() | nil, [api?]) :: - {String.t() | nil, String.t() | nil} + {String.t() | nil | :empty, String.t() | nil | :empty} def fetch_implementation_address_hash(proxy_address_hash, proxy_abi, metadata_from_verified_twin, options) when not is_nil(proxy_address_hash) and not is_nil(proxy_abi) do implementation_address_hash_string = get_implementation_address_hash_string(proxy_address_hash, proxy_abi) diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/verification_status.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/verification_status.ex new file mode 100644 index 000000000000..403d040fb1b6 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/verification_status.ex @@ -0,0 +1,125 @@ +defmodule Explorer.Chain.SmartContract.Proxy.VerificationStatus do + @moduledoc """ + Represents single proxy verification submission + """ + + use Explorer.Schema + + import Ecto.Changeset + + alias Explorer.Chain.Hash + alias Explorer.{Chain, Repo} + + @typedoc """ + * `contract_address_hash` - address of the contract which was tried to verify + * `status` - submission status: :pending | :pass | :fail + * `uid` - unique verification identifier + """ + + @type t :: %__MODULE__{ + uid: String.t(), + contract_address_hash: Hash.Address.t(), + status: non_neg_integer() | atom() + } + + @typep status :: integer() | atom() + + @primary_key false + schema "proxy_smart_contract_verification_statuses" do + field(:uid, :string, primary_key: true) + field(:status, Ecto.Enum, values: [pending: 0, pass: 1, fail: 2]) + field(:contract_address_hash, Hash.Address) + + timestamps() + end + + @required_fields ~w(uid status contract_address_hash)a + + @doc """ + Creates a changeset based on the `struct` and `params`. + """ + @spec changeset(Explorer.Chain.SmartContract.Proxy.VerificationStatus.t()) :: Ecto.Changeset.t() + def changeset(%__MODULE__{} = struct, params \\ %{}) do + struct + |> cast(params, @required_fields) + |> validate_required(@required_fields) + end + + @doc """ + Inserts verification status + """ + @spec insert_status(String.t(), status(), Hash.Address.t() | String.t()) :: any() + def insert_status(uid, status, address_hash) do + {:ok, hash} = if is_binary(address_hash), do: Chain.string_to_address_hash(address_hash), else: {:ok, address_hash} + + %__MODULE__{} + |> changeset(%{uid: uid, status: status, contract_address_hash: hash}) + |> Repo.insert() + end + + @doc """ + Updates verification status + """ + @spec update_status(String.t(), status()) :: __MODULE__.t() + def update_status(uid, status) do + __MODULE__ + |> Repo.get_by(uid: uid) + |> changeset(%{status: status}) + |> Repo.update() + end + + @doc """ + Fetches verification status + """ + @spec fetch_status(binary()) :: __MODULE__.t() | nil + def fetch_status(uid) do + case validate_uid(uid) do + {:ok, valid_uid} -> + __MODULE__ + |> Repo.get_by(uid: valid_uid) + + _ -> + nil + end + end + + @doc """ + Generates uid based on address hash and timestamp + """ + @spec generate_uid(Explorer.Chain.Hash.t()) :: String.t() + def generate_uid(%Hash{byte_count: 20, bytes: address_hash}) do + address_encoded = Base.encode16(address_hash, case: :lower) + timestamp = DateTime.utc_now() |> DateTime.to_unix() |> Integer.to_string(16) |> String.downcase() + address_encoded <> timestamp + end + + @doc """ + Validates uid + """ + @spec validate_uid(String.t()) :: :error | {:ok, <<_::64, _::_*8>>} + def validate_uid(<<_address::binary-size(40), timestamp_hex::binary>> = uid) do + case Integer.parse(timestamp_hex, 16) do + {timestamp, ""} -> + if DateTime.utc_now() |> DateTime.to_unix() > timestamp do + {:ok, uid} + else + :error + end + + _ -> + :error + end + end + + def validate_uid(_), do: :error + + @doc """ + Sets proxy verification result + """ + @spec set_proxy_verification_result({String.t() | nil | :empty, String.t() | nil | :empty}, String.t()) :: + __MODULE__.t() + def set_proxy_verification_result({empty_or_nil, _}, uid) when empty_or_nil in [:empty, nil], + do: update_status(uid, :fail) + + def set_proxy_verification_result({_, _}, uid), do: update_status(uid, :pass) +end diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index 38dad6123521..e83f87d703c7 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -25,7 +25,6 @@ defmodule Explorer.Chain.TokenTransfer do use Explorer.Schema import Ecto.Changeset - 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, DenormalizationHelper, Hash, Log, TokenTransfer, Transaction} @@ -360,10 +359,14 @@ defmodule Explorer.Chain.TokenTransfer do def filter_by_type(query, _), do: query + @doc """ + Returns ecto query to fetch consensus token transfers + """ + @spec only_consensus_transfers_query() :: Ecto.Query.t() def only_consensus_transfers_query do from(token_transfer in __MODULE__, - inner_join: block in Block, - on: token_transfer.block_hash == block.hash, + inner_join: block in assoc(token_transfer, :block), + as: :block, where: block.consensus == true ) end @@ -394,4 +397,15 @@ defmodule Explorer.Chain.TokenTransfer do Repo.stream_reduce(query, [], &[&1 | &2]) end + + @doc """ + Returns ecto query to fetch consensus token transfers with ERC-721 token type + """ + @spec erc_721_token_transfers_query() :: Ecto.Query.t() + def erc_721_token_transfers_query do + only_consensus_transfers_query() + |> join(:inner, [tt], token in assoc(tt, :token), as: :token) + |> where([tt, token: token], token.type == "ERC-721") + |> preload([tt, token: token], [{:token, token}]) + end end diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index 023f74e9dadb..8c15dd3f828d 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -3,7 +3,9 @@ defmodule Explorer.Etherscan do The etherscan context. """ - import Ecto.Query, only: [from: 2, where: 3, or_where: 3, union: 2, subquery: 1, order_by: 3] + import Ecto.Query, + only: [from: 2, where: 3, or_where: 3, union: 2, subquery: 1, order_by: 3, limit: 2, offset: 2, preload: 3] + import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] alias Explorer.Etherscan.Logs @@ -286,6 +288,50 @@ defmodule Explorer.Etherscan do end end + @doc """ + Gets a list of ERC-721 token transfers for a given address_hash. If contract_address_hash is not nil, transfers will be filtered by contract. + """ + @spec list_nft_token_transfers(Hash.Address.t(), Hash.Address.t() | nil, map()) :: [TokenTransfer.t()] + def list_nft_token_transfers( + %Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash, + contract_address_hash, + options \\ @default_options + ) do + options + |> base_nft_token_transfers_query(contract_address_hash) + |> where([tt], tt.from_address_hash == ^address_hash or tt.to_address_hash == ^address_hash) + |> Repo.replica().all() + end + + @doc """ + Gets a list of ERC-721 token transfers for a given token contract_address_hash. + """ + @spec list_nft_token_transfers_by_token(Hash.Address.t(), map()) :: [TokenTransfer.t()] + def list_nft_token_transfers_by_token( + %Hash{byte_count: unquote(Hash.Address.byte_count())} = contract_address_hash, + options \\ @default_options + ) do + options + |> base_nft_token_transfers_query(contract_address_hash) + |> Repo.replica().all() + end + + defp base_nft_token_transfers_query(options, contract_address_hash) do + options = Map.merge(@default_options, options) + + TokenTransfer.erc_721_token_transfers_query() + |> where_contract_address_match(contract_address_hash) + |> order_by([tt], [ + {^options.order_by_direction, tt.block_number}, + {^options.order_by_direction, tt.log_index} + ]) + |> where_start_block_match_tt(options) + |> where_end_block_match_tt(options) + |> limit(^options.page_size) + |> offset(^offset(options)) + |> preload([block: block], [{:block, block}, :transaction]) + end + @doc """ Gets a list of blocks mined by `t:Explorer.Chain.Hash.Address.t/0`. @@ -508,6 +554,8 @@ defmodule Explorer.Etherscan do tt_specific_token_query = tt_query + |> where_start_block_match_tt(options) + |> where_end_block_match_tt(options) |> where_contract_address_match(contract_address_hash) wrapped_query = @@ -579,8 +627,6 @@ defmodule Explorer.Etherscan do end wrapped_query - |> where_start_transaction_block_match(options) - |> where_end_transaction_block_match(options) |> Repo.replica().all() end @@ -616,6 +662,18 @@ defmodule Explorer.Etherscan do end end + defp where_start_block_match_tt(query, %{start_block: nil}), do: query + + defp where_start_block_match_tt(query, %{start_block: start_block}) do + where(query, [tt], tt.block_number >= ^start_block) + end + + defp where_end_block_match_tt(query, %{end_block: nil}), do: query + + defp where_end_block_match_tt(query, %{end_block: end_block}) do + where(query, [tt], tt.block_number <= ^end_block) + end + defp where_start_timestamp_match(query, %{start_timestamp: nil}), do: query defp where_start_timestamp_match(query, %{start_timestamp: start_timestamp}) do @@ -639,7 +697,7 @@ defmodule Explorer.Etherscan do defp where_contract_address_match(query, nil), do: query defp where_contract_address_match(query, contract_address_hash) do - where(query, [tt, _], tt.token_contract_address_hash == ^contract_address_hash) + where(query, [tt], tt.token_contract_address_hash == ^contract_address_hash) end defp offset(options), do: (options.page_number - 1) * options.page_size diff --git a/apps/explorer/lib/explorer/etherscan/contracts.ex b/apps/explorer/lib/explorer/etherscan/contracts.ex index 5cfdc0c52b09..72975cfb5a21 100644 --- a/apps/explorer/lib/explorer/etherscan/contracts.ex +++ b/apps/explorer/lib/explorer/etherscan/contracts.ex @@ -16,8 +16,11 @@ defmodule Explorer.Etherscan.Contracts do alias Explorer.Chain.SmartContract.Proxy alias Explorer.Chain.SmartContract.Proxy.EIP1167 + @doc """ + Returns address with preloaded SmartContract and proxy info if it exists + """ @spec address_hash_to_address_with_source_code(Hash.Address.t()) :: Address.t() | nil - def address_hash_to_address_with_source_code(address_hash) do + def address_hash_to_address_with_source_code(address_hash, twin_needed? \\ true) do result = case Repo.replica().get(Address, address_hash) do nil -> @@ -39,8 +42,7 @@ defmodule Explorer.Etherscan.Contracts do } else address_verified_twin_contract = - EIP1167.get_implementation_address(address_hash) || - SmartContract.get_address_verified_twin_contract(address_hash).verified_contract + EIP1167.get_implementation_address(address_hash) || maybe_fetch_twin(twin_needed?, address_hash) compose_address_with_smart_contract( address_with_smart_contract, @@ -53,6 +55,9 @@ defmodule Explorer.Etherscan.Contracts do |> append_proxy_info() end + defp maybe_fetch_twin(twin_needed?, address_hash), + do: if(twin_needed?, do: SmartContract.get_address_verified_twin_contract(address_hash).verified_contract) + defp compose_address_with_smart_contract(address_with_smart_contract, address_verified_twin_contract) do if address_verified_twin_contract do formatted_code = format_source_code_output(address_verified_twin_contract) diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex b/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex index b9ae134d54cb..06911dfd333d 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex @@ -49,15 +49,12 @@ defmodule Explorer.SmartContract.Solidity.PublisherWorker do end def perform({"json_api", %{"address_hash" => address_hash} = params, json_input, uid}) when is_binary(uid) do - VerificationStatus.insert_status(uid, :pending, address_hash) - - case Publisher.publish_with_standard_json_input(params, json_input) do - {:ok, _contract} -> - VerificationStatus.update_status(uid, :pass) + publish_and_update_status(:publish_with_standard_json_input, [params, json_input], address_hash, uid) + end - {:error, _changeset} -> - VerificationStatus.update_status(uid, :fail) - end + def perform({"flattened_api", %{"address_hash" => address_hash} = params, external_libraries, uid}) + when is_binary(uid) do + publish_and_update_status(:publish, [address_hash, params, external_libraries], address_hash, uid) end defp broadcast(method, address_hash, args, conn \\ nil) do @@ -78,4 +75,16 @@ defmodule Explorer.SmartContract.Solidity.PublisherWorker do EventsPublisher.broadcast([{:contract_verification_result, {address_hash, result}}], :on_demand) end end + + defp publish_and_update_status(method, args, address_hash, uid) do + VerificationStatus.insert_status(uid, :pending, address_hash) + + case apply(Publisher, method, args) do + {:ok, _contract} -> + VerificationStatus.update_status(uid, :pass) + + {:error, _changeset} -> + VerificationStatus.update_status(uid, :fail) + end + end end diff --git a/apps/explorer/lib/explorer/tags/address_to_tag.ex b/apps/explorer/lib/explorer/tags/address_to_tag.ex index 26468cf29abb..a08e21290ac3 100644 --- a/apps/explorer/lib/explorer/tags/address_to_tag.ex +++ b/apps/explorer/lib/explorer/tags/address_to_tag.ex @@ -94,7 +94,7 @@ defmodule Explorer.Tags.AddressToTag do addresses_to_add |> Enum.map(fn address_hash_string -> with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), - :ok <- Chain.check_address_exists(address_hash) do + :ok <- Address.check_address_exists(address_hash) do %{ tag_id: tag_id, address_hash: address_hash, diff --git a/apps/explorer/priv/repo/migrations/20231227170848_add_proxy_verification_status.exs b/apps/explorer/priv/repo/migrations/20231227170848_add_proxy_verification_status.exs new file mode 100644 index 000000000000..0b20b0a914e2 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231227170848_add_proxy_verification_status.exs @@ -0,0 +1,17 @@ +defmodule Explorer.Repo.Migrations.AddProxyVerificationStatus do + use Ecto.Migration + + def change do + create table("proxy_smart_contract_verification_statuses", primary_key: false) do + add(:uid, :string, size: 64, primary_key: true) + add(:status, :int2, null: false) + + add( + :contract_address_hash, + references(:smart_contracts, column: :address_hash, on_delete: :delete_all, type: :bytea) + ) + + timestamps() + end + end +end diff --git a/cspell.json b/cspell.json index aa6c88ac854f..a88ef9a7a95f 100644 --- a/cspell.json +++ b/cspell.json @@ -542,7 +542,28 @@ "zindex", "zipcode", "zkbob", - "zkevm" + "zkevm", + "erts", + "Asfpp", + "Nerg", + "secp", + "qwertyuioiuytrewertyuioiuytrertyuio", + "urlset", + "lastmod", + "qitmeer", + "meer", + "DefiLlama", + "SOLIDITYSCAN", + "fkey", + "getcontractcreation", + "contractaddresses", + "tokennfttx", + "libraryname", + "libraryaddress", + "evmversion", + "verifyproxycontract", + "checkproxyverification", + "NOTOK" ], "enableFiletypes": [ "dotenv", From 1352619a5a68ba3e97478ef9cd152afb68e96f5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Jan 2024 18:22:55 +0300 Subject: [PATCH 310/607] Bump credo from 1.7.1 to 1.7.3 (#9117) * Bump credo from 1.7.1 to 1.7.3 Bumps [credo](https://github.com/rrrene/credo) from 1.7.1 to 1.7.3. - [Changelog](https://github.com/rrrene/credo/blob/master/CHANGELOG.md) - [Commits](https://github.com/rrrene/credo/commits/v1.7.3) --- updated-dependencies: - dependency-name: credo dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * replace is_ with question mark --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Viktor Baranov --- .../lib/block_scout_web/captcha_helper.ex | 6 ++--- .../api/v2/smart_contract_controller.ex | 2 +- .../controllers/api/v2/token_controller.ex | 10 ++++---- .../controllers/smart_contract_controller.ex | 12 +++++----- .../tokens/instance/holder_controller.ex | 4 ++-- .../tokens/instance/metadata_controller.ex | 2 +- .../tokens/instance/transfer_controller.ex | 4 ++-- .../controllers/tokens/instance_controller.ex | 2 +- .../templates/csv_export/index.html.eex | 2 +- .../block_scout_web/views/access_helper.ex | 10 ++++---- .../lib/block_scout_web/views/address_view.ex | 6 ++--- .../block_scout_web/views/api/v2/helper.ex | 12 +++++----- .../views/api/v2/transaction_view.ex | 8 +++---- .../lib/explorer/account/custom_abi.ex | 2 +- .../account/notifier/forbidden_address.ex | 6 ++--- .../lib/explorer/account/notifier/notify.ex | 4 ++-- apps/explorer/lib/explorer/chain.ex | 12 +++++----- apps/explorer/lib/explorer/chain/address.ex | 10 ++++---- .../lib/explorer/chain/block/reward.ex | 4 ++-- .../chain/cache/addresses_tabs_counters.ex | 10 ++++---- ...dress_internal_transaction_csv_exporter.ex | 2 +- .../address_transaction_csv_exporter.ex | 2 +- .../lib/explorer/chain/csv_export/helper.ex | 10 ++++---- .../lib/explorer/chain/hash/address.ex | 24 +++++++++---------- .../lib/explorer/chain/token/instance.ex | 10 ++++---- .../lib/explorer/chain/transaction.ex | 8 +++---- .../lib/indexer/fetcher/polygon_edge.ex | 4 ++-- .../token_instance/metadata_retriever.ex | 8 +++---- apps/indexer/lib/indexer/helper.ex | 6 ++--- .../polygon_edge/deposit_executes.ex | 2 +- .../transform/polygon_edge/withdrawals.ex | 2 +- .../indexer/transform/transaction_actions.ex | 4 ++-- mix.lock | 4 ++-- 33 files changed, 107 insertions(+), 107 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex b/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex index a158813b9921..78194bd8df7c 100644 --- a/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex @@ -18,7 +18,7 @@ defmodule BlockScoutWeb.CaptchaHelper do case HTTPoison.post("https://www.google.com/recaptcha/api/siteverify", body, headers, []) do {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> case Jason.decode!(body) do - %{"success" => true} = resp -> is_success?(resp) + %{"success" => true} = resp -> success?(resp) _ -> false end @@ -27,11 +27,11 @@ defmodule BlockScoutWeb.CaptchaHelper do end end - defp is_success?(%{"score" => score}) do + defp success?(%{"score" => score}) do check_recaptcha_v3_score(score) end - defp is_success?(_resp), do: true + defp success?(_resp), do: true defp check_recaptcha_v3_score(score) do if score >= 0.5 do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex index 0407d0160289..dfc4836bd9ca 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex @@ -205,7 +205,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do with {:format_address, {:ok, address_hash}} <- {:format_address, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), {:address, {:ok, address}} <- {:address, Chain.hash_to_address(address_hash)}, - {:is_smart_contract, true} <- {:is_smart_contract, Address.is_smart_contract(address)}, + {:is_smart_contract, true} <- {:is_smart_contract, Address.smart_contract?(address)}, response = SolidityScan.solidityscan_request(address_hash_string), {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do conn diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex index 2e18b6649ec2..767af3dbbbc6 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex @@ -104,7 +104,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), {:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)}, - {:not_found, false} <- {:not_found, Chain.is_erc_20_token?(token)}, + {:not_found, false} <- {:not_found, Chain.erc_20_token?(token)}, {:format, {:ok, holder_address_hash}} <- {:format, Chain.string_to_address_hash(holder_address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(holder_address_hash_string, params) do holder_address = Repo.get_by(Address, hash: holder_address_hash) @@ -160,7 +160,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), {:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)}, - {:not_found, false} <- {:not_found, Chain.is_erc_20_token?(token)}, + {:not_found, false} <- {:not_found, Chain.erc_20_token?(token)}, {:format, {token_id, ""}} <- {:format, Integer.parse(token_id_str)} do token_instance = case Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, address_hash, @api_true) do @@ -186,7 +186,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), {:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)}, - {:not_found, false} <- {:not_found, Chain.is_erc_20_token?(token)}, + {:not_found, false} <- {:not_found, Chain.erc_20_token?(token)}, {:format, {token_id, ""}} <- {:format, Integer.parse(token_id_str)} do paging_options = paging_options(params) @@ -216,7 +216,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), {:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)}, - {:not_found, false} <- {:not_found, Chain.is_erc_20_token?(token)}, + {:not_found, false} <- {:not_found, Chain.erc_20_token?(token)}, {:format, {token_id, ""}} <- {:format, Integer.parse(token_id_str)} do paging_options = paging_options(params) @@ -250,7 +250,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), {:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)}, - {:not_found, false} <- {:not_found, Chain.is_erc_20_token?(token)}, + {:not_found, false} <- {:not_found, Chain.erc_20_token?(token)}, {:format, {token_id, ""}} <- {:format, Integer.parse(token_id_str)} do conn |> put_status(200) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex index acaee3f7bd4d..6b333af67cf6 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex @@ -224,7 +224,7 @@ defmodule BlockScoutWeb.SmartContractController do end defp convert_map_to_array(map) do - if is_turned_out_array?(map) do + if turned_out_array?(map) do map |> Map.values() |> try_to_map_elements() else try_to_map_elements(map) @@ -239,11 +239,11 @@ defmodule BlockScoutWeb.SmartContractController do end end - defp is_turned_out_array?(map) when is_map(map), do: Enum.all?(Map.keys(map), &is_integer?/1) + defp turned_out_array?(map) when is_map(map), do: Enum.all?(Map.keys(map), &integer?/1) - defp is_turned_out_array?(_), do: false + defp turned_out_array?(_), do: false - defp is_integer?(string) when is_binary(string) do + defp integer?(string) when is_binary(string) do case string |> String.trim() |> Integer.parse() do {_, ""} -> true @@ -253,9 +253,9 @@ defmodule BlockScoutWeb.SmartContractController do end end - defp is_integer?(integer) when is_integer(integer), do: true + defp integer?(integer) when is_integer(integer), do: true - defp is_integer?(_), do: false + defp integer?(_), do: false defp write_contract_api_disabled?(action), do: AddressView.contract_interaction_disabled?() && action == "write" end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/holder_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/holder_controller.ex index 2ee82f5253cf..4794e14723b4 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/holder_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/holder_controller.ex @@ -12,7 +12,7 @@ defmodule BlockScoutWeb.Tokens.Instance.HolderController do def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id_str, "type" => "JSON"} = params) do with {:ok, address_hash} <- Chain.string_to_address_hash(token_address_hash), {:ok, token} <- Chain.token_from_address_hash(address_hash), - false <- Chain.is_erc_20_token?(token), + false <- Chain.erc_20_token?(token), {token_id, ""} <- Integer.parse(token_id_str), token_holders <- Chain.fetch_token_holders_from_token_hash_and_token_id(address_hash, token_id, paging_options(params)) do @@ -58,7 +58,7 @@ defmodule BlockScoutWeb.Tokens.Instance.HolderController do with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash), {:ok, token} <- Chain.token_from_address_hash(hash, options), - false <- Chain.is_erc_20_token?(token), + false <- Chain.erc_20_token?(token), {token_id, ""} <- Integer.parse(token_id_str) do case Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, hash) do {:ok, token_instance} -> Helper.render(conn, token_instance, hash, token_id, token) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/metadata_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/metadata_controller.ex index 2f02185d63af..fe691d770e17 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/metadata_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/metadata_controller.ex @@ -9,7 +9,7 @@ defmodule BlockScoutWeb.Tokens.Instance.MetadataController do with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash), {:ok, token} <- Chain.token_from_address_hash(hash, options), - false <- Chain.is_erc_20_token?(token), + false <- Chain.erc_20_token?(token), {token_id, ""} <- Integer.parse(token_id_str), {:ok, token_instance} <- Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, hash) do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex index 0eaa0115adc8..bd52bdba24f0 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex @@ -16,7 +16,7 @@ defmodule BlockScoutWeb.Tokens.Instance.TransferController do def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id_str, "type" => "JSON"} = params) do with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash), {:ok, token} <- Chain.token_from_address_hash(hash), - false <- Chain.is_erc_20_token?(token), + false <- Chain.erc_20_token?(token), {token_id, ""} <- Integer.parse(token_id_str), token_transfers <- Chain.fetch_token_transfers_from_token_hash_and_token_id(hash, token_id, paging_options(params)) do @@ -61,7 +61,7 @@ defmodule BlockScoutWeb.Tokens.Instance.TransferController do with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash), {:ok, token} <- Chain.token_from_address_hash(hash, options), - false <- Chain.is_erc_20_token?(token), + false <- Chain.erc_20_token?(token), {token_id, ""} <- Integer.parse(token_id_str) do case Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, hash) do {:ok, token_instance} -> Helper.render(conn, token_instance, hash, token_id, token) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance_controller.ex index 65d11fae762b..fd222f9294e6 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance_controller.ex @@ -7,7 +7,7 @@ defmodule BlockScoutWeb.Tokens.InstanceController do def show(conn, %{"token_id" => token_address_hash, "id" => token_id}) do with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash), {:ok, token} <- Chain.token_from_address_hash(hash), - false <- Chain.is_erc_20_token?(token) do + false <- Chain.erc_20_token?(token) do token_instance_transfer_path = conn |> token_instance_transfer_path(:index, token_address_hash, token_id) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex index 979926300cf6..19e010c26e23 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex @@ -10,7 +10,7 @@

<%= gettext "Export Data" %>

- <% filter_text = if Helper.is_valid_filter?(@filter_type, @filter_value, @type), do: " with applied filter by #{@filter_type} (#{@filter_value})", else: "" %> + <% filter_text = if Helper.valid_filter?(@filter_type, @filter_value, @type), do: " with applied filter by #{@filter_type} (#{@filter_value})", else: "" %>

<%= gettext("Export") %> <%= type_display_name(@type) %> <%= gettext("for address") %> <%= link( @address_hash_string, to: address_path(@conn, :show, @address_hash_string) diff --git a/apps/block_scout_web/lib/block_scout_web/views/access_helper.ex b/apps/block_scout_web/lib/block_scout_web/views/access_helper.ex index d0fb8d0c574a..ecfb0ffe556e 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/access_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/access_helper.ex @@ -93,10 +93,10 @@ defmodule BlockScoutWeb.AccessHelper do Enum.member?(whitelisted_ips(rate_limit_config), ip_string) -> rate_limit(ip_string, limit_by_whitelisted_ip, time_interval_limit) - is_api_v2_request?(conn) && !is_nil(token) && !is_nil(user_agent) -> + api_v2_request?(conn) && !is_nil(token) && !is_nil(user_agent) -> rate_limit(token, api_v2_ui_limit, time_interval_limit) - is_api_v2_request?(conn) && !is_nil(user_agent) -> + api_v2_request?(conn) && !is_nil(user_agent) -> rate_limit(ip_string, limit_by_ip, time_interval_by_ip) true -> @@ -155,8 +155,8 @@ defmodule BlockScoutWeb.AccessHelper do end end - defp is_api_v2_request?(%Plug.Conn{request_path: "/api/v2/" <> _}), do: true - defp is_api_v2_request?(_), do: false + defp api_v2_request?(%Plug.Conn{request_path: "/api/v2/" <> _}), do: true + defp api_v2_request?(_), do: false def conn_to_ip_string(conn) do is_blockscout_behind_proxy = Application.get_env(:block_scout_web, :api_rate_limit)[:is_blockscout_behind_proxy] @@ -171,7 +171,7 @@ defmodule BlockScoutWeb.AccessHelper do api_v2_temp_token_key = Application.get_env(:block_scout_web, :api_v2_temp_token_key) conn = Conn.fetch_cookies(conn, signed: [api_v2_temp_token_key]) - case is_api_v2_request?(conn) && conn.cookies[api_v2_temp_token_key] do + case api_v2_request?(conn) && conn.cookies[api_v2_temp_token_key] do %{ip: ^ip_string} -> conn.req_cookies[api_v2_temp_token_key] diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex index ed3f54040657..532aceed6b5f 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex @@ -256,12 +256,12 @@ defmodule BlockScoutWeb.AddressView do def smart_contract_verified?(%Address{smart_contract: nil}), do: false def smart_contract_with_read_only_functions?(%Address{smart_contract: %SmartContract{}} = address) do - Enum.any?(address.smart_contract.abi || [], &is_read_function?(&1)) + Enum.any?(address.smart_contract.abi || [], &read_function?(&1)) end def smart_contract_with_read_only_functions?(%Address{smart_contract: _}), do: false - def is_read_function?(function), do: Helper.queriable_method?(function) || Helper.read_with_wallet_method?(function) + def read_function?(function), do: Helper.queriable_method?(function) || Helper.read_with_wallet_method?(function) def smart_contract_is_proxy?(address, options \\ []) @@ -480,7 +480,7 @@ defmodule BlockScoutWeb.AddressView do end def check_custom_abi_for_having_read_functions(custom_abi), - do: !is_nil(custom_abi) && Enum.any?(custom_abi.abi, &is_read_function?(&1)) + do: !is_nil(custom_abi) && Enum.any?(custom_abi.abi, &read_function?(&1)) def has_address_custom_abi_with_write_functions?(conn, address_hash) do if contract_interaction_disabled?() do diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex index 010171545d3b..2839b49439da 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex @@ -55,10 +55,10 @@ defmodule BlockScoutWeb.API.V2.Helper do def address_with_info(%Address{} = address, _address_hash) do %{ "hash" => Address.checksum(address), - "is_contract" => Address.is_smart_contract(address), + "is_contract" => Address.smart_contract?(address), "name" => address_name(address), "implementation_name" => implementation_name(address), - "is_verified" => is_verified(address), + "is_verified" => verified?(address), "ens_domain_name" => address.ens_domain_name } end @@ -105,10 +105,10 @@ defmodule BlockScoutWeb.API.V2.Helper do def implementation_name(_), do: nil - def is_verified(%Address{smart_contract: nil}), do: false - def is_verified(%Address{smart_contract: %{metadata_from_verified_twin: true}}), do: false - def is_verified(%Address{smart_contract: %NotLoaded{}}), do: nil - def is_verified(%Address{smart_contract: _}), do: true + def verified?(%Address{smart_contract: nil}), do: false + def verified?(%Address{smart_contract: %{metadata_from_verified_twin: true}}), do: false + def verified?(%Address{smart_contract: %NotLoaded{}}), do: nil + def verified?(%Address{smart_contract: _}), do: true def market_cap(:standard, %{available_supply: available_supply, usd_value: usd_value, market_cap_usd: market_cap_usd}) when is_nil(available_supply) or is_nil(usd_value) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index a3ac1335ea45..0db3dc1a8e68 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -736,7 +736,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do _, skip_sc_check? ) do - if skip_sc_check? || Address.is_smart_contract(to_address) do + if skip_sc_check? || Address.smart_contract?(to_address) do "0x" <> Base.encode16(method_id, case: :lower) else nil @@ -805,7 +805,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do def tx_types(%Transaction{to_address: to_address} = tx, types, :contract_call) do types = - if Address.is_smart_contract(to_address) do + if Address.smart_contract?(to_address) do [:contract_call | types] else types @@ -827,7 +827,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do def tx_types(tx, types, :rootstock_remasc) do types = - if Transaction.is_rootstock_remasc_transaction(tx) do + if Transaction.rootstock_remasc_transaction?(tx) do [:rootstock_remasc | types] else types @@ -837,7 +837,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end def tx_types(tx, types, :rootstock_bridge) do - if Transaction.is_rootstock_bridge_transaction(tx) do + if Transaction.rootstock_bridge_transaction?(tx) do [:rootstock_bridge | types] else types diff --git a/apps/explorer/lib/explorer/account/custom_abi.ex b/apps/explorer/lib/explorer/account/custom_abi.ex index b48596b7ceec..58da24a50c99 100644 --- a/apps/explorer/lib/explorer/account/custom_abi.ex +++ b/apps/explorer/lib/explorer/account/custom_abi.ex @@ -65,7 +65,7 @@ defmodule Explorer.Account.CustomABI do defp check_smart_contract_address(custom_abi), do: custom_abi defp check_smart_contract_address_inner(changeset, address_hash) do - if Chain.is_address_hash_is_smart_contract?(address_hash) do + if Chain.address_hash_is_smart_contract?(address_hash) do changeset else add_error(changeset, :address_hash, "Address is not a smart contract") diff --git a/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex b/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex index b20cd898ed2a..f977b3e4dcb2 100644 --- a/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex +++ b/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex @@ -31,7 +31,7 @@ defmodule Explorer.Account.Notifier.ForbiddenAddress do address_hash in blacklist() -> {:error, "This address is blacklisted"} - is_contract(address_hash) -> + contract?(address_hash) -> {:error, "This address isn't EOA"} match?({:restricted_access, true}, AccessHelper.restricted_access?(to_string(address_hash), %{})) -> @@ -42,10 +42,10 @@ defmodule Explorer.Account.Notifier.ForbiddenAddress do end end - defp is_contract(%Explorer.Chain.Hash{} = address_hash) do + defp contract?(%Explorer.Chain.Hash{} = address_hash) do case hash_to_address(address_hash) do {:error, :not_found} -> false - {:ok, address} -> Address.is_smart_contract(address) + {:ok, address} -> Address.smart_contract?(address) end end diff --git a/apps/explorer/lib/explorer/account/notifier/notify.ex b/apps/explorer/lib/explorer/account/notifier/notify.ex index f9c9232546ac..47adfc1b2c0c 100644 --- a/apps/explorer/lib/explorer/account/notifier/notify.ex +++ b/apps/explorer/lib/explorer/account/notifier/notify.ex @@ -117,7 +117,7 @@ defmodule Explorer.Account.Notifier.Notify do direction = :incoming || :outgoing """ def build_watchlist_notification(%Explorer.Account.WatchlistAddress{} = address, summary, direction) do - if is_watched(address, summary, direction) do + if watched?(address, summary, direction) do %WatchlistNotification{ watchlist_address_id: address.id, watchlist_id: address.watchlist_id, @@ -140,7 +140,7 @@ defmodule Explorer.Account.Notifier.Notify do end end - defp is_watched(%WatchlistAddress{} = address, %{type: type}, direction) do + defp watched?(%WatchlistAddress{} = address, %{type: type}, direction) do case {type, direction} do {"COIN", :incoming} -> address.watch_coin_input {"COIN", :outgoing} -> address.watch_coin_output diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 93f37e813910..1e7d51f3cacc 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -4369,12 +4369,12 @@ defmodule Explorer.Chain do Repo.one(query) end - @spec is_erc_20_token?(Token.t()) :: bool - def is_erc_20_token?(token) do - is_erc_20_token_type?(token.type) + @spec erc_20_token?(Token.t()) :: bool + def erc_20_token?(token) do + erc_20_token_type?(token.type) end - defp is_erc_20_token_type?(type) do + defp erc_20_token_type?(type) do case type do "ERC-20" -> true _ -> false @@ -4826,9 +4826,9 @@ defmodule Explorer.Chain do |> Repo.one() end - def is_address_hash_is_smart_contract?(nil), do: false + def address_hash_is_smart_contract?(nil), do: false - def is_address_hash_is_smart_contract?(address_hash) do + def address_hash_is_smart_contract?(address_hash) do with %Address{contract_code: bytecode} <- Repo.get_by(Address, hash: address_hash), false <- is_nil(bytecode) do true diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index f256f7ade126..83458445cd12 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -340,11 +340,11 @@ defmodule Explorer.Chain.Address do @doc """ Checks if given address is smart-contract """ - @spec is_smart_contract(any()) :: boolean() | nil - def is_smart_contract(%__MODULE__{contract_code: nil}), do: false - def is_smart_contract(%__MODULE__{contract_code: _}), do: true - def is_smart_contract(%NotLoaded{}), do: nil - def is_smart_contract(_), do: false + @spec smart_contract?(any()) :: boolean() | nil + def smart_contract?(%__MODULE__{contract_code: nil}), do: false + def smart_contract?(%__MODULE__{contract_code: _}), do: true + def smart_contract?(%NotLoaded{}), do: nil + def smart_contract?(_), do: false defp get_addresses(options) do accounts_with_n = fetch_top_addresses(options) diff --git a/apps/explorer/lib/explorer/chain/block/reward.ex b/apps/explorer/lib/explorer/chain/block/reward.ex index d45df2666669..a05b1cf1b7cd 100644 --- a/apps/explorer/lib/explorer/chain/block/reward.ex +++ b/apps/explorer/lib/explorer/chain/block/reward.ex @@ -143,7 +143,7 @@ defmodule Explorer.Chain.Block.Reward do end end - defp is_validator(mining_key) do + defp validator?(mining_key) do validators_contract_address = Application.get_env(:explorer, Explorer.Chain.Block.Reward, %{})[:validators_contract_address] @@ -191,7 +191,7 @@ defmodule Explorer.Chain.Block.Reward do end def get_validator_payout_key_by_mining(mining_key) do - is_validator = is_validator(mining_key) + is_validator = validator?(mining_key) if is_validator do keys_manager_contract_address = diff --git a/apps/explorer/lib/explorer/chain/cache/addresses_tabs_counters.ex b/apps/explorer/lib/explorer/chain/cache/addresses_tabs_counters.ex index ab79fbf0a5f2..750617da62c9 100644 --- a/apps/explorer/lib/explorer/chain/cache/addresses_tabs_counters.ex +++ b/apps/explorer/lib/explorer/chain/cache/addresses_tabs_counters.ex @@ -76,7 +76,7 @@ defmodule Explorer.Chain.Cache.AddressesTabsCounters do def handle_cast({:set_txs_state, address_hash, %{txs_types: txs_types} = results}, state) do address_hash = lowercased_string(address_hash) - if is_ignored?(state[address_hash]) do + if ignored?(state[address_hash]) do {:noreply, state} else address_state = @@ -104,15 +104,15 @@ defmodule Explorer.Chain.Cache.AddressesTabsCounters do end end - defp is_ignored?({:updated, datetime}), do: is_up_to_date?(datetime, ttl()) - defp is_ignored?(_), do: false + defp ignored?({:updated, datetime}), do: up_to_date?(datetime, ttl()) + defp ignored?(_), do: false defp check_staleness(nil), do: nil defp check_staleness({datetime, counter}) when counter > 50, do: {datetime, counter, :limit_value} defp check_staleness({datetime, counter}) do status = - if is_up_to_date?(datetime, ttl()) do + if up_to_date?(datetime, ttl()) do :up_to_date else :stale @@ -121,7 +121,7 @@ defmodule Explorer.Chain.Cache.AddressesTabsCounters do {datetime, counter, status} end - defp is_up_to_date?(datetime, ttl) do + defp up_to_date?(datetime, ttl) do datetime |> DateTime.add(ttl, :millisecond) |> DateTime.compare(DateTime.utc_now()) != :lt diff --git a/apps/explorer/lib/explorer/chain/csv_export/address_internal_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_internal_transaction_csv_exporter.ex index f54ae2b36d13..de4bcad24d12 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/address_internal_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address_internal_transaction_csv_exporter.ex @@ -34,7 +34,7 @@ defmodule Explorer.Chain.CSVExport.AddressInternalTransactionCsvExporter do |> Keyword.put(:paging_options, paging_options) |> Keyword.put(:from_block, from_block) |> Keyword.put(:to_block, to_block) - |> (&if(Helper.is_valid_filter?(filter_type, filter_value, "internal_transactions"), + |> (&if(Helper.valid_filter?(filter_type, filter_value, "internal_transactions"), do: &1 |> Keyword.put(:direction, String.to_atom(filter_value)), else: &1 )).() diff --git a/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex index 600d53d63aa3..52f7298c1551 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex @@ -34,7 +34,7 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do |> Keyword.put(:paging_options, paging_options) |> Keyword.put(:from_block, from_block) |> Keyword.put(:to_block, to_block) - |> (&if(Helper.is_valid_filter?(filter_type, filter_value, "transactions"), + |> (&if(Helper.valid_filter?(filter_type, filter_value, "transactions"), do: &1 |> Keyword.put(:direction, String.to_atom(filter_value)), else: &1 )).() diff --git a/apps/explorer/lib/explorer/chain/csv_export/helper.ex b/apps/explorer/lib/explorer/chain/csv_export/helper.ex index 92cf9687c427..ea9e6c9fc129 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/helper.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/helper.ex @@ -88,16 +88,16 @@ defmodule Explorer.Chain.CSVExport.Helper do ["to", "from"] end - @spec is_valid_filter?(String.t(), String.t(), String.t()) :: boolean() - def is_valid_filter?(filter_type, filter_value, item_type) do - is_valid_filter_type(filter_type, filter_value, item_type) && is_valid_filter_value(filter_type, filter_value) + @spec valid_filter?(String.t(), String.t(), String.t()) :: boolean() + def valid_filter?(filter_type, filter_value, item_type) do + valid_filter_type?(filter_type, filter_value, item_type) && valid_filter_value?(filter_type, filter_value) end - defp is_valid_filter_type(filter_type, filter_value, item_type) do + defp valid_filter_type?(filter_type, filter_value, item_type) do filter_type in supported_filters(item_type) && filter_value && filter_value !== "" end - defp is_valid_filter_value(filter_type, filter_value) do + defp valid_filter_value?(filter_type, filter_value) do case filter_type do "address" -> filter_value in supported_address_filter_values() diff --git a/apps/explorer/lib/explorer/chain/hash/address.ex b/apps/explorer/lib/explorer/chain/hash/address.ex index 930774ee5944..f37db26dee9d 100644 --- a/apps/explorer/lib/explorer/chain/hash/address.ex +++ b/apps/explorer/lib/explorer/chain/hash/address.ex @@ -169,9 +169,9 @@ defmodule Explorer.Chain.Hash.Address do @spec validate(String.t()) :: {:ok, String.t()} | {:error, :invalid_length | :invalid_characters | :invalid_checksum} def validate("0x" <> hash) do with {:length, true} <- {:length, String.length(hash) == 40}, - {:hex, true} <- {:hex, is_hex?(hash)}, - {:mixed_case, true} <- {:mixed_case, is_mixed_case?(hash)}, - {:checksummed, true} <- {:checksummed, is_checksummed?(hash)} do + {:hex, true} <- {:hex, hex?(hash)}, + {:mixed_case, true} <- {:mixed_case, mixed_case?(hash)}, + {:checksummed, true} <- {:checksummed, checksummed?(hash)} do {:ok, "0x" <> hash} else {:length, false} -> @@ -188,16 +188,16 @@ defmodule Explorer.Chain.Hash.Address do end end - @spec is_hex?(String.t()) :: boolean() - defp is_hex?(hash) do + @spec hex?(String.t()) :: boolean() + defp hex?(hash) do case Regex.run(~r|[0-9a-f]{40}|i, hash) do nil -> false [_] -> true end end - @spec is_mixed_case?(String.t()) :: boolean() - defp is_mixed_case?(hash) do + @spec mixed_case?(String.t()) :: boolean() + defp mixed_case?(hash) do upper_check = ~r|[0-9A-F]{40}| lower_check = ~r|[0-9a-f]{40}| @@ -209,8 +209,8 @@ defmodule Explorer.Chain.Hash.Address do end end - @spec is_checksummed?(String.t()) :: boolean() - defp is_checksummed?(original_hash) do + @spec checksummed?(String.t()) :: boolean() + defp checksummed?(original_hash) do lowercase_hash = String.downcase(original_hash) sha3_hash = ExKeccak.hash_256(lowercase_hash) @@ -224,15 +224,15 @@ defmodule Explorer.Chain.Hash.Address do <> = sha3_hash <> = address_hash - if is_proper_case?(checksum_digit, current_char) do + if proper_case?(checksum_digit, current_char) do do_checksum_check(remaining_sha3_hash, remaining_address_hash) else false end end - @spec is_proper_case?(integer, String.t()) :: boolean() - defp is_proper_case?(checksum_digit, character) do + @spec proper_case?(integer, String.t()) :: boolean() + defp proper_case?(checksum_digit, character) do case_map = %{ "0" => :both, "1" => :both, diff --git a/apps/explorer/lib/explorer/chain/token/instance.ex b/apps/explorer/lib/explorer/chain/token/instance.ex index 43249f7c722b..04af69a30e99 100644 --- a/apps/explorer/lib/explorer/chain/token/instance.ex +++ b/apps/explorer/lib/explorer/chain/token/instance.ex @@ -444,10 +444,10 @@ defmodule Explorer.Chain.Token.Instance do end def put_is_unique(instance, token, options) do - %__MODULE__{instance | is_unique: is_unique?(instance, token, options)} + %__MODULE__{instance | is_unique: unique?(instance, token, options)} end - defp is_unique?( + defp unique?( %Instance{current_token_balance: %CurrentTokenBalance{value: %Decimal{} = value}} = instance, token, options @@ -455,15 +455,15 @@ defmodule Explorer.Chain.Token.Instance do if Decimal.compare(value, 1) == :gt do false else - is_unique?(%Instance{instance | current_token_balance: nil}, token, options) + unique?(%Instance{instance | current_token_balance: nil}, token, options) end end - defp is_unique?(%Instance{current_token_balance: %CurrentTokenBalance{value: value}}, _token, _options) + defp unique?(%Instance{current_token_balance: %CurrentTokenBalance{value: value}}, _token, _options) when value > 1, do: false - defp is_unique?(instance, token, options), + defp unique?(instance, token, options), do: not (token.type == "ERC-1155") or Chain.token_id_1155_is_unique?(token.contract_address_hash, instance.token_id, options) diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 10b730d6bcd7..18cdfee76877 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -1124,8 +1124,8 @@ defmodule Explorer.Chain.Transaction do @doc """ Returns true if the transaction is a Rootstock REMASC transaction. """ - @spec is_rootstock_remasc_transaction(Explorer.Chain.Transaction.t()) :: boolean - def is_rootstock_remasc_transaction(%__MODULE__{to_address_hash: to_address_hash}) do + @spec rootstock_remasc_transaction?(Explorer.Chain.Transaction.t()) :: boolean + def rootstock_remasc_transaction?(%__MODULE__{to_address_hash: to_address_hash}) do case Hash.Address.cast(Application.get_env(:explorer, __MODULE__)[:rootstock_remasc_address]) do {:ok, address} -> address == to_address_hash _ -> false @@ -1135,8 +1135,8 @@ defmodule Explorer.Chain.Transaction do @doc """ Returns true if the transaction is a Rootstock bridge transaction. """ - @spec is_rootstock_bridge_transaction(Explorer.Chain.Transaction.t()) :: boolean - def is_rootstock_bridge_transaction(%__MODULE__{to_address_hash: to_address_hash}) do + @spec rootstock_bridge_transaction?(Explorer.Chain.Transaction.t()) :: boolean + def rootstock_bridge_transaction?(%__MODULE__{to_address_hash: to_address_hash}) do case Hash.Address.cast(Application.get_env(:explorer, __MODULE__)[:rootstock_bridge_address]) do {:ok, address} -> address == to_address_hash _ -> false diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge.ex index 9365bd58609f..29cb94def706 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge.ex @@ -83,7 +83,7 @@ defmodule Indexer.Fetcher.PolygonEdge do {:reorg_monitor_started, !is_nil(Process.whereis(Indexer.Fetcher.PolygonEdge))}, polygon_edge_l1_rpc = Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonEdge][:polygon_edge_l1_rpc], {:rpc_l1_undefined, false} <- {:rpc_l1_undefined, is_nil(polygon_edge_l1_rpc)}, - {:contract_is_valid, true} <- {:contract_is_valid, Helper.is_address_correct?(contract_address)}, + {:contract_is_valid, true} <- {:contract_is_valid, Helper.address_correct?(contract_address)}, start_block_l1 = parse_integer(env[:start_block_l1]), false <- is_nil(start_block_l1), true <- start_block_l1 > 0, @@ -163,7 +163,7 @@ defmodule Indexer.Fetcher.PolygonEdge do def init_l2(table, env, pid, contract_address, contract_name, table_name, entity_name, json_rpc_named_arguments) when table in [Explorer.Chain.PolygonEdge.DepositExecute, Explorer.Chain.PolygonEdge.Withdrawal] do with {:start_block_l2_undefined, false} <- {:start_block_l2_undefined, is_nil(env[:start_block_l2])}, - {:contract_address_valid, true} <- {:contract_address_valid, Helper.is_address_correct?(contract_address)}, + {:contract_address_valid, true} <- {:contract_address_valid, Helper.address_correct?(contract_address)}, start_block_l2 = parse_integer(env[:start_block_l2]), false <- is_nil(start_block_l2), true <- start_block_l2 > 0, diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex b/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex index a4812fb332a5..271da131176b 100644 --- a/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex +++ b/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex @@ -215,8 +215,8 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do end defp check_content_type(content_type, uri, hex_token_id, body) do - image = is_image?(content_type) - video = is_video?(content_type) + image = image?(content_type) + video = video?(content_type) if content_type && (image || video) do json = if image, do: %{"image" => uri}, else: %{"animation_url" => uri} @@ -238,11 +238,11 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do content_type end - defp is_image?(content_type) do + defp image?(content_type) do content_type && String.starts_with?(content_type, "image/") end - defp is_video?(content_type) do + defp video?(content_type) do content_type && String.starts_with?(content_type, "video/") end diff --git a/apps/indexer/lib/indexer/helper.ex b/apps/indexer/lib/indexer/helper.ex index 21b69831f26e..f0d1a3154e38 100644 --- a/apps/indexer/lib/indexer/helper.ex +++ b/apps/indexer/lib/indexer/helper.ex @@ -24,12 +24,12 @@ defmodule Indexer.Helper do end end - @spec is_address_correct?(binary()) :: boolean() - def is_address_correct?(address) when is_binary(address) do + @spec address_correct?(binary()) :: boolean() + def address_correct?(address) when is_binary(address) do String.match?(address, ~r/^0x[[:xdigit:]]{40}$/i) end - def is_address_correct?(_address) do + def address_correct?(_address) do false end diff --git a/apps/indexer/lib/indexer/transform/polygon_edge/deposit_executes.ex b/apps/indexer/lib/indexer/transform/polygon_edge/deposit_executes.ex index 93dec160353c..0e3ddeae5520 100644 --- a/apps/indexer/lib/indexer/transform/polygon_edge/deposit_executes.ex +++ b/apps/indexer/lib/indexer/transform/polygon_edge/deposit_executes.ex @@ -20,7 +20,7 @@ defmodule Indexer.Transform.PolygonEdge.DepositExecutes do with false <- is_nil(Application.get_env(:indexer, DepositExecute)[:start_block_l2]), state_receiver = Application.get_env(:indexer, DepositExecute)[:state_receiver], - true <- Helper.is_address_correct?(state_receiver) do + true <- Helper.address_correct?(state_receiver) do state_receiver = String.downcase(state_receiver) state_sync_result_event_signature = DepositExecute.state_sync_result_event_signature() diff --git a/apps/indexer/lib/indexer/transform/polygon_edge/withdrawals.ex b/apps/indexer/lib/indexer/transform/polygon_edge/withdrawals.ex index 1562f1fea61d..02ffcbaaa71e 100644 --- a/apps/indexer/lib/indexer/transform/polygon_edge/withdrawals.ex +++ b/apps/indexer/lib/indexer/transform/polygon_edge/withdrawals.ex @@ -19,7 +19,7 @@ defmodule Indexer.Transform.PolygonEdge.Withdrawals do items = with false <- is_nil(Application.get_env(:indexer, Withdrawal)[:start_block_l2]), state_sender = Application.get_env(:indexer, Withdrawal)[:state_sender], - true <- Helper.is_address_correct?(state_sender) do + true <- Helper.address_correct?(state_sender) do state_sender = String.downcase(state_sender) l2_state_synced_event_signature = Withdrawal.l2_state_synced_event_signature() diff --git a/apps/indexer/lib/indexer/transform/transaction_actions.ex b/apps/indexer/lib/indexer/transform/transaction_actions.ex index 618c311f2a95..8c5e34262b0b 100644 --- a/apps/indexer/lib/indexer/transform/transaction_actions.ex +++ b/apps/indexer/lib/indexer/transform/transaction_actions.ex @@ -677,10 +677,10 @@ defmodule Indexer.Transform.TransactionActions do end) |> Enum.map(fn {pool_address, pool} -> token0 = - if Helper.is_address_correct?(pool.token0), do: String.downcase(pool.token0), else: burn_address_hash_string() + if Helper.address_correct?(pool.token0), do: String.downcase(pool.token0), else: burn_address_hash_string() token1 = - if Helper.is_address_correct?(pool.token1), do: String.downcase(pool.token1), else: burn_address_hash_string() + if Helper.address_correct?(pool.token1), do: String.downcase(pool.token1), else: burn_address_hash_string() fee = if pool.fee == "", do: 0, else: pool.fee diff --git a/mix.lock b/mix.lock index eff0c9efd4a6..c76820d08ec5 100644 --- a/mix.lock +++ b/mix.lock @@ -9,7 +9,7 @@ "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "benchee_csv": {:hex, :benchee_csv, "1.0.0", "0b3b9223290bfcb8003552705bec9bcf1a89b4a83b70bd686e45295c264f3d16", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:csv, "~> 2.0", [hex: :csv, repo: "hexpm", optional: false]}], "hexpm", "cdefb804c021dcf7a99199492026584be9b5a21d6644ac0d01c81c5d97c520d5"}, "briefly": {:git, "https://github.com/CargoSense/briefly.git", "4836ba322ffb504a102a15cc6e35d928ef97120e", []}, - "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "bureaucrat": {:hex, :bureaucrat, "0.2.9", "d98e4d2b9bdbf22e4a45c2113ce8b38b5b63278506c6ff918e3b943a4355d85b", [:mix], [{:inflex, ">= 1.10.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.2.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, ">= 1.0.0", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "111c8dd84382a62e1026ae011d592ceee918553e5203fe8448d9ba6ccbdfff7d"}, "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, "castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"}, @@ -27,7 +27,7 @@ "cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"}, "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, - "credo": {:hex, :credo, "1.7.1", "6e26bbcc9e22eefbff7e43188e69924e78818e2fe6282487d0703652bc20fd62", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e9871c6095a4c0381c89b6aa98bc6260a8ba6addccf7f6a53da8849c748a58a2"}, + "credo": {:hex, :credo, "1.7.3", "05bb11eaf2f2b8db370ecaa6a6bda2ec49b2acd5e0418bc106b73b07128c0436", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "35ea675a094c934c22fb1dca3696f3c31f2728ae6ef5a53b5d648c11180a4535"}, "csv": {:hex, :csv, "2.5.0", "c47b5a5221bf2e56d6e8eb79e77884046d7fd516280dc7d9b674251e0ae46246", [:mix], [{:parallel_stream, "~> 1.0.4 or ~> 1.1.0", [hex: :parallel_stream, repo: "hexpm", optional: false]}], "hexpm", "e821f541487045c7591a1963eeb42afff0dfa99bdcdbeb3410795a2f59c77d34"}, "dataloader": {:hex, :dataloader, "1.0.11", "49bbfc7dd8a1990423c51000b869b1fecaab9e3ccd6b29eab51616ae8ad0a2f5", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba0b0ec532ec68e9d033d03553561d693129bd7cbd5c649dc7903f07ffba08fe"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, From bffaa1c4e38a78170d9d8f90169e4b7c9da50a5d Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 19 Jan 2024 16:54:01 +0300 Subject: [PATCH 311/607] Migrate to Postgres 15 --- .github/workflows/config.yml | 8 ++++---- CHANGELOG.md | 1 + docker-compose/services/db.yml | 4 ++-- docker-compose/services/stats.yml | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 80ffe70c0bc0..14bb38b8d2ea 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -410,7 +410,7 @@ jobs: - matrix-builder services: postgres: - image: postgres + image: postgres:15 env: # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database POSTGRES_DB: explorer_test @@ -470,7 +470,7 @@ jobs: - matrix-builder services: postgres: - image: postgres + image: postgres:15 env: # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database POSTGRES_DB: explorer_test @@ -541,7 +541,7 @@ jobs: - matrix-builder services: postgres: - image: postgres + image: postgres:15 env: # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database POSTGRES_DB: explorer_test @@ -609,7 +609,7 @@ jobs: - 6379:6379 postgres: - image: postgres + image: postgres:15 env: # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database POSTGRES_DB: explorer_test diff --git a/CHANGELOG.md b/CHANGELOG.md index aa7cd8ba8b0a..e461500219f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ ### Chore +- [#9198](https://github.com/blockscout/blockscout/pull/9198) - Make Postgres@15 default option - [#9196](https://github.com/blockscout/blockscout/pull/9196) - Compatibility with docker-compose 2.24 - [#9193](https://github.com/blockscout/blockscout/pull/9193) - Equalize elixir stack versions diff --git a/docker-compose/services/db.yml b/docker-compose/services/db.yml index edea5693d66c..a7370d6b1531 100644 --- a/docker-compose/services/db.yml +++ b/docker-compose/services/db.yml @@ -2,7 +2,7 @@ version: '3.9' services: db-init: - image: postgres:14 + image: postgres:15 volumes: - ./blockscout-db-data:/var/lib/postgresql/data entrypoint: @@ -15,7 +15,7 @@ services: depends_on: db-init: condition: service_completed_successfully - image: postgres:14 + image: postgres:15 user: 2000:2000 shm_size: 256m restart: always diff --git a/docker-compose/services/stats.yml b/docker-compose/services/stats.yml index 77b9ad76ba39..15b1a8d1ff46 100644 --- a/docker-compose/services/stats.yml +++ b/docker-compose/services/stats.yml @@ -2,7 +2,7 @@ version: '3.9' services: stats-db-init: - image: postgres:14 + image: postgres:15 volumes: - ./stats-db-data:/var/lib/postgresql/data entrypoint: @@ -15,7 +15,7 @@ services: depends_on: stats-db-init: condition: service_completed_successfully - image: postgres:14 + image: postgres:15 user: 2000:2000 shm_size: 256m restart: always From 15e827e69c753ab3bac9a79c416a8fdba86ccea6 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Sat, 20 Jan 2024 20:25:21 +0400 Subject: [PATCH 312/607] fix: review refactor --- .dialyzer-ignore | 2 +- CHANGELOG.md | 1 + .../controllers/api/v2/blob_controller.ex | 7 -- .../block_scout_web/views/api/v2/blob_view.ex | 11 +-- .../views/api/v2/transaction_view.ex | 5 +- apps/explorer/lib/explorer/chain.ex | 1 + .../lib/explorer/chain/beacon/blob.ex | 14 ++-- .../lib/explorer/chain/beacon/reader.ex | 82 ++++++++++++++----- .../import/runner/beacon/blob_transactions.ex | 2 +- apps/explorer/test/support/factory.ex | 4 +- .../lib/indexer/fetcher/beacon/blob.ex | 22 ++--- .../lib/indexer/fetcher/beacon/client.ex | 3 + config/runtime.exs | 12 +-- docker-compose/envs/common-blockscout.env | 7 ++ 14 files changed, 107 insertions(+), 66 deletions(-) diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 15662b23302e..2dd80dd56887 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -23,4 +23,4 @@ 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:170 +lib/explorer/chain/transaction.ex:171 diff --git a/CHANGELOG.md b/CHANGELOG.md index 34d0140233b4..8f2891ccf1a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#9168](https://github.com/blockscout/blockscout/pull/9168) - Support EIP4844 blobs indexing & API - [#9155](https://github.com/blockscout/blockscout/pull/9155) - Allow bypassing avg block time in proxy implementation re-fetch ttl calculation - [#9131](https://github.com/blockscout/blockscout/pull/9131) - Merge addresses stage with address referencing - [#9072](https://github.com/blockscout/blockscout/pull/9072) - Add tracing by block logic for geth diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex index a4817dbf2808..54ec3b96d9e5 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex @@ -1,13 +1,6 @@ defmodule BlockScoutWeb.API.V2.BlobController do use BlockScoutWeb, :controller - import BlockScoutWeb.Chain, - only: [ - next_page_params: 3, - paging_options: 1, - split_list_by_page: 1 - ] - alias Explorer.Chain alias Explorer.Chain.Beacon.Reader diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/blob_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/blob_view.ex index 16595206a6a2..680fa5f04604 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/blob_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/blob_view.ex @@ -1,7 +1,6 @@ defmodule BlockScoutWeb.API.V2.BlobView do use BlockScoutWeb, :view - alias BlockScoutWeb.API.V2.Helper alias Explorer.Chain.Beacon.Blob def render("blob.json", %{blob: blob, transaction_hashes: transaction_hashes}) do @@ -20,13 +19,9 @@ defmodule BlockScoutWeb.API.V2.BlobView do def prepare_blob(blob) do %{ "hash" => blob.hash, - "blob_data" => encode_binary(blob.blob_data), - "kzg_commitment" => encode_binary(blob.kzg_commitment), - "kzg_proof" => encode_binary(blob.kzg_proof) + "blob_data" => blob.blob_data, + "kzg_commitment" => blob.kzg_commitment, + "kzg_proof" => blob.kzg_proof } end - - defp encode_binary(binary) do - "0x" <> Base.encode16(binary, case: :lower) - end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index 562edf13b8e6..4843166cc886 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -510,7 +510,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do |> Map.put( "execution_node", Helper.address_with_info( - single_tx? && conn, + conn, transaction.execution_node, transaction.execution_node_hash, single_tx?, @@ -522,7 +522,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do "nonce" => transaction.wrapped_nonce, "to" => Helper.address_with_info( - single_tx? && conn, + conn, transaction.wrapped_to_address, transaction.wrapped_to_address_hash, single_tx?, @@ -783,6 +783,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do def tx_types(tx, types \\ [], stage \\ :blob_transaction) def tx_types(%Transaction{type: type} = tx, types, :blob_transaction) do + # EIP-2718 blob transaction type types = if type == 3 do [:blob_transaction | types] diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index ea1bde90f67f..dacbcaaffd3e 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -5067,6 +5067,7 @@ defmodule Explorer.Chain do end def filter_blob_transaction_dynamic(dynamic) do + # EIP-2718 blob transaction type dynamic([tx], ^dynamic or tx.type == 3) end diff --git a/apps/explorer/lib/explorer/chain/beacon/blob.ex b/apps/explorer/lib/explorer/chain/beacon/blob.ex index f016d3ca3b2f..b0ab109cf847 100644 --- a/apps/explorer/lib/explorer/chain/beacon/blob.ex +++ b/apps/explorer/lib/explorer/chain/beacon/blob.ex @@ -3,22 +3,22 @@ defmodule Explorer.Chain.Beacon.Blob do use Explorer.Schema - alias Explorer.Chain.Hash + alias Explorer.Chain.{Data, Hash} @required_attrs ~w(hash blob_data kzg_commitment kzg_proof)a @type t :: %__MODULE__{ hash: Hash.t(), - blob_data: binary(), - kzg_commitment: binary(), - kzg_proof: binary() + blob_data: Data.t(), + kzg_commitment: Data.t(), + kzg_proof: Data.t() } @primary_key {:hash, Hash.Full, autogenerate: false} schema "beacon_blobs" do - field(:blob_data, :binary) - field(:kzg_commitment, :binary) - field(:kzg_proof, :binary) + field(:blob_data, Data) + field(:kzg_commitment, Data) + field(:kzg_proof, Data) timestamps(updated_at: false) end diff --git a/apps/explorer/lib/explorer/chain/beacon/reader.ex b/apps/explorer/lib/explorer/chain/beacon/reader.ex index 477239f22fd9..0f06298550ab 100644 --- a/apps/explorer/lib/explorer/chain/beacon/reader.ex +++ b/apps/explorer/lib/explorer/chain/beacon/reader.ex @@ -4,7 +4,7 @@ defmodule Explorer.Chain.Beacon.Reader do import Ecto.Query, only: [ subquery: 1, - preload: 2, + distinct: 3, from: 2, limit: 2, order_by: 3, @@ -16,10 +16,11 @@ defmodule Explorer.Chain.Beacon.Reader do import Explorer.Chain, only: [select_repo: 1] + alias Explorer.{Chain, Repo} + alias Explorer.Chain.{DenormalizationHelper, Hash, Transaction} alias Explorer.Chain.Beacon.{Blob, BlobTransaction} - alias Explorer.{Chain, PagingOptions, Repo} - alias Explorer.Chain.{Hash, Transaction} + @spec blob(Hash.Full.t(), [Chain.api?()]) :: {:error, :not_found} | {:ok, Blob.t()} def blob(hash, options) when is_list(options) do Blob |> where(hash: ^hash) @@ -30,20 +31,48 @@ defmodule Explorer.Chain.Beacon.Reader do end end + @spec blob_hash_to_transactions(Hash.Full.t(), [Chain.api?()]) :: [ + %{ + block_consensus: boolean(), + transaction_hash: Hash.Full.t() + } + ] def blob_hash_to_transactions(hash, options) when is_list(options) do - BlobTransaction - |> where(type(^hash, Hash.Full) == fragment("any(blob_versioned_hashes)")) - |> join(:inner, [bt], transaction in Transaction, on: bt.hash == transaction.hash) - |> order_by([bt, transaction], desc: transaction.block_consensus, desc: transaction.block_number) - |> limit(10) - |> select([bt, transaction], %{ - block_consensus: transaction.block_consensus, - transaction_hash: transaction.hash - }) - |> select_repo(options).all() + query = + BlobTransaction + |> where(type(^hash, Hash.Full) == fragment("any(blob_versioned_hashes)")) + |> join(:inner, [bt], transaction in Transaction, on: bt.hash == transaction.hash) + |> order_by([bt, transaction], desc: transaction.block_consensus, desc: transaction.block_number) + |> limit(10) + + query_with_denormalization = + if DenormalizationHelper.denormalization_finished?() do + query + |> select([bt, transaction], %{ + block_consensus: transaction.block_consensus, + transaction_hash: transaction.hash + }) + else + query + |> join(:inner, [bt, transaction], block in Block, on: block.hash == transaction.block_hash) + |> select([bt, transaction, block], %{ + block_consensus: block.consensus, + transaction_hash: transaction.hash + }) + end + + query_with_denormalization |> select_repo(options).all() end - def stream_missed_blob_transactions_timestamps(min_block, max_block, initial, reducer, options \\ []) + @spec stream_missed_blob_transactions_timestamps( + initial :: accumulator, + reducer :: (entry :: Hash.Address.t(), accumulator -> accumulator), + min_block :: integer() | nil, + max_block :: integer() | nil, + options :: [] + ) :: {:ok, accumulator} + when accumulator: term() + def stream_missed_blob_transactions_timestamps(initial, reducer, min_block, max_block, options \\ []) when is_list(options) do query = from( @@ -58,23 +87,34 @@ defmodule Explorer.Chain.Beacon.Reader do ), inner_join: transaction in Transaction, on: transaction_blob.transaction_hash == transaction.hash, + # EIP-2718 blob transaction type where: transaction.type == 3, left_join: blob in Blob, on: blob.hash == transaction_blob.blob_hash, - where: is_nil(blob.hash), - distinct: transaction.block_timestamp, - select: transaction.block_timestamp + where: is_nil(blob.hash) ) - query + query_with_denormalization = + if DenormalizationHelper.denormalization_finished?() do + query + |> distinct([transaction_blob, transaction, blob], transaction.block_timestamp) + |> select([transaction_blob, transaction, blob], transaction.block_timestamp) + else + query + |> join(:inner, [transaction_blob, transaction, blob], block in Block, on: block.hash == transaction.block_hash) + |> distinct([transaction_blob, transaction, blob, block], block.timestamp) + |> select([transaction_blob, transaction, blob, block], block.timestamp) + end + + query_with_denormalization |> add_min_block_filter(min_block) - |> add_max_block_filter(min_block) + |> add_max_block_filter(max_block) |> Repo.stream_reduce(initial, reducer) end defp add_min_block_filter(query, block_number) do if is_integer(block_number) do - query |> where([_, transaction], transaction.block_number <= ^block_number) + query |> where([_, transaction], transaction.block_number >= ^block_number) else query end @@ -82,7 +122,7 @@ defmodule Explorer.Chain.Beacon.Reader do defp add_max_block_filter(query, block_number) do if is_integer(block_number) and block_number > 0 do - query |> where([_, transaction], transaction.block_number >= ^block_number) + query |> where([_, transaction], transaction.block_number <= ^block_number) else query end diff --git a/apps/explorer/lib/explorer/chain/import/runner/beacon/blob_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/beacon/blob_transactions.ex index 48c4b70bf4c8..e3f61616cd19 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/beacon/blob_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/beacon/blob_transactions.ex @@ -9,7 +9,7 @@ defmodule Explorer.Chain.Import.Runner.Beacon.BlobTransactions do alias Explorer.Chain.Beacon.BlobTransaction alias Ecto.{Multi, Repo} - alias Explorer.Chain.{Block, Hash, Import} + alias Explorer.Chain.{Hash, Import} alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 2a8cf968026c..5fdd286ee056 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -65,8 +65,8 @@ defmodule Explorer.Factory do end def auth_factory do - %{ - info: %{ + %Auth{ + info: %Info{ birthday: nil, description: nil, email: sequence(:email, &"test_user-#{&1}@blockscout.com"), diff --git a/apps/indexer/lib/indexer/fetcher/beacon/blob.ex b/apps/indexer/lib/indexer/fetcher/beacon/blob.ex index b33b4ea6e0b3..ad8e698b068a 100644 --- a/apps/indexer/lib/indexer/fetcher/beacon/blob.ex +++ b/apps/indexer/lib/indexer/fetcher/beacon/blob.ex @@ -61,14 +61,14 @@ defmodule Indexer.Fetcher.Beacon.Blob do def init(initial, reducer, state) do {:ok, final} = Reader.stream_missed_blob_transactions_timestamps( - state.start_block, - state.end_block, initial, fn fields, acc -> fields |> entry() |> reducer.(acc) - end + end, + state.start_block, + state.end_block ) final @@ -91,13 +91,13 @@ defmodule Indexer.Fetcher.Beacon.Blob do |> Enum.map(×tamp_to_slot(&1, state)) |> Client.get_blob_sidecars() |> case do - {:ok, fetched_blobs, retries} -> + {:ok, fetched_blobs, retry_indices} -> run_fetched_blobs(fetched_blobs) - if Enum.empty?(retries) do + if Enum.empty?(retry_indices) do :ok else - {:retry, retries |> Enum.map(&Enum.at(entries, &1))} + {:retry, retry_indices |> Enum.map(&Enum.at(entries, &1))} end end end @@ -123,7 +123,7 @@ defmodule Indexer.Fetcher.Beacon.Blob do Repo.insert_all(Blob, blobs, on_conflict: :nothing, conflict_target: [:hash]) end - def blob_entry(%{ + defp blob_entry(%{ "blob" => blob, "kzg_commitment" => kzg_commitment, "kzg_proof" => kzg_proof @@ -134,13 +134,13 @@ defmodule Indexer.Fetcher.Beacon.Blob do %{ hash: blob_hash(kzg_commitment.bytes), - blob_data: blob.bytes, - kzg_commitment: kzg_commitment.bytes, - kzg_proof: kzg_proof.bytes + blob_data: blob, + kzg_commitment: kzg_commitment, + kzg_proof: kzg_proof } end - def blob_hash(kzg_commitment) do + defp blob_hash(kzg_commitment) do raw_hash = :crypto.hash(:sha256, kzg_commitment) <<_::size(8), rest::binary>> = raw_hash {:ok, hash} = Hash.Full.cast(<<1>> <> rest) diff --git a/apps/indexer/lib/indexer/fetcher/beacon/client.ex b/apps/indexer/lib/indexer/fetcher/beacon/client.ex index b443ee6d7a47..c5554e367e3b 100644 --- a/apps/indexer/lib/indexer/fetcher/beacon/client.ex +++ b/apps/indexer/lib/indexer/fetcher/beacon/client.ex @@ -31,6 +31,7 @@ defmodule Indexer.Fetcher.Beacon.Client do end end + @spec get_blob_sidecars([integer()]) :: {:ok, list(), [integer()]} def get_blob_sidecars(slots) when is_list(slots) do {oks, errors_with_retries} = slots @@ -53,6 +54,7 @@ defmodule Indexer.Fetcher.Beacon.Client do {:ok, oks |> Enum.map(fn {_, blob} -> blob end), retries} end + @spec get_blob_sidecars(integer()) :: {:error, any()} | {:ok, any()} def get_blob_sidecars(slot) do http_get_request(blob_sidecars_url(slot)) end @@ -63,6 +65,7 @@ defmodule Indexer.Fetcher.Beacon.Client do defp successful?({:ok, _}), do: true defp successful?(_), do: false + @spec get_header(integer()) :: {:error, any()} | {:ok, any()} def get_header(slot) do http_get_request(header_url(slot)) end diff --git a/config/runtime.exs b/config/runtime.exs index 7ef79e36d9ff..9292a8dbd8a1 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -684,15 +684,15 @@ config :indexer, Indexer.Fetcher.Beacon, beacon_rpc: System.get_env("INDEXER_BEA config :indexer, Indexer.Fetcher.Beacon.Blob.Supervisor, disabled?: ConfigHelper.chain_type() != "ethereum" || - ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_BEACON_BLOB_SANITIZE_FETCHER") + ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_BEACON_BLOB_FETCHER") config :indexer, Indexer.Fetcher.Beacon.Blob, - slot_duration: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_SANITIZE_FETCHER_SLOT_DURATION", 12), - reference_slot: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_SANITIZE_FETCHER_REFERENCE_SLOT", 8_206_822), + slot_duration: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_FETCHER_SLOT_DURATION", 12), + reference_slot: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_FETCHER_REFERENCE_SLOT", 8_206_822), reference_timestamp: - ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_SANITIZE_FETCHER_REFERENCE_TIMESTAMP", 1_705_305_887), - start_block: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_SANITIZE_FETCHER_START_BLOCK", 8_206_822), - end_block: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_SANITIZE_FETCHER_END_BLOCK", 0) + ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_FETCHER_REFERENCE_TIMESTAMP", 1_705_305_887), + start_block: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_FETCHER_START_BLOCK", 8_206_822), + end_block: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_FETCHER_END_BLOCK", 0) Code.require_file("#{config_env()}.exs", "config/runtime") diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index b12e37cac135..d734fdb428f5 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -181,6 +181,13 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false # INDEXER_ROOTSTOCK_DATA_FETCHER_BATCH_SIZE= # INDEXER_ROOTSTOCK_DATA_FETCHER_CONCURRENCY= # INDEXER_ROOTSTOCK_DATA_FETCHER_DB_BATCH_SIZE= +# INDEXER_BEACON_RPC_URL= +# INDEXER_DISABLE_BEACON_BLOB_FETCHER= +# INDEXER_BEACON_BLOB_FETCHER_SLOT_DURATION=12 +# INDEXER_BEACON_BLOB_FETCHER_REFERENCE_SLOT=8206822 +# INDEXER_BEACON_BLOB_FETCHER_REFERENCE_TIMESTAMP=1705305887 +# INDEXER_BEACON_BLOB_FETCHER_START_BLOCK=8206822 +# INDEXER_BEACON_BLOB_FETCHER_END_BLOCK=0 # TOKEN_ID_MIGRATION_FIRST_BLOCK= # TOKEN_ID_MIGRATION_CONCURRENCY= # TOKEN_ID_MIGRATION_BATCH_SIZE= From 93661de2631496e656c45f63bc96f5f55e44d709 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Sat, 20 Jan 2024 22:24:39 +0400 Subject: [PATCH 313/607] fix: fmt --- apps/block_scout_web/mix.exs | 2 +- apps/indexer/lib/indexer/fetcher/beacon/blob.ex | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index 741558064926..80b5491d0002 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -24,7 +24,7 @@ defmodule BlockScoutWeb.Mixfile do ], start_permanent: Mix.env() == :prod, version: "6.0.0", - xref: [exclude: [Explorer.Chain.Zkevm.Reader]] + xref: [exclude: [Explorer.Chain.Zkevm.Reader, Explorer.Chain.Beacon.Reader]] ] end diff --git a/apps/indexer/lib/indexer/fetcher/beacon/blob.ex b/apps/indexer/lib/indexer/fetcher/beacon/blob.ex index ad8e698b068a..15061046b931 100644 --- a/apps/indexer/lib/indexer/fetcher/beacon/blob.ex +++ b/apps/indexer/lib/indexer/fetcher/beacon/blob.ex @@ -124,10 +124,10 @@ defmodule Indexer.Fetcher.Beacon.Blob do end defp blob_entry(%{ - "blob" => blob, - "kzg_commitment" => kzg_commitment, - "kzg_proof" => kzg_proof - }) do + "blob" => blob, + "kzg_commitment" => kzg_commitment, + "kzg_proof" => kzg_proof + }) do {:ok, kzg_commitment} = Data.cast(kzg_commitment) {:ok, blob} = Data.cast(blob) {:ok, kzg_proof} = Data.cast(kzg_proof) From c53757ac94528630b62a2aba44ecd8639c82d075 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 18:25:57 +0000 Subject: [PATCH 314/607] Bump core-js from 3.35.0 to 3.35.1 in /apps/block_scout_web/assets Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.35.0 to 3.35.1. - [Release notes](https://github.com/zloirock/core-js/releases) - [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/zloirock/core-js/commits/v3.35.1/packages/core-js) --- updated-dependencies: - dependency-name: core-js dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 49fded9ff7b5..3dd8f3337474 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -17,7 +17,7 @@ "chart.js": "^4.4.1", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", - "core-js": "^3.35.0", + "core-js": "^3.35.1", "crypto-browserify": "^3.12.0", "dropzone": "^5.9.3", "eth-net-props": "^1.0.41", @@ -5949,9 +5949,9 @@ } }, "node_modules/core-js": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.0.tgz", - "integrity": "sha512-ntakECeqg81KqMueeGJ79Q5ZgQNR+6eaE8sxGCx62zMbAIj65q+uYvatToew3m6eAGdU4gNZwpZ34NMe4GYswg==", + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.1.tgz", + "integrity": "sha512-IgdsbxNyMskrTFxa9lWHyMwAJU5gXOPP+1yO+K59d50VLVAIDAbs7gIv705KzALModfK3ZrSZTPNpC0PQgIZuw==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -22251,9 +22251,9 @@ } }, "core-js": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.0.tgz", - "integrity": "sha512-ntakECeqg81KqMueeGJ79Q5ZgQNR+6eaE8sxGCx62zMbAIj65q+uYvatToew3m6eAGdU4gNZwpZ34NMe4GYswg==" + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.1.tgz", + "integrity": "sha512-IgdsbxNyMskrTFxa9lWHyMwAJU5gXOPP+1yO+K59d50VLVAIDAbs7gIv705KzALModfK3ZrSZTPNpC0PQgIZuw==" }, "core-js-compat": { "version": "3.35.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 86e38b7df8f5..b66aea75a181 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -29,7 +29,7 @@ "chart.js": "^4.4.1", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", - "core-js": "^3.35.0", + "core-js": "^3.35.1", "crypto-browserify": "^3.12.0", "dropzone": "^5.9.3", "eth-net-props": "^1.0.41", From e934788e8023001361bdd976f0c4cd40328f0083 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 18:26:43 +0000 Subject: [PATCH 315/607] Bump sass from 1.69.7 to 1.70.0 in /apps/block_scout_web/assets Bumps [sass](https://github.com/sass/dart-sass) from 1.69.7 to 1.70.0. - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.69.7...1.70.0) --- updated-dependencies: - dependency-name: sass dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 49fded9ff7b5..d1e63ca7978c 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -89,7 +89,7 @@ "mini-css-extract-plugin": "^2.7.7", "postcss": "^8.4.33", "postcss-loader": "^7.3.4", - "sass": "^1.69.7", + "sass": "^1.70.0", "sass-loader": "^14.0.0", "style-loader": "^3.3.4", "webpack": "^5.89.0", @@ -15205,9 +15205,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.69.7", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz", - "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==", + "version": "1.70.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.70.0.tgz", + "integrity": "sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -29266,9 +29266,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass": { - "version": "1.69.7", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz", - "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==", + "version": "1.70.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.70.0.tgz", + "integrity": "sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 86e38b7df8f5..dc6e1ac7b721 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -101,7 +101,7 @@ "mini-css-extract-plugin": "^2.7.7", "postcss": "^8.4.33", "postcss-loader": "^7.3.4", - "sass": "^1.69.7", + "sass": "^1.70.0", "sass-loader": "^14.0.0", "style-loader": "^3.3.4", "webpack": "^5.89.0", From 92c29c545d38028d3c038bf8e13474c6a53a83c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 18:26:59 +0000 Subject: [PATCH 316/607] Bump copy-webpack-plugin in /apps/block_scout_web/assets Bumps [copy-webpack-plugin](https://github.com/webpack-contrib/copy-webpack-plugin) from 12.0.1 to 12.0.2. - [Release notes](https://github.com/webpack-contrib/copy-webpack-plugin/releases) - [Changelog](https://github.com/webpack-contrib/copy-webpack-plugin/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v12.0.1...v12.0.2) --- updated-dependencies: - dependency-name: copy-webpack-plugin dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 49fded9ff7b5..321d3acf806a 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -75,7 +75,7 @@ "@babel/preset-env": "^7.23.8", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", - "copy-webpack-plugin": "^12.0.1", + "copy-webpack-plugin": "^12.0.2", "css-loader": "^6.9.0", "css-minimizer-webpack-plugin": "^5.0.1", "eslint": "^8.56.0", @@ -5925,9 +5925,9 @@ } }, "node_modules/copy-webpack-plugin": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.1.tgz", - "integrity": "sha512-dhMfjJMYKDmmbG6Yn2pRSs1g8FgeQRtbE/JM6VAM9Xouk3KO1UVrwlLHLXxaI5F+o9WgnRfhFZzY9eV34O2gZQ==", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", "dev": true, "dependencies": { "fast-glob": "^3.3.2", @@ -22237,9 +22237,9 @@ } }, "copy-webpack-plugin": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.1.tgz", - "integrity": "sha512-dhMfjJMYKDmmbG6Yn2pRSs1g8FgeQRtbE/JM6VAM9Xouk3KO1UVrwlLHLXxaI5F+o9WgnRfhFZzY9eV34O2gZQ==", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", "dev": true, "requires": { "fast-glob": "^3.3.2", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 86e38b7df8f5..1bfc0e562ef3 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -87,7 +87,7 @@ "@babel/preset-env": "^7.23.8", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", - "copy-webpack-plugin": "^12.0.1", + "copy-webpack-plugin": "^12.0.2", "css-loader": "^6.9.0", "css-minimizer-webpack-plugin": "^5.0.1", "eslint": "^8.56.0", From a185aacbc6bb80e8221e28358b69284715025e84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 18:34:33 +0000 Subject: [PATCH 317/607] Bump dialyxir from 1.4.2 to 1.4.3 Bumps [dialyxir](https://github.com/jeremyjh/dialyxir) from 1.4.2 to 1.4.3. - [Release notes](https://github.com/jeremyjh/dialyxir/releases) - [Changelog](https://github.com/jeremyjh/dialyxir/blob/master/CHANGELOG.md) - [Commits](https://github.com/jeremyjh/dialyxir/compare/1.4.2...1.4.3) --- updated-dependencies: - dependency-name: dialyxir dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index c76820d08ec5..15407bd198c4 100644 --- a/mix.lock +++ b/mix.lock @@ -34,7 +34,7 @@ "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "decorator": {:hex, :decorator, "1.4.0", "a57ac32c823ea7e4e67f5af56412d12b33274661bb7640ec7fc882f8d23ac419", [:mix], [], "hexpm", "0a07cedd9083da875c7418dea95b78361197cf2bf3211d743f6f7ce39656597f"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, - "dialyxir": {:hex, :dialyxir, "1.4.2", "764a6e8e7a354f0ba95d58418178d486065ead1f69ad89782817c296d0d746a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "516603d8067b2fd585319e4b13d3674ad4f314a5902ba8130cd97dc902ce6bbd"}, + "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "digital_token": {:hex, :digital_token, "0.6.0", "13e6de581f0b1f6c686f7c7d12ab11a84a7b22fa79adeb4b50eec1a2d278d258", [:mix], [{:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "2455d626e7c61a128b02a4a8caddb092548c3eb613ac6f6a85e4cbb6caddc4d1"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, From 45148945458d74dc9f6fb84b54e7c29501a63017 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 18:40:32 +0000 Subject: [PATCH 318/607] Bump ex_cldr_numbers from 2.32.3 to 2.32.4 Bumps [ex_cldr_numbers](https://github.com/elixir-cldr/cldr_numbers) from 2.32.3 to 2.32.4. - [Release notes](https://github.com/elixir-cldr/cldr_numbers/releases) - [Changelog](https://github.com/elixir-cldr/cldr_numbers/blob/main/CHANGELOG.md) - [Commits](https://github.com/elixir-cldr/cldr_numbers/compare/v2.32.3...v2.32.4) --- updated-dependencies: - dependency-name: ex_cldr_numbers dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index c76820d08ec5..735be96c310e 100644 --- a/mix.lock +++ b/mix.lock @@ -12,7 +12,7 @@ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "bureaucrat": {:hex, :bureaucrat, "0.2.9", "d98e4d2b9bdbf22e4a45c2113ce8b38b5b63278506c6ff918e3b943a4355d85b", [:mix], [{:inflex, ">= 1.10.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.2.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, ">= 1.0.0", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "111c8dd84382a62e1026ae011d592ceee918553e5203fe8448d9ba6ccbdfff7d"}, "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, - "castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"}, + "castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"}, "cbor": {:hex, :cbor, "1.0.1", "39511158e8ea5a57c1fcb9639aaa7efde67129678fee49ebbda780f6f24959b0", [:mix], [], "hexpm", "5431acbe7a7908f17f6a9cd43311002836a34a8ab01876918d8cfb709cd8b6a2"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "cldr_utils": {:hex, :cldr_utils, "2.24.2", "364fa30be55d328e704629568d431eb74cd2f085752b27f8025520b566352859", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "3362b838836a9f0fa309de09a7127e36e67310e797d556db92f71b548832c7cf"}, @@ -45,7 +45,7 @@ "ex_cldr": {:hex, :ex_cldr, "2.37.5", "9da6d97334035b961d2c2de167dc6af8cd3e09859301a5b8f49f90bd8b034593", [:mix], [{:cldr_utils, "~> 2.21", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "74ad5ddff791112ce4156382e171a5f5d3766af9d5c4675e0571f081fe136479"}, "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.15.1", "e92ba17c41e7405b7784e0e65f406b5f17cfe313e0e70de9befd653e12854822", [:mix], [{:ex_cldr, "~> 2.34", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "31df8bd37688340f8819bdd770eb17d659652078d34db632b85d4a32864d6a25"}, "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.10.2", "c8dbb3324ca35cea3679a96f4c774cdf4bdd425786a44c4f52aacb57a0cee446", [:mix], [{:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "0cc2124eccffa5438045c2504dd3365490b64065131f58ecc27f344db1edb4b8"}, - "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.32.3", "b631ff94c982ec518e46bf4736000a30a33d6b58facc085d5f240305f512ad4a", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7b626ff1e59a0ec9c3c5db5ce9ca91a6995e2ab56426b71f3cbf67181ea225f5"}, + "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.32.4", "5562148dfc631b04712983975093d2aac29df30b3bf2f7257e0c94b85b72e91b", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "6fd5a82f0785418fa8b698c0be2b1845dff92b77f1b3172c763d37868fb503d2"}, "ex_cldr_units": {:hex, :ex_cldr_units, "3.16.4", "fee054e9ebed40ef05cbb405cb0c7e7c9fda201f8f03ec0d1e54e879af413246", [:mix], [{:cldr_utils, "~> 2.24", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.31", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7c15c6357dd555a5bc6c72fdeb243e4706a04065753dbd2f40150f062ca996c7"}, "ex_doc": {:hex, :ex_doc, "0.31.0", "06eb1dfd787445d9cab9a45088405593dd3bb7fe99e097eaa71f37ba80c7a676", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5350cafa6b7f77bdd107aa2199fe277acf29d739aba5aee7e865fc680c62a110"}, "ex_json_schema": {:hex, :ex_json_schema, "0.10.2", "7c4b8c1481fdeb1741e2ce66223976edfb9bccebc8014f6aec35d4efe964fb71", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "37f43be60f8407659d4d0155a7e45e7f406dab1f827051d3d35858a709baf6a6"}, From 297b15d679a8eaaf10884270cf120268b7967b5d Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Tue, 23 Jan 2024 10:39:16 +0400 Subject: [PATCH 319/607] Add missing filter to txlist query --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/etherscan.ex | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e461500219f6..b55a562d3104 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Fixes +- [#9229](https://github.com/blockscout/blockscout/pull/9229) - Add missing filter to txlist query - [#9139](https://github.com/blockscout/blockscout/pull/9139) - TokenBalanceOnDemand fixes - [#9125](https://github.com/blockscout/blockscout/pull/9125) - Fix Explorer.Chain.Cache.GasPriceOracle.merge_fees - [#9124](https://github.com/blockscout/blockscout/pull/9124) - EIP-1167 display multiple sources of implementation diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index 8c15dd3f828d..070e8e2cd276 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -473,6 +473,7 @@ defmodule Explorer.Etherscan do if DenormalizationHelper.denormalization_finished?() do from( t in Transaction, + where: not is_nil(t.block_hash), order_by: [{^options.order_by_direction, t.block_number}], limit: ^options.page_size, offset: ^offset(options), From d7a915ec8fffdc362ca6df3750b10fe42a0a1087 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jan 2024 08:46:54 +0000 Subject: [PATCH 320/607] Bump postcss-loader from 7.3.4 to 8.0.0 in /apps/block_scout_web/assets Bumps [postcss-loader](https://github.com/webpack-contrib/postcss-loader) from 7.3.4 to 8.0.0. - [Release notes](https://github.com/webpack-contrib/postcss-loader/releases) - [Changelog](https://github.com/webpack-contrib/postcss-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/postcss-loader/compare/v7.3.4...v8.0.0) --- updated-dependencies: - dependency-name: postcss-loader dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 70 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index d1e63ca7978c..a6e8b8dadc1f 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -88,7 +88,7 @@ "jest-environment-jsdom": "^29.7.0", "mini-css-extract-plugin": "^2.7.7", "postcss": "^8.4.33", - "postcss-loader": "^7.3.4", + "postcss-loader": "^8.0.0", "sass": "^1.70.0", "sass-loader": "^14.0.0", "style-loader": "^3.3.4", @@ -5988,15 +5988,15 @@ } }, "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "dependencies": { + "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" + "parse-json": "^5.2.0" }, "engines": { "node": ">=14" @@ -7041,6 +7041,15 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/envinfo": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", @@ -13547,15 +13556,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -13881,17 +13881,17 @@ } }, "node_modules/postcss-loader": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", - "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.0.0.tgz", + "integrity": "sha512-+RiNlmYd1aXYv6QSBOAu6n9eJYy0ydyXTfjljAJ3vFU6MMo2M552zTVcBpBH+R5aAeKaYVG1K9UEyAVsLL1Qjg==", "dev": true, "dependencies": { - "cosmiconfig": "^8.3.5", + "cosmiconfig": "^9.0.0", "jiti": "^1.20.0", "semver": "^7.5.4" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", @@ -22278,15 +22278,15 @@ } }, "cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "requires": { + "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" + "parse-json": "^5.2.0" }, "dependencies": { "argparse": { @@ -23060,6 +23060,12 @@ "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", "dev": true }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, "envinfo": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", @@ -28103,12 +28109,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, "pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -28311,12 +28311,12 @@ "requires": {} }, "postcss-loader": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", - "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.0.0.tgz", + "integrity": "sha512-+RiNlmYd1aXYv6QSBOAu6n9eJYy0ydyXTfjljAJ3vFU6MMo2M552zTVcBpBH+R5aAeKaYVG1K9UEyAVsLL1Qjg==", "dev": true, "requires": { - "cosmiconfig": "^8.3.5", + "cosmiconfig": "^9.0.0", "jiti": "^1.20.0", "semver": "^7.5.4" }, diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index dc6e1ac7b721..fd90ff942a48 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -100,7 +100,7 @@ "jest-environment-jsdom": "^29.7.0", "mini-css-extract-plugin": "^2.7.7", "postcss": "^8.4.33", - "postcss-loader": "^7.3.4", + "postcss-loader": "^8.0.0", "sass": "^1.70.0", "sass-loader": "^14.0.0", "style-loader": "^3.3.4", From 750f3116f351825b87644673a2050f272502498d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jan 2024 09:15:56 +0000 Subject: [PATCH 321/607] Bump plug_cowboy from 2.6.1 to 2.6.2 Bumps [plug_cowboy](https://github.com/elixir-plug/plug_cowboy) from 2.6.1 to 2.6.2. - [Changelog](https://github.com/elixir-plug/plug_cowboy/blob/master/CHANGELOG.md) - [Commits](https://github.com/elixir-plug/plug_cowboy/compare/v2.6.1...v2.6.2) --- updated-dependencies: - dependency-name: plug_cowboy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 5cb7ec92a4cd..79a27ea3fb66 100644 --- a/mix.lock +++ b/mix.lock @@ -104,8 +104,8 @@ "phoenix_html": {:hex, :phoenix_html, "3.0.4", "232d41884fe6a9c42d09f48397c175cd6f0d443aaa34c7424da47604201df2e1", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "ce17fd3cf815b2ed874114073e743507704b1f5288bb03c304a77458485efc8b"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, - "plug": {:hex, :plug, "1.15.2", "94cf1fa375526f30ff8770837cb804798e0045fd97185f0bb9e5fcd858c792a3", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, + "plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.6.2", "753611b23b29231fb916b0cdd96028084b12aff57bfd7b71781bd04b1dbeb5c9", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "951ed2433df22f4c97b85fdb145d4cee561f36b74854d64c06d896d7cd2921a7"}, "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, From eb8c97f3469e5d62aaef8c2ba95493444ee5dff6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jan 2024 10:37:10 +0000 Subject: [PATCH 322/607] Bump css-loader from 6.9.0 to 6.9.1 in /apps/block_scout_web/assets Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 6.9.0 to 6.9.1. - [Release notes](https://github.com/webpack-contrib/css-loader/releases) - [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/css-loader/compare/v6.9.0...v6.9.1) --- updated-dependencies: - dependency-name: css-loader dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 50 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index b594813c657e..3129abdda64d 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -76,7 +76,7 @@ "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", - "css-loader": "^6.9.0", + "css-loader": "^6.9.1", "css-minimizer-webpack-plugin": "^5.0.1", "eslint": "^8.56.0", "eslint-config-standard": "^17.1.0", @@ -6237,16 +6237,16 @@ } }, "node_modules/css-loader": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.9.0.tgz", - "integrity": "sha512-3I5Nu4ytWlHvOP6zItjiHlefBNtrH+oehq8tnQa2kO305qpVyx9XNIT1CXIj5bgCJs7qICBCkgCYxQLKPANoLA==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.9.1.tgz", + "integrity": "sha512-OzABOh0+26JKFdMzlK6PY1u5Zx8+Ck7CVRlcGNZoY9qwJjdfu2VWFuprTIpPW+Av5TZTVViYWcFQaEEQURLknQ==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.31", + "postcss": "^8.4.33", "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.3", - "postcss-modules-scope": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.4", + "postcss-modules-scope": "^3.1.1", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", "semver": "^7.5.4" @@ -14028,9 +14028,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", - "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz", + "integrity": "sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==", "dev": true, "dependencies": { "icss-utils": "^5.0.0", @@ -14045,9 +14045,9 @@ } }, "node_modules/postcss-modules-scope": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.0.tgz", - "integrity": "sha512-SaIbK8XW+MZbd0xHPf7kdfA/3eOt7vxJ72IRecn3EzuZVLr1r0orzf0MX/pN8m+NMDoo6X/SQd8oeKqGZd8PXg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz", + "integrity": "sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.4" @@ -22469,16 +22469,16 @@ "requires": {} }, "css-loader": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.9.0.tgz", - "integrity": "sha512-3I5Nu4ytWlHvOP6zItjiHlefBNtrH+oehq8tnQa2kO305qpVyx9XNIT1CXIj5bgCJs7qICBCkgCYxQLKPANoLA==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.9.1.tgz", + "integrity": "sha512-OzABOh0+26JKFdMzlK6PY1u5Zx8+Ck7CVRlcGNZoY9qwJjdfu2VWFuprTIpPW+Av5TZTVViYWcFQaEEQURLknQ==", "dev": true, "requires": { "icss-utils": "^5.1.0", - "postcss": "^8.4.31", + "postcss": "^8.4.33", "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.3", - "postcss-modules-scope": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.4", + "postcss-modules-scope": "^3.1.1", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", "semver": "^7.5.4" @@ -28402,9 +28402,9 @@ "requires": {} }, "postcss-modules-local-by-default": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", - "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz", + "integrity": "sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==", "dev": true, "requires": { "icss-utils": "^5.0.0", @@ -28413,9 +28413,9 @@ } }, "postcss-modules-scope": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.0.tgz", - "integrity": "sha512-SaIbK8XW+MZbd0xHPf7kdfA/3eOt7vxJ72IRecn3EzuZVLr1r0orzf0MX/pN8m+NMDoo6X/SQd8oeKqGZd8PXg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz", + "integrity": "sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==", "dev": true, "requires": { "postcss-selector-parser": "^6.0.4" diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 3fa1d8333646..6d7cb3260666 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -88,7 +88,7 @@ "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", - "css-loader": "^6.9.0", + "css-loader": "^6.9.1", "css-minimizer-webpack-plugin": "^5.0.1", "eslint": "^8.56.0", "eslint-config-standard": "^17.1.0", From a87469af19e04a1d706620e7196ab05cf7a74a15 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jan 2024 12:23:56 +0000 Subject: [PATCH 323/607] Bump css-minimizer-webpack-plugin in /apps/block_scout_web/assets Bumps [css-minimizer-webpack-plugin](https://github.com/webpack-contrib/css-minimizer-webpack-plugin) from 5.0.1 to 6.0.0. - [Release notes](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/releases) - [Changelog](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v5.0.1...v6.0.0) --- updated-dependencies: - dependency-name: css-minimizer-webpack-plugin dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 754 +++++++++--------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 379 insertions(+), 377 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 3129abdda64d..938d133be82b 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -77,7 +77,7 @@ "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", "css-loader": "^6.9.1", - "css-minimizer-webpack-plugin": "^5.0.1", + "css-minimizer-webpack-plugin": "^6.0.0", "eslint": "^8.56.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.1", @@ -3342,12 +3342,12 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@kurkle/color": { @@ -6225,12 +6225,12 @@ } }, "node_modules/css-declaration-sorter": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.0.tgz", - "integrity": "sha512-jDfsatwWMWN0MODAFuHszfjphEXfNw9JUAhmY4pLu3TyTU+ohUpsbVtbU+1MZn4a47D9kqh03i4eyOm+74+zew==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.1.1.tgz", + "integrity": "sha512-dZ3bVTEEc1vxr3Bek9vGwfB5Z6ESPULhcRvO472mfjVnj8jRcTnKO8/JTczlvxM10Myb+wBM++1MtdO76eWcaQ==", "dev": true, "engines": { - "node": "^10 || ^12 || >=14" + "node": "^14 || ^16 || >=18" }, "peerDependencies": { "postcss": "^8.0.9" @@ -6278,20 +6278,20 @@ } }, "node_modules/css-minimizer-webpack-plugin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", - "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-6.0.0.tgz", + "integrity": "sha512-BLpR9CCDkKvhO3i0oZQgad6v9pCxUuhSc5RT6iUEy9M8hBXi4TJb5vqF2GQ2deqYHmRi3O6IR9hgAZQWg0EBwA==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "cssnano": "^6.0.1", - "jest-worker": "^29.4.3", - "postcss": "^8.4.24", - "schema-utils": "^4.0.1", - "serialize-javascript": "^6.0.1" + "@jridgewell/trace-mapping": "^0.3.21", + "cssnano": "^6.0.3", + "jest-worker": "^29.7.0", + "postcss": "^8.4.33", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", @@ -6331,13 +6331,13 @@ } }, "node_modules/css-minimizer-webpack-plugin/node_modules/jest-worker": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", - "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "dependencies": { "@types/node": "*", - "jest-util": "^29.5.0", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -6429,13 +6429,13 @@ "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=" }, "node_modules/cssnano": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.1.tgz", - "integrity": "sha512-fVO1JdJ0LSdIGJq68eIxOqFpIJrZqXUsBt8fkrBcztCQqAjQD51OhZp7tc0ImcbwXD4k7ny84QTV90nZhmqbkg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.3.tgz", + "integrity": "sha512-MRq4CIj8pnyZpcI2qs6wswoYoDD1t0aL28n+41c1Ukcpm56m1h6mCexIHBGjfZfnTqtGSSCP4/fB1ovxgjBOiw==", "dev": true, "dependencies": { - "cssnano-preset-default": "^6.0.1", - "lilconfig": "^2.1.0" + "cssnano-preset-default": "^6.0.3", + "lilconfig": "^3.0.0" }, "engines": { "node": "^14 || ^16 || >=18.0" @@ -6445,62 +6445,62 @@ "url": "https://opencollective.com/cssnano" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/cssnano-preset-default": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.1.tgz", - "integrity": "sha512-7VzyFZ5zEB1+l1nToKyrRkuaJIx0zi/1npjvZfbBwbtNTzhLtlvYraK/7/uqmX2Wb2aQtd983uuGw79jAjLSuQ==", - "dev": true, - "dependencies": { - "css-declaration-sorter": "^6.3.1", - "cssnano-utils": "^4.0.0", - "postcss-calc": "^9.0.0", - "postcss-colormin": "^6.0.0", - "postcss-convert-values": "^6.0.0", - "postcss-discard-comments": "^6.0.0", - "postcss-discard-duplicates": "^6.0.0", - "postcss-discard-empty": "^6.0.0", - "postcss-discard-overridden": "^6.0.0", - "postcss-merge-longhand": "^6.0.0", - "postcss-merge-rules": "^6.0.1", - "postcss-minify-font-values": "^6.0.0", - "postcss-minify-gradients": "^6.0.0", - "postcss-minify-params": "^6.0.0", - "postcss-minify-selectors": "^6.0.0", - "postcss-normalize-charset": "^6.0.0", - "postcss-normalize-display-values": "^6.0.0", - "postcss-normalize-positions": "^6.0.0", - "postcss-normalize-repeat-style": "^6.0.0", - "postcss-normalize-string": "^6.0.0", - "postcss-normalize-timing-functions": "^6.0.0", - "postcss-normalize-unicode": "^6.0.0", - "postcss-normalize-url": "^6.0.0", - "postcss-normalize-whitespace": "^6.0.0", - "postcss-ordered-values": "^6.0.0", - "postcss-reduce-initial": "^6.0.0", - "postcss-reduce-transforms": "^6.0.0", - "postcss-svgo": "^6.0.0", - "postcss-unique-selectors": "^6.0.0" + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.3.tgz", + "integrity": "sha512-4y3H370aZCkT9Ev8P4SO4bZbt+AExeKhh8wTbms/X7OLDo5E7AYUUy6YPxa/uF5Grf+AJwNcCnxKhZynJ6luBA==", + "dev": true, + "dependencies": { + "css-declaration-sorter": "^7.1.1", + "cssnano-utils": "^4.0.1", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.0.2", + "postcss-convert-values": "^6.0.2", + "postcss-discard-comments": "^6.0.1", + "postcss-discard-duplicates": "^6.0.1", + "postcss-discard-empty": "^6.0.1", + "postcss-discard-overridden": "^6.0.1", + "postcss-merge-longhand": "^6.0.2", + "postcss-merge-rules": "^6.0.3", + "postcss-minify-font-values": "^6.0.1", + "postcss-minify-gradients": "^6.0.1", + "postcss-minify-params": "^6.0.2", + "postcss-minify-selectors": "^6.0.2", + "postcss-normalize-charset": "^6.0.1", + "postcss-normalize-display-values": "^6.0.1", + "postcss-normalize-positions": "^6.0.1", + "postcss-normalize-repeat-style": "^6.0.1", + "postcss-normalize-string": "^6.0.1", + "postcss-normalize-timing-functions": "^6.0.1", + "postcss-normalize-unicode": "^6.0.2", + "postcss-normalize-url": "^6.0.1", + "postcss-normalize-whitespace": "^6.0.1", + "postcss-ordered-values": "^6.0.1", + "postcss-reduce-initial": "^6.0.2", + "postcss-reduce-transforms": "^6.0.1", + "postcss-svgo": "^6.0.2", + "postcss-unique-selectors": "^6.0.2" }, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/cssnano-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.0.tgz", - "integrity": "sha512-Z39TLP+1E0KUcd7LGyF4qMfu8ZufI0rDzhdyAMsa/8UyNUU8wpS0fhdBxbQbv32r64ea00h4878gommRVg2BHw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.1.tgz", + "integrity": "sha512-6qQuYDqsGoiXssZ3zct6dcMxiqfT6epy7x4R0TQJadd4LWO3sPR6JH6ZByOvVLoZ6EdwPGgd7+DR1EmX3tiXQQ==", "dev": true, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/csso": { @@ -12372,12 +12372,12 @@ } }, "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", + "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", "dev": true, "engines": { - "node": ">=10" + "node": ">=14" } }, "node_modules/lines-and-columns": { @@ -13799,12 +13799,12 @@ } }, "node_modules/postcss-colormin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.0.0.tgz", - "integrity": "sha512-EuO+bAUmutWoZYgHn2T1dG1pPqHU6L4TjzPlu4t1wZGXQ/fxV16xg2EJmYi0z+6r+MGV1yvpx1BHkUaRrPa2bw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.0.2.tgz", + "integrity": "sha512-TXKOxs9LWcdYo5cgmcSHPkyrLAh86hX1ijmyy6J8SbOhyv6ua053M3ZAM/0j44UsnQNIWdl8gb5L7xX2htKeLw==", "dev": true, "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.22.2", "caniuse-api": "^3.0.0", "colord": "^2.9.1", "postcss-value-parser": "^4.2.0" @@ -13813,71 +13813,71 @@ "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-convert-values": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.0.0.tgz", - "integrity": "sha512-U5D8QhVwqT++ecmy8rnTb+RL9n/B806UVaS3m60lqle4YDFcpbS3ae5bTQIh3wOGUSDHSEtMYLs/38dNG7EYFw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.0.2.tgz", + "integrity": "sha512-aeBmaTnGQ+NUSVQT8aY0sKyAD/BaLJenEKZ03YK0JnDE1w1Rr8XShoxdal2V2H26xTJKr3v5haByOhJuyT4UYw==", "dev": true, "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.22.2", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-discard-comments": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.0.tgz", - "integrity": "sha512-p2skSGqzPMZkEQvJsgnkBhCn8gI7NzRH2683EEjrIkoMiwRELx68yoUJ3q3DGSGuQ8Ug9Gsn+OuDr46yfO+eFw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.1.tgz", + "integrity": "sha512-f1KYNPtqYLUeZGCHQPKzzFtsHaRuECe6jLakf/RjSRqvF5XHLZnM2+fXLhb8Qh/HBFHs3M4cSLb1k3B899RYIg==", "dev": true, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-discard-duplicates": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.0.tgz", - "integrity": "sha512-bU1SXIizMLtDW4oSsi5C/xHKbhLlhek/0/yCnoMQany9k3nPBq+Ctsv/9oMmyqbR96HYHxZcHyK2HR5P/mqoGA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.1.tgz", + "integrity": "sha512-1hvUs76HLYR8zkScbwyJ8oJEugfPV+WchpnA+26fpJ7Smzs51CzGBHC32RS03psuX/2l0l0UKh2StzNxOrKCYg==", "dev": true, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-discard-empty": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.0.tgz", - "integrity": "sha512-b+h1S1VT6dNhpcg+LpyiUrdnEZfICF0my7HAKgJixJLW7BnNmpRH34+uw/etf5AhOlIhIAuXApSzzDzMI9K/gQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.1.tgz", + "integrity": "sha512-yitcmKwmVWtNsrrRqGJ7/C0YRy53i0mjexBDQ9zYxDwTWVBgbU4+C9jIZLmQlTDT9zhml+u0OMFJh8+31krmOg==", "dev": true, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-discard-overridden": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.0.tgz", - "integrity": "sha512-4VELwssYXDFigPYAZ8vL4yX4mUepF/oCBeeIT4OXsJPYOtvJumyz9WflmJWTfDwCUcpDR+z0zvCWBXgTx35SVw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.1.tgz", + "integrity": "sha512-qs0ehZMMZpSESbRkw1+inkf51kak6OOzNRaoLd/U7Fatp0aN2HQ1rxGOrJvYcRAN9VpX8kUF13R2ofn8OlvFVA==", "dev": true, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-loader": { @@ -13918,43 +13918,43 @@ } }, "node_modules/postcss-merge-longhand": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.0.tgz", - "integrity": "sha512-4VSfd1lvGkLTLYcxFuISDtWUfFS4zXe0FpF149AyziftPFQIWxjvFSKhA4MIxMe4XM3yTDgQMbSNgzIVxChbIg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.2.tgz", + "integrity": "sha512-+yfVB7gEM8SrCo9w2lCApKIEzrTKl5yS1F4yGhV3kSim6JzbfLGJyhR1B6X+6vOT0U33Mgx7iv4X9MVWuaSAfw==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.0.0" + "stylehacks": "^6.0.2" }, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-merge-rules": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.1.tgz", - "integrity": "sha512-a4tlmJIQo9SCjcfiCcCMg/ZCEe0XTkl/xK0XHBs955GWg9xDX3NwP9pwZ78QUOWB8/0XCjZeJn98Dae0zg6AAw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.3.tgz", + "integrity": "sha512-yfkDqSHGohy8sGYIJwBmIGDv4K4/WrJPX355XrxQb/CSsT4Kc/RxDi6akqn5s9bap85AWgv21ArcUWwWdGNSHA==", "dev": true, "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.22.2", "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.0", - "postcss-selector-parser": "^6.0.5" + "cssnano-utils": "^4.0.1", + "postcss-selector-parser": "^6.0.15" }, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-minify-font-values": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.0.tgz", - "integrity": "sha512-zNRAVtyh5E8ndZEYXA4WS8ZYsAp798HiIQ1V2UF/C/munLp2r1UGHwf1+6JFu7hdEhJFN+W1WJQKBrtjhFgEnA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.1.tgz", + "integrity": "sha512-tIwmF1zUPoN6xOtA/2FgVk1ZKrLcCvE0dpZLtzyyte0j9zUeB8RTbCqrHZGjJlxOvNWKMYtunLrrl7HPOiR46w==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -13963,56 +13963,56 @@ "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-minify-gradients": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.0.tgz", - "integrity": "sha512-wO0F6YfVAR+K1xVxF53ueZJza3L+R3E6cp0VwuXJQejnNUH0DjcAFe3JEBeTY1dLwGa0NlDWueCA1VlEfiKgAA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.1.tgz", + "integrity": "sha512-M1RJWVjd6IOLPl1hYiOd5HQHgpp6cvJVLrieQYS9y07Yo8itAr6jaekzJphaJFR0tcg4kRewCk3kna9uHBxn/w==", "dev": true, "dependencies": { "colord": "^2.9.1", - "cssnano-utils": "^4.0.0", + "cssnano-utils": "^4.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-minify-params": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.0.0.tgz", - "integrity": "sha512-Fz/wMQDveiS0n5JPcvsMeyNXOIMrwF88n7196puSuQSWSa+/Ofc1gDOSY2xi8+A4PqB5dlYCKk/WfqKqsI+ReQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.0.2.tgz", + "integrity": "sha512-zwQtbrPEBDj+ApELZ6QylLf2/c5zmASoOuA4DzolyVGdV38iR2I5QRMsZcHkcdkZzxpN8RS4cN7LPskOkTwTZw==", "dev": true, "dependencies": { - "browserslist": "^4.21.4", - "cssnano-utils": "^4.0.0", + "browserslist": "^4.22.2", + "cssnano-utils": "^4.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-minify-selectors": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.0.tgz", - "integrity": "sha512-ec/q9JNCOC2CRDNnypipGfOhbYPuUkewGwLnbv6omue/PSASbHSU7s6uSQ0tcFRVv731oMIx8k0SP4ZX6be/0g==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.2.tgz", + "integrity": "sha512-0b+m+w7OAvZejPQdN2GjsXLv5o0jqYHX3aoV0e7RBKPCsB7TYG5KKWBFhGnB/iP3213Ts8c5H4wLPLMm7z28Sg==", "dev": true, "dependencies": { - "postcss-selector-parser": "^6.0.5" + "postcss-selector-parser": "^6.0.15" }, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-modules-extract-imports": { @@ -14075,21 +14075,21 @@ } }, "node_modules/postcss-normalize-charset": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.0.tgz", - "integrity": "sha512-cqundwChbu8yO/gSWkuFDmKrCZ2vJzDAocheT2JTd0sFNA4HMGoKMfbk2B+J0OmO0t5GUkiAkSM5yF2rSLUjgQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.1.tgz", + "integrity": "sha512-aW5LbMNRZ+oDV57PF9K+WI1Z8MPnF+A8qbajg/T8PP126YrGX1f9IQx21GI2OlGz7XFJi/fNi0GTbY948XJtXg==", "dev": true, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-display-values": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.0.tgz", - "integrity": "sha512-Qyt5kMrvy7dJRO3OjF7zkotGfuYALETZE+4lk66sziWSPzlBEt7FrUshV6VLECkI4EN8Z863O6Nci4NXQGNzYw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.1.tgz", + "integrity": "sha512-mc3vxp2bEuCb4LgCcmG1y6lKJu1Co8T+rKHrcbShJwUmKJiEl761qb/QQCfFwlrvSeET3jksolCR/RZuMURudw==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -14098,13 +14098,13 @@ "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-positions": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.0.tgz", - "integrity": "sha512-mPCzhSV8+30FZyWhxi6UoVRYd3ZBJgTRly4hOkaSifo0H+pjDYcii/aVT4YE6QpOil15a5uiv6ftnY3rm0igPg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.1.tgz", + "integrity": "sha512-HRsq8u/0unKNvm0cvwxcOUEcakFXqZ41fv3FOdPn916XFUrympjr+03oaLkuZENz3HE9RrQE9yU0Xv43ThWjQg==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -14113,13 +14113,13 @@ "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-repeat-style": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.0.tgz", - "integrity": "sha512-50W5JWEBiOOAez2AKBh4kRFm2uhrT3O1Uwdxz7k24aKtbD83vqmcVG7zoIwo6xI2FZ/HDlbrCopXhLeTpQib1A==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.1.tgz", + "integrity": "sha512-Gbb2nmCy6tTiA7Sh2MBs3fj9W8swonk6lw+dFFeQT68B0Pzwp1kvisJQkdV6rbbMSd9brMlS8I8ts52tAGWmGQ==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -14128,13 +14128,13 @@ "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-string": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.0.tgz", - "integrity": "sha512-KWkIB7TrPOiqb8ZZz6homet2KWKJwIlysF5ICPZrXAylGe2hzX/HSf4NTX2rRPJMAtlRsj/yfkrWGavFuB+c0w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.1.tgz", + "integrity": "sha512-5Fhx/+xzALJD9EI26Aq23hXwmv97Zfy2VFrt5PLT8lAhnBIZvmaT5pQk+NuJ/GWj/QWaKSKbnoKDGLbV6qnhXg==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -14143,13 +14143,13 @@ "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-timing-functions": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.0.tgz", - "integrity": "sha512-tpIXWciXBp5CiFs8sem90IWlw76FV4oi6QEWfQwyeREVwUy39VSeSqjAT7X0Qw650yAimYW5gkl2Gd871N5SQg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.1.tgz", + "integrity": "sha512-4zcczzHqmCU7L5dqTB9rzeqPWRMc0K2HoR+Bfl+FSMbqGBUcP5LRfgcH4BdRtLuzVQK1/FHdFoGT3F7rkEnY+g==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -14158,29 +14158,29 @@ "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-unicode": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.0.tgz", - "integrity": "sha512-ui5crYkb5ubEUDugDc786L/Me+DXp2dLg3fVJbqyAl0VPkAeALyAijF2zOsnZyaS1HyfPuMH0DwyY18VMFVNkg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.2.tgz", + "integrity": "sha512-Ff2VdAYCTGyMUwpevTZPZ4w0+mPjbZzLLyoLh/RMpqUqeQKZ+xMm31hkxBavDcGKcxm6ACzGk0nBfZ8LZkStKA==", "dev": true, "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.22.2", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-url": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.0.tgz", - "integrity": "sha512-98mvh2QzIPbb02YDIrYvAg4OUzGH7s1ZgHlD3fIdTHLgPLRpv1ZTKJDnSAKr4Rt21ZQFzwhGMXxpXlfrUBKFHw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.1.tgz", + "integrity": "sha512-jEXL15tXSvbjm0yzUV7FBiEXwhIa9H88JOXDGQzmcWoB4mSjZIsmtto066s2iW9FYuIrIF4k04HA2BKAOpbsaQ==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -14189,13 +14189,13 @@ "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-whitespace": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.0.tgz", - "integrity": "sha512-7cfE1AyLiK0+ZBG6FmLziJzqQCpTQY+8XjMhMAz8WSBSCsCNNUKujgIgjCAmDT3cJ+3zjTXFkoD15ZPsckArVw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.1.tgz", + "integrity": "sha512-76i3NpWf6bB8UHlVuLRxG4zW2YykF9CTEcq/9LGAiz2qBuX5cBStadkk0jSkg9a9TCIXbMQz7yzrygKoCW9JuA==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -14204,45 +14204,45 @@ "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-ordered-values": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.0.tgz", - "integrity": "sha512-K36XzUDpvfG/nWkjs6d1hRBydeIxGpKS2+n+ywlKPzx1nMYDYpoGbcjhj5AwVYJK1qV2/SDoDEnHzlPD6s3nMg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.1.tgz", + "integrity": "sha512-XXbb1O/MW9HdEhnBxitZpPFbIvDgbo9NK4c/5bOfiKpnIGZDoL2xd7/e6jW5DYLsWxBbs+1nZEnVgnjnlFViaA==", "dev": true, "dependencies": { - "cssnano-utils": "^4.0.0", + "cssnano-utils": "^4.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-reduce-initial": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.0.tgz", - "integrity": "sha512-s2UOnidpVuXu6JiiI5U+fV2jamAw5YNA9Fdi/GRK0zLDLCfXmSGqQtzpUPtfN66RtCbb9fFHoyZdQaxOB3WxVA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.2.tgz", + "integrity": "sha512-YGKalhNlCLcjcLvjU5nF8FyeCTkCO5UtvJEt0hrPZVCTtRLSOH4z00T1UntQPj4dUmIYZgMj8qK77JbSX95hSw==", "dev": true, "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.22.2", "caniuse-api": "^3.0.0" }, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-reduce-transforms": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.0.tgz", - "integrity": "sha512-FQ9f6xM1homnuy1wLe9lP1wujzxnwt1EwiigtWwuyf8FsqqXUDUp2Ulxf9A5yjlUOTdCJO6lonYjg1mgqIIi2w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.1.tgz", + "integrity": "sha512-fUbV81OkUe75JM+VYO1gr/IoA2b/dRiH6HvMwhrIBSUrxq3jNZQZitSnugcTLDi1KkQh1eR/zi+iyxviUNBkcQ==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -14251,13 +14251,13 @@ "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -14268,34 +14268,34 @@ } }, "node_modules/postcss-svgo": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.0.tgz", - "integrity": "sha512-r9zvj/wGAoAIodn84dR/kFqwhINp5YsJkLoujybWG59grR/IHx+uQ2Zo+IcOwM0jskfYX3R0mo+1Kip1VSNcvw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.2.tgz", + "integrity": "sha512-IH5R9SjkTkh0kfFOQDImyy1+mTCb+E830+9SV1O+AaDcoHTvfsvt6WwJeo7KwcHbFnevZVCsXhDmjFiGVuwqFQ==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0", - "svgo": "^3.0.2" + "svgo": "^3.2.0" }, "engines": { "node": "^14 || ^16 || >= 18" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-unique-selectors": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.0.tgz", - "integrity": "sha512-EPQzpZNxOxP7777t73RQpZE5e9TrnCrkvp7AH7a0l89JmZiPnS82y216JowHXwpBCQitfyxrof9TK3rYbi7/Yw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.2.tgz", + "integrity": "sha512-8IZGQ94nechdG7Y9Sh9FlIY2b4uS8/k8kdKRX040XHsS3B6d1HrJAkXrBSsSu4SuARruSsUjW3nlSw8BHkaAYQ==", "dev": true, "dependencies": { - "postcss-selector-parser": "^6.0.5" + "postcss-selector-parser": "^6.0.15" }, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-value-parser": { @@ -15949,19 +15949,19 @@ } }, "node_modules/stylehacks": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.0.0.tgz", - "integrity": "sha512-+UT589qhHPwz6mTlCLSt/vMNTJx8dopeJlZAlBMJPWA3ORqu6wmQY7FBXf+qD+FsqoBJODyqNxOUP3jdntFRdw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.0.2.tgz", + "integrity": "sha512-00zvJGnCu64EpMjX8b5iCZ3us2Ptyw8+toEkb92VdmkEaRaSGBNKAoK6aWZckhXxmQP8zWiTaFaiMGIU8Ve8sg==", "dev": true, "dependencies": { - "browserslist": "^4.21.4", - "postcss-selector-parser": "^6.0.4" + "browserslist": "^4.22.2", + "postcss-selector-parser": "^6.0.15" }, "engines": { "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/supports-color": { @@ -15987,15 +15987,16 @@ } }, "node_modules/svgo": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.2.tgz", - "integrity": "sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz", + "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==", "dev": true, "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", "css-select": "^5.1.0", - "css-tree": "^2.2.1", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.0.0" }, @@ -20160,12 +20161,12 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "@kurkle/color": { @@ -22462,9 +22463,9 @@ "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" }, "css-declaration-sorter": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.0.tgz", - "integrity": "sha512-jDfsatwWMWN0MODAFuHszfjphEXfNw9JUAhmY4pLu3TyTU+ohUpsbVtbU+1MZn4a47D9kqh03i4eyOm+74+zew==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.1.1.tgz", + "integrity": "sha512-dZ3bVTEEc1vxr3Bek9vGwfB5Z6ESPULhcRvO472mfjVnj8jRcTnKO8/JTczlvxM10Myb+wBM++1MtdO76eWcaQ==", "dev": true, "requires": {} }, @@ -22496,17 +22497,17 @@ } }, "css-minimizer-webpack-plugin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", - "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-6.0.0.tgz", + "integrity": "sha512-BLpR9CCDkKvhO3i0oZQgad6v9pCxUuhSc5RT6iUEy9M8hBXi4TJb5vqF2GQ2deqYHmRi3O6IR9hgAZQWg0EBwA==", "dev": true, "requires": { - "@jridgewell/trace-mapping": "^0.3.18", - "cssnano": "^6.0.1", - "jest-worker": "^29.4.3", - "postcss": "^8.4.24", - "schema-utils": "^4.0.1", - "serialize-javascript": "^6.0.1" + "@jridgewell/trace-mapping": "^0.3.21", + "cssnano": "^6.0.3", + "jest-worker": "^29.7.0", + "postcss": "^8.4.33", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" }, "dependencies": { "has-flag": { @@ -22516,13 +22517,13 @@ "dev": true }, "jest-worker": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", - "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "requires": { "@types/node": "*", - "jest-util": "^29.5.0", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } @@ -22589,56 +22590,56 @@ "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=" }, "cssnano": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.1.tgz", - "integrity": "sha512-fVO1JdJ0LSdIGJq68eIxOqFpIJrZqXUsBt8fkrBcztCQqAjQD51OhZp7tc0ImcbwXD4k7ny84QTV90nZhmqbkg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.3.tgz", + "integrity": "sha512-MRq4CIj8pnyZpcI2qs6wswoYoDD1t0aL28n+41c1Ukcpm56m1h6mCexIHBGjfZfnTqtGSSCP4/fB1ovxgjBOiw==", "dev": true, "requires": { - "cssnano-preset-default": "^6.0.1", - "lilconfig": "^2.1.0" + "cssnano-preset-default": "^6.0.3", + "lilconfig": "^3.0.0" } }, "cssnano-preset-default": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.1.tgz", - "integrity": "sha512-7VzyFZ5zEB1+l1nToKyrRkuaJIx0zi/1npjvZfbBwbtNTzhLtlvYraK/7/uqmX2Wb2aQtd983uuGw79jAjLSuQ==", - "dev": true, - "requires": { - "css-declaration-sorter": "^6.3.1", - "cssnano-utils": "^4.0.0", - "postcss-calc": "^9.0.0", - "postcss-colormin": "^6.0.0", - "postcss-convert-values": "^6.0.0", - "postcss-discard-comments": "^6.0.0", - "postcss-discard-duplicates": "^6.0.0", - "postcss-discard-empty": "^6.0.0", - "postcss-discard-overridden": "^6.0.0", - "postcss-merge-longhand": "^6.0.0", - "postcss-merge-rules": "^6.0.1", - "postcss-minify-font-values": "^6.0.0", - "postcss-minify-gradients": "^6.0.0", - "postcss-minify-params": "^6.0.0", - "postcss-minify-selectors": "^6.0.0", - "postcss-normalize-charset": "^6.0.0", - "postcss-normalize-display-values": "^6.0.0", - "postcss-normalize-positions": "^6.0.0", - "postcss-normalize-repeat-style": "^6.0.0", - "postcss-normalize-string": "^6.0.0", - "postcss-normalize-timing-functions": "^6.0.0", - "postcss-normalize-unicode": "^6.0.0", - "postcss-normalize-url": "^6.0.0", - "postcss-normalize-whitespace": "^6.0.0", - "postcss-ordered-values": "^6.0.0", - "postcss-reduce-initial": "^6.0.0", - "postcss-reduce-transforms": "^6.0.0", - "postcss-svgo": "^6.0.0", - "postcss-unique-selectors": "^6.0.0" + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.3.tgz", + "integrity": "sha512-4y3H370aZCkT9Ev8P4SO4bZbt+AExeKhh8wTbms/X7OLDo5E7AYUUy6YPxa/uF5Grf+AJwNcCnxKhZynJ6luBA==", + "dev": true, + "requires": { + "css-declaration-sorter": "^7.1.1", + "cssnano-utils": "^4.0.1", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.0.2", + "postcss-convert-values": "^6.0.2", + "postcss-discard-comments": "^6.0.1", + "postcss-discard-duplicates": "^6.0.1", + "postcss-discard-empty": "^6.0.1", + "postcss-discard-overridden": "^6.0.1", + "postcss-merge-longhand": "^6.0.2", + "postcss-merge-rules": "^6.0.3", + "postcss-minify-font-values": "^6.0.1", + "postcss-minify-gradients": "^6.0.1", + "postcss-minify-params": "^6.0.2", + "postcss-minify-selectors": "^6.0.2", + "postcss-normalize-charset": "^6.0.1", + "postcss-normalize-display-values": "^6.0.1", + "postcss-normalize-positions": "^6.0.1", + "postcss-normalize-repeat-style": "^6.0.1", + "postcss-normalize-string": "^6.0.1", + "postcss-normalize-timing-functions": "^6.0.1", + "postcss-normalize-unicode": "^6.0.2", + "postcss-normalize-url": "^6.0.1", + "postcss-normalize-whitespace": "^6.0.1", + "postcss-ordered-values": "^6.0.1", + "postcss-reduce-initial": "^6.0.2", + "postcss-reduce-transforms": "^6.0.1", + "postcss-svgo": "^6.0.2", + "postcss-unique-selectors": "^6.0.2" } }, "cssnano-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.0.tgz", - "integrity": "sha512-Z39TLP+1E0KUcd7LGyF4qMfu8ZufI0rDzhdyAMsa/8UyNUU8wpS0fhdBxbQbv32r64ea00h4878gommRVg2BHw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.1.tgz", + "integrity": "sha512-6qQuYDqsGoiXssZ3zct6dcMxiqfT6epy7x4R0TQJadd4LWO3sPR6JH6ZByOvVLoZ6EdwPGgd7+DR1EmX3tiXQQ==", "dev": true, "requires": {} }, @@ -27160,9 +27161,9 @@ } }, "lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", + "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", "dev": true }, "lines-and-columns": { @@ -28261,52 +28262,52 @@ } }, "postcss-colormin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.0.0.tgz", - "integrity": "sha512-EuO+bAUmutWoZYgHn2T1dG1pPqHU6L4TjzPlu4t1wZGXQ/fxV16xg2EJmYi0z+6r+MGV1yvpx1BHkUaRrPa2bw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.0.2.tgz", + "integrity": "sha512-TXKOxs9LWcdYo5cgmcSHPkyrLAh86hX1ijmyy6J8SbOhyv6ua053M3ZAM/0j44UsnQNIWdl8gb5L7xX2htKeLw==", "dev": true, "requires": { - "browserslist": "^4.21.4", + "browserslist": "^4.22.2", "caniuse-api": "^3.0.0", "colord": "^2.9.1", "postcss-value-parser": "^4.2.0" } }, "postcss-convert-values": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.0.0.tgz", - "integrity": "sha512-U5D8QhVwqT++ecmy8rnTb+RL9n/B806UVaS3m60lqle4YDFcpbS3ae5bTQIh3wOGUSDHSEtMYLs/38dNG7EYFw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.0.2.tgz", + "integrity": "sha512-aeBmaTnGQ+NUSVQT8aY0sKyAD/BaLJenEKZ03YK0JnDE1w1Rr8XShoxdal2V2H26xTJKr3v5haByOhJuyT4UYw==", "dev": true, "requires": { - "browserslist": "^4.21.4", + "browserslist": "^4.22.2", "postcss-value-parser": "^4.2.0" } }, "postcss-discard-comments": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.0.tgz", - "integrity": "sha512-p2skSGqzPMZkEQvJsgnkBhCn8gI7NzRH2683EEjrIkoMiwRELx68yoUJ3q3DGSGuQ8Ug9Gsn+OuDr46yfO+eFw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.1.tgz", + "integrity": "sha512-f1KYNPtqYLUeZGCHQPKzzFtsHaRuECe6jLakf/RjSRqvF5XHLZnM2+fXLhb8Qh/HBFHs3M4cSLb1k3B899RYIg==", "dev": true, "requires": {} }, "postcss-discard-duplicates": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.0.tgz", - "integrity": "sha512-bU1SXIizMLtDW4oSsi5C/xHKbhLlhek/0/yCnoMQany9k3nPBq+Ctsv/9oMmyqbR96HYHxZcHyK2HR5P/mqoGA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.1.tgz", + "integrity": "sha512-1hvUs76HLYR8zkScbwyJ8oJEugfPV+WchpnA+26fpJ7Smzs51CzGBHC32RS03psuX/2l0l0UKh2StzNxOrKCYg==", "dev": true, "requires": {} }, "postcss-discard-empty": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.0.tgz", - "integrity": "sha512-b+h1S1VT6dNhpcg+LpyiUrdnEZfICF0my7HAKgJixJLW7BnNmpRH34+uw/etf5AhOlIhIAuXApSzzDzMI9K/gQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.1.tgz", + "integrity": "sha512-yitcmKwmVWtNsrrRqGJ7/C0YRy53i0mjexBDQ9zYxDwTWVBgbU4+C9jIZLmQlTDT9zhml+u0OMFJh8+31krmOg==", "dev": true, "requires": {} }, "postcss-discard-overridden": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.0.tgz", - "integrity": "sha512-4VELwssYXDFigPYAZ8vL4yX4mUepF/oCBeeIT4OXsJPYOtvJumyz9WflmJWTfDwCUcpDR+z0zvCWBXgTx35SVw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.1.tgz", + "integrity": "sha512-qs0ehZMMZpSESbRkw1+inkf51kak6OOzNRaoLd/U7Fatp0aN2HQ1rxGOrJvYcRAN9VpX8kUF13R2ofn8OlvFVA==", "dev": true, "requires": {} }, @@ -28333,65 +28334,65 @@ } }, "postcss-merge-longhand": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.0.tgz", - "integrity": "sha512-4VSfd1lvGkLTLYcxFuISDtWUfFS4zXe0FpF149AyziftPFQIWxjvFSKhA4MIxMe4XM3yTDgQMbSNgzIVxChbIg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.2.tgz", + "integrity": "sha512-+yfVB7gEM8SrCo9w2lCApKIEzrTKl5yS1F4yGhV3kSim6JzbfLGJyhR1B6X+6vOT0U33Mgx7iv4X9MVWuaSAfw==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.0.0" + "stylehacks": "^6.0.2" } }, "postcss-merge-rules": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.1.tgz", - "integrity": "sha512-a4tlmJIQo9SCjcfiCcCMg/ZCEe0XTkl/xK0XHBs955GWg9xDX3NwP9pwZ78QUOWB8/0XCjZeJn98Dae0zg6AAw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.3.tgz", + "integrity": "sha512-yfkDqSHGohy8sGYIJwBmIGDv4K4/WrJPX355XrxQb/CSsT4Kc/RxDi6akqn5s9bap85AWgv21ArcUWwWdGNSHA==", "dev": true, "requires": { - "browserslist": "^4.21.4", + "browserslist": "^4.22.2", "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.0", - "postcss-selector-parser": "^6.0.5" + "cssnano-utils": "^4.0.1", + "postcss-selector-parser": "^6.0.15" } }, "postcss-minify-font-values": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.0.tgz", - "integrity": "sha512-zNRAVtyh5E8ndZEYXA4WS8ZYsAp798HiIQ1V2UF/C/munLp2r1UGHwf1+6JFu7hdEhJFN+W1WJQKBrtjhFgEnA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.1.tgz", + "integrity": "sha512-tIwmF1zUPoN6xOtA/2FgVk1ZKrLcCvE0dpZLtzyyte0j9zUeB8RTbCqrHZGjJlxOvNWKMYtunLrrl7HPOiR46w==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-minify-gradients": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.0.tgz", - "integrity": "sha512-wO0F6YfVAR+K1xVxF53ueZJza3L+R3E6cp0VwuXJQejnNUH0DjcAFe3JEBeTY1dLwGa0NlDWueCA1VlEfiKgAA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.1.tgz", + "integrity": "sha512-M1RJWVjd6IOLPl1hYiOd5HQHgpp6cvJVLrieQYS9y07Yo8itAr6jaekzJphaJFR0tcg4kRewCk3kna9uHBxn/w==", "dev": true, "requires": { "colord": "^2.9.1", - "cssnano-utils": "^4.0.0", + "cssnano-utils": "^4.0.1", "postcss-value-parser": "^4.2.0" } }, "postcss-minify-params": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.0.0.tgz", - "integrity": "sha512-Fz/wMQDveiS0n5JPcvsMeyNXOIMrwF88n7196puSuQSWSa+/Ofc1gDOSY2xi8+A4PqB5dlYCKk/WfqKqsI+ReQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.0.2.tgz", + "integrity": "sha512-zwQtbrPEBDj+ApELZ6QylLf2/c5zmASoOuA4DzolyVGdV38iR2I5QRMsZcHkcdkZzxpN8RS4cN7LPskOkTwTZw==", "dev": true, "requires": { - "browserslist": "^4.21.4", - "cssnano-utils": "^4.0.0", + "browserslist": "^4.22.2", + "cssnano-utils": "^4.0.1", "postcss-value-parser": "^4.2.0" } }, "postcss-minify-selectors": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.0.tgz", - "integrity": "sha512-ec/q9JNCOC2CRDNnypipGfOhbYPuUkewGwLnbv6omue/PSASbHSU7s6uSQ0tcFRVv731oMIx8k0SP4ZX6be/0g==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.2.tgz", + "integrity": "sha512-0b+m+w7OAvZejPQdN2GjsXLv5o0jqYHX3aoV0e7RBKPCsB7TYG5KKWBFhGnB/iP3213Ts8c5H4wLPLMm7z28Sg==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.5" + "postcss-selector-parser": "^6.0.15" } }, "postcss-modules-extract-imports": { @@ -28431,118 +28432,118 @@ } }, "postcss-normalize-charset": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.0.tgz", - "integrity": "sha512-cqundwChbu8yO/gSWkuFDmKrCZ2vJzDAocheT2JTd0sFNA4HMGoKMfbk2B+J0OmO0t5GUkiAkSM5yF2rSLUjgQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.1.tgz", + "integrity": "sha512-aW5LbMNRZ+oDV57PF9K+WI1Z8MPnF+A8qbajg/T8PP126YrGX1f9IQx21GI2OlGz7XFJi/fNi0GTbY948XJtXg==", "dev": true, "requires": {} }, "postcss-normalize-display-values": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.0.tgz", - "integrity": "sha512-Qyt5kMrvy7dJRO3OjF7zkotGfuYALETZE+4lk66sziWSPzlBEt7FrUshV6VLECkI4EN8Z863O6Nci4NXQGNzYw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.1.tgz", + "integrity": "sha512-mc3vxp2bEuCb4LgCcmG1y6lKJu1Co8T+rKHrcbShJwUmKJiEl761qb/QQCfFwlrvSeET3jksolCR/RZuMURudw==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-positions": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.0.tgz", - "integrity": "sha512-mPCzhSV8+30FZyWhxi6UoVRYd3ZBJgTRly4hOkaSifo0H+pjDYcii/aVT4YE6QpOil15a5uiv6ftnY3rm0igPg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.1.tgz", + "integrity": "sha512-HRsq8u/0unKNvm0cvwxcOUEcakFXqZ41fv3FOdPn916XFUrympjr+03oaLkuZENz3HE9RrQE9yU0Xv43ThWjQg==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-repeat-style": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.0.tgz", - "integrity": "sha512-50W5JWEBiOOAez2AKBh4kRFm2uhrT3O1Uwdxz7k24aKtbD83vqmcVG7zoIwo6xI2FZ/HDlbrCopXhLeTpQib1A==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.1.tgz", + "integrity": "sha512-Gbb2nmCy6tTiA7Sh2MBs3fj9W8swonk6lw+dFFeQT68B0Pzwp1kvisJQkdV6rbbMSd9brMlS8I8ts52tAGWmGQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-string": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.0.tgz", - "integrity": "sha512-KWkIB7TrPOiqb8ZZz6homet2KWKJwIlysF5ICPZrXAylGe2hzX/HSf4NTX2rRPJMAtlRsj/yfkrWGavFuB+c0w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.1.tgz", + "integrity": "sha512-5Fhx/+xzALJD9EI26Aq23hXwmv97Zfy2VFrt5PLT8lAhnBIZvmaT5pQk+NuJ/GWj/QWaKSKbnoKDGLbV6qnhXg==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-timing-functions": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.0.tgz", - "integrity": "sha512-tpIXWciXBp5CiFs8sem90IWlw76FV4oi6QEWfQwyeREVwUy39VSeSqjAT7X0Qw650yAimYW5gkl2Gd871N5SQg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.1.tgz", + "integrity": "sha512-4zcczzHqmCU7L5dqTB9rzeqPWRMc0K2HoR+Bfl+FSMbqGBUcP5LRfgcH4BdRtLuzVQK1/FHdFoGT3F7rkEnY+g==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-unicode": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.0.tgz", - "integrity": "sha512-ui5crYkb5ubEUDugDc786L/Me+DXp2dLg3fVJbqyAl0VPkAeALyAijF2zOsnZyaS1HyfPuMH0DwyY18VMFVNkg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.2.tgz", + "integrity": "sha512-Ff2VdAYCTGyMUwpevTZPZ4w0+mPjbZzLLyoLh/RMpqUqeQKZ+xMm31hkxBavDcGKcxm6ACzGk0nBfZ8LZkStKA==", "dev": true, "requires": { - "browserslist": "^4.21.4", + "browserslist": "^4.22.2", "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-url": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.0.tgz", - "integrity": "sha512-98mvh2QzIPbb02YDIrYvAg4OUzGH7s1ZgHlD3fIdTHLgPLRpv1ZTKJDnSAKr4Rt21ZQFzwhGMXxpXlfrUBKFHw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.1.tgz", + "integrity": "sha512-jEXL15tXSvbjm0yzUV7FBiEXwhIa9H88JOXDGQzmcWoB4mSjZIsmtto066s2iW9FYuIrIF4k04HA2BKAOpbsaQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-whitespace": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.0.tgz", - "integrity": "sha512-7cfE1AyLiK0+ZBG6FmLziJzqQCpTQY+8XjMhMAz8WSBSCsCNNUKujgIgjCAmDT3cJ+3zjTXFkoD15ZPsckArVw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.1.tgz", + "integrity": "sha512-76i3NpWf6bB8UHlVuLRxG4zW2YykF9CTEcq/9LGAiz2qBuX5cBStadkk0jSkg9a9TCIXbMQz7yzrygKoCW9JuA==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-ordered-values": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.0.tgz", - "integrity": "sha512-K36XzUDpvfG/nWkjs6d1hRBydeIxGpKS2+n+ywlKPzx1nMYDYpoGbcjhj5AwVYJK1qV2/SDoDEnHzlPD6s3nMg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.1.tgz", + "integrity": "sha512-XXbb1O/MW9HdEhnBxitZpPFbIvDgbo9NK4c/5bOfiKpnIGZDoL2xd7/e6jW5DYLsWxBbs+1nZEnVgnjnlFViaA==", "dev": true, "requires": { - "cssnano-utils": "^4.0.0", + "cssnano-utils": "^4.0.1", "postcss-value-parser": "^4.2.0" } }, "postcss-reduce-initial": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.0.tgz", - "integrity": "sha512-s2UOnidpVuXu6JiiI5U+fV2jamAw5YNA9Fdi/GRK0zLDLCfXmSGqQtzpUPtfN66RtCbb9fFHoyZdQaxOB3WxVA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.2.tgz", + "integrity": "sha512-YGKalhNlCLcjcLvjU5nF8FyeCTkCO5UtvJEt0hrPZVCTtRLSOH4z00T1UntQPj4dUmIYZgMj8qK77JbSX95hSw==", "dev": true, "requires": { - "browserslist": "^4.21.4", + "browserslist": "^4.22.2", "caniuse-api": "^3.0.0" } }, "postcss-reduce-transforms": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.0.tgz", - "integrity": "sha512-FQ9f6xM1homnuy1wLe9lP1wujzxnwt1EwiigtWwuyf8FsqqXUDUp2Ulxf9A5yjlUOTdCJO6lonYjg1mgqIIi2w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.1.tgz", + "integrity": "sha512-fUbV81OkUe75JM+VYO1gr/IoA2b/dRiH6HvMwhrIBSUrxq3jNZQZitSnugcTLDi1KkQh1eR/zi+iyxviUNBkcQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", "dev": true, "requires": { "cssesc": "^3.0.0", @@ -28550,22 +28551,22 @@ } }, "postcss-svgo": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.0.tgz", - "integrity": "sha512-r9zvj/wGAoAIodn84dR/kFqwhINp5YsJkLoujybWG59grR/IHx+uQ2Zo+IcOwM0jskfYX3R0mo+1Kip1VSNcvw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.2.tgz", + "integrity": "sha512-IH5R9SjkTkh0kfFOQDImyy1+mTCb+E830+9SV1O+AaDcoHTvfsvt6WwJeo7KwcHbFnevZVCsXhDmjFiGVuwqFQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0", - "svgo": "^3.0.2" + "svgo": "^3.2.0" } }, "postcss-unique-selectors": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.0.tgz", - "integrity": "sha512-EPQzpZNxOxP7777t73RQpZE5e9TrnCrkvp7AH7a0l89JmZiPnS82y216JowHXwpBCQitfyxrof9TK3rYbi7/Yw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.2.tgz", + "integrity": "sha512-8IZGQ94nechdG7Y9Sh9FlIY2b4uS8/k8kdKRX040XHsS3B6d1HrJAkXrBSsSu4SuARruSsUjW3nlSw8BHkaAYQ==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.5" + "postcss-selector-parser": "^6.0.15" } }, "postcss-value-parser": { @@ -29811,13 +29812,13 @@ } }, "stylehacks": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.0.0.tgz", - "integrity": "sha512-+UT589qhHPwz6mTlCLSt/vMNTJx8dopeJlZAlBMJPWA3ORqu6wmQY7FBXf+qD+FsqoBJODyqNxOUP3jdntFRdw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.0.2.tgz", + "integrity": "sha512-00zvJGnCu64EpMjX8b5iCZ3us2Ptyw8+toEkb92VdmkEaRaSGBNKAoK6aWZckhXxmQP8zWiTaFaiMGIU8Ve8sg==", "dev": true, "requires": { - "browserslist": "^4.21.4", - "postcss-selector-parser": "^6.0.4" + "browserslist": "^4.22.2", + "postcss-selector-parser": "^6.0.15" } }, "supports-color": { @@ -29834,15 +29835,16 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, "svgo": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.2.tgz", - "integrity": "sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz", + "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==", "dev": true, "requires": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", "css-select": "^5.1.0", - "css-tree": "^2.2.1", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.0.0" } diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 6d7cb3260666..76c1ab6d078c 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -89,7 +89,7 @@ "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", "css-loader": "^6.9.1", - "css-minimizer-webpack-plugin": "^5.0.1", + "css-minimizer-webpack-plugin": "^6.0.0", "eslint": "^8.56.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.1", From 4ae6242e789221082b637302b24747e6f551264b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jan 2024 12:24:25 +0000 Subject: [PATCH 324/607] Bump autoprefixer in /apps/block_scout_web/assets Bumps [autoprefixer](https://github.com/postcss/autoprefixer) from 10.4.16 to 10.4.17. - [Release notes](https://github.com/postcss/autoprefixer/releases) - [Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/autoprefixer/compare/10.4.16...10.4.17) --- updated-dependencies: - dependency-name: autoprefixer dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 50 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 3129abdda64d..03e91e6624d0 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -73,7 +73,7 @@ "devDependencies": { "@babel/core": "^7.23.7", "@babel/preset-env": "^7.23.8", - "autoprefixer": "^10.4.16", + "autoprefixer": "^10.4.17", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", "css-loader": "^6.9.1", @@ -4634,9 +4634,9 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "node_modules/autoprefixer": { - "version": "10.4.16", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", - "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", + "version": "10.4.17", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", + "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==", "dev": true, "funding": [ { @@ -4653,9 +4653,9 @@ } ], "dependencies": { - "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001538", - "fraction.js": "^4.3.6", + "browserslist": "^4.22.2", + "caniuse-lite": "^1.0.30001578", + "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -5508,9 +5508,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001568", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001568.tgz", - "integrity": "sha512-vSUkH84HontZJ88MiNrOau1EBrCqEQYgkC5gIySiDlpsm8sGVrhU7Kx4V6h0tnqaHzIHZv08HlJIwPbL4XL9+A==", + "version": "1.0.30001579", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz", + "integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==", "funding": [ { "type": "opencollective", @@ -8985,9 +8985,9 @@ } }, "node_modules/fraction.js": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz", - "integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true, "engines": { "node": "*" @@ -21265,14 +21265,14 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "autoprefixer": { - "version": "10.4.16", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", - "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", + "version": "10.4.17", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", + "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==", "dev": true, "requires": { - "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001538", - "fraction.js": "^4.3.6", + "browserslist": "^4.22.2", + "caniuse-lite": "^1.0.30001578", + "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -21914,9 +21914,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001568", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001568.tgz", - "integrity": "sha512-vSUkH84HontZJ88MiNrOau1EBrCqEQYgkC5gIySiDlpsm8sGVrhU7Kx4V6h0tnqaHzIHZv08HlJIwPbL4XL9+A==" + "version": "1.0.30001579", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz", + "integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==" }, "caseless": { "version": "0.12.0", @@ -24646,9 +24646,9 @@ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, "fraction.js": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz", - "integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true }, "fresh": { diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 6d7cb3260666..3ac0c90e232a 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -85,7 +85,7 @@ "devDependencies": { "@babel/core": "^7.23.7", "@babel/preset-env": "^7.23.8", - "autoprefixer": "^10.4.16", + "autoprefixer": "^10.4.17", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", "css-loader": "^6.9.1", From a22d4ca3366b9a4c78d7e24f15f751861f7fdd95 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 23 Jan 2024 21:13:49 +0400 Subject: [PATCH 325/607] chore: docstrings and broken tests --- .../lib/explorer/chain/beacon/blob.ex | 11 +++++ .../lib/explorer/chain/beacon/reader.ex | 45 ++++++++++++++++--- apps/explorer/lib/explorer/chain/block.ex | 2 +- apps/explorer/test/support/factory.ex | 26 +++++++++++ apps/indexer/lib/indexer/block/fetcher.ex | 4 +- .../lib/indexer/fetcher/beacon/blob.ex | 14 ++---- .../lib/indexer/fetcher/beacon/client.ex | 6 +++ .../bound_interval_supervisor_test.exs | 11 ++++- .../indexer/block/catchup/fetcher_test.exs | 16 +++++-- .../test/indexer/block/fetcher_test.exs | 9 ++++ .../indexer/block/realtime/fetcher_test.exs | 19 ++++++++ .../fetcher/beacon_blob_supervisor_case.ex | 18 ++++++++ config/runtime.exs | 2 +- 13 files changed, 159 insertions(+), 24 deletions(-) create mode 100644 apps/indexer/test/support/indexer/fetcher/beacon_blob_supervisor_case.ex diff --git a/apps/explorer/lib/explorer/chain/beacon/blob.ex b/apps/explorer/lib/explorer/chain/beacon/blob.ex index b0ab109cf847..d6cb28a27402 100644 --- a/apps/explorer/lib/explorer/chain/beacon/blob.ex +++ b/apps/explorer/lib/explorer/chain/beacon/blob.ex @@ -33,4 +33,15 @@ defmodule Explorer.Chain.Beacon.Blob do |> validate_required(@required_attrs) |> unique_constraint(:hash) end + + @doc """ + Returns the `hash` of the `t:Explorer.Chain.Beacon.Blob.t/0` as per EIP-4844. + """ + @spec hash(binary()) :: Hash.Full.t() + def hash(kzg_commitment) do + raw_hash = :crypto.hash(:sha256, kzg_commitment) + <<_::size(8), rest::binary>> = raw_hash + {:ok, hash} = Hash.Full.cast(<<1>> <> rest) + hash + end end diff --git a/apps/explorer/lib/explorer/chain/beacon/reader.ex b/apps/explorer/lib/explorer/chain/beacon/reader.ex index 0f06298550ab..b40ab894ab95 100644 --- a/apps/explorer/lib/explorer/chain/beacon/reader.ex +++ b/apps/explorer/lib/explorer/chain/beacon/reader.ex @@ -17,37 +17,67 @@ defmodule Explorer.Chain.Beacon.Reader do import Explorer.Chain, only: [select_repo: 1] alias Explorer.{Chain, Repo} - alias Explorer.Chain.{DenormalizationHelper, Hash, Transaction} + alias Explorer.Chain.{Block, DenormalizationHelper, Hash, Transaction} alias Explorer.Chain.Beacon.{Blob, BlobTransaction} + @doc """ + Finds `t:Explorer.Chain.Beacon.Blob.t/0` by its `hash`. + + Returns `{:ok, %Explorer.Chain.Beacon.Blob{}}` if found + + iex> %Explorer.Chain.Beacon.Blob{hash: hash} = insert(:blob) + iex> {:ok, %Explorer.Chain.Beacon.Blob{hash: found_hash}} = Explorer.Chain.Beacon.Reader.blob(hash) + iex> found_hash == hash + true + + Returns `{:error, :not_found}` if not found + + iex> {:ok, hash} = Explorer.Chain.string_to_transaction_hash( + ...> "0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b" + ...> ) + iex> Explorer.Chain.Beacon.Reader.blob(hash) + {:error, :not_found} + + """ @spec blob(Hash.Full.t(), [Chain.api?()]) :: {:error, :not_found} | {:ok, Blob.t()} - def blob(hash, options) when is_list(options) do + def blob(hash, options \\ []) when is_list(options) do Blob |> where(hash: ^hash) |> select_repo(options).one() |> case do nil -> {:error, :not_found} - batch -> {:ok, batch} + blob -> {:ok, blob} end end + @doc """ + Finds associated transaction hashes for the given blob `hash` identifier. Returns at most 10 matches. + + Returns a list of `%{block_consensus: boolean(), transaction_hash: Hash.Full.t()}` maps for all found transactions. + + iex> %Explorer.Chain.Beacon.Blob{hash: blob_hash} = insert(:blob) + iex> %Explorer.Chain.Beacon.BlobTransaction{hash: transaction_hash} = insert(:blob_transaction, blob_versioned_hashes: [blob_hash]) + iex> blob_transactions = Explorer.Chain.Beacon.Reader.blob_hash_to_transactions(blob_hash) + iex> blob_transactions == [%{block_consensus: true, transaction_hash: transaction_hash}] + true + """ @spec blob_hash_to_transactions(Hash.Full.t(), [Chain.api?()]) :: [ %{ block_consensus: boolean(), transaction_hash: Hash.Full.t() } ] - def blob_hash_to_transactions(hash, options) when is_list(options) do + def blob_hash_to_transactions(hash, options \\ []) when is_list(options) do query = BlobTransaction |> where(type(^hash, Hash.Full) == fragment("any(blob_versioned_hashes)")) |> join(:inner, [bt], transaction in Transaction, on: bt.hash == transaction.hash) - |> order_by([bt, transaction], desc: transaction.block_consensus, desc: transaction.block_number) |> limit(10) query_with_denormalization = if DenormalizationHelper.denormalization_finished?() do query + |> order_by([bt, transaction], desc: transaction.block_consensus, desc: transaction.block_number) |> select([bt, transaction], %{ block_consensus: transaction.block_consensus, transaction_hash: transaction.hash @@ -55,6 +85,7 @@ defmodule Explorer.Chain.Beacon.Reader do else query |> join(:inner, [bt, transaction], block in Block, on: block.hash == transaction.block_hash) + |> order_by([bt, transaction, block], desc: block.consensus, desc: transaction.block_number) |> select([bt, transaction, block], %{ block_consensus: block.consensus, transaction_hash: transaction.hash @@ -64,6 +95,10 @@ defmodule Explorer.Chain.Beacon.Reader do query_with_denormalization |> select_repo(options).all() end + @doc """ + Returns a stream of all unique block timestamps containing missing data blobs. + Filters blocks by `min_block` and `max_block` if provided. + """ @spec stream_missed_blob_transactions_timestamps( initial :: accumulator, reducer :: (entry :: Hash.Address.t(), accumulator -> accumulator), diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index 79fe7ee8788b..cb32199eb470 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -298,7 +298,7 @@ defmodule Explorer.Chain.Block do |> Decimal.new() |> Decimal.add(acc) end) - |> Decimal.add(gas_price_to_decimal(base_fee_per_gas)) + |> Decimal.mult(gas_price_to_decimal(base_fee_per_gas)) end end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 5fdd286ee056..5ab4e0dad0b7 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -21,6 +21,7 @@ defmodule Explorer.Factory do } alias Explorer.Admin.Administrator + alias Explorer.Chain.Beacon.{Blob, BlobTransaction} alias Explorer.Chain.Block.{EmissionReward, Range, Reward} alias Explorer.Chain.{ @@ -1086,5 +1087,30 @@ defmodule Explorer.Factory do sequence("withdrawal_validator_index", & &1) end + def blob_factory do + kzg_commitment = data(:kzg_commitment) + + %Blob{ + hash: Blob.hash(kzg_commitment.bytes), + blob_data: data(:blob_data), + kzg_commitment: kzg_commitment, + kzg_proof: data(:kzg_proof) + } + end + + def blob_transaction_factory do + blob = build(:blob) + transaction = build(:transaction) + + %BlobTransaction{ + hash: transaction.hash, + transaction: transaction, + max_fee_per_blob_gas: Decimal.new(1_000_000_000), + blob_gas_price: Decimal.new(1_000_000_000), + blob_gas_used: Decimal.new(131_072), + blob_versioned_hashes: [blob.hash] + } + end + def random_bool, do: Enum.random([true, false]) end diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index bf91091f8bc0..06b4985dbc2c 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -391,7 +391,9 @@ defmodule Indexer.Block.Fetcher do |> Enum.filter(fn block -> block |> Map.get(:blob_gas_used, 0) > 0 end) |> Enum.map(&Map.get(&1, :timestamp)) - Blob.async_fetch(timestamps) + if !Enum.empty?(timestamps) do + Blob.async_fetch(timestamps) + end end def async_import_blobs(_), do: :ok diff --git a/apps/indexer/lib/indexer/fetcher/beacon/blob.ex b/apps/indexer/lib/indexer/fetcher/beacon/blob.ex index 15061046b931..eaa1f7dd095e 100644 --- a/apps/indexer/lib/indexer/fetcher/beacon/blob.ex +++ b/apps/indexer/lib/indexer/fetcher/beacon/blob.ex @@ -9,8 +9,8 @@ defmodule Indexer.Fetcher.Beacon.Blob do require Logger alias Explorer.Repo - alias Explorer.Chain.{Data, Hash} alias Explorer.Chain.Beacon.{Blob, Reader} + alias Explorer.Chain.Data alias Indexer.{BufferedTask, Tracer} alias Indexer.Fetcher.Beacon.Blob.Supervisor, as: BlobSupervisor alias Indexer.Fetcher.Beacon.Client @@ -41,7 +41,6 @@ defmodule Indexer.Fetcher.Beacon.Blob do optional(:type) => :supervisor | :worker } @doc false - # credo:disable-for-next-line Credo.Check.Design.DuplicatedCode def child_spec([init_options, gen_server_options]) do state = :indexer @@ -133,20 +132,13 @@ defmodule Indexer.Fetcher.Beacon.Blob do {:ok, kzg_proof} = Data.cast(kzg_proof) %{ - hash: blob_hash(kzg_commitment.bytes), + hash: Blob.hash(kzg_commitment.bytes), blob_data: blob, kzg_commitment: kzg_commitment, kzg_proof: kzg_proof } end - defp blob_hash(kzg_commitment) do - raw_hash = :crypto.hash(:sha256, kzg_commitment) - <<_::size(8), rest::binary>> = raw_hash - {:ok, hash} = Hash.Full.cast(<<1>> <> rest) - hash - end - defp defaults do [ poll: false, @@ -154,7 +146,7 @@ defmodule Indexer.Fetcher.Beacon.Blob do max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size, max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency, task_supervisor: Indexer.Fetcher.Beacon.Blob.TaskSupervisor, - metadata: [fetcher: :beacon_blobs_sanitize] + metadata: [fetcher: :beacon_blob] ] end end diff --git a/apps/indexer/lib/indexer/fetcher/beacon/client.ex b/apps/indexer/lib/indexer/fetcher/beacon/client.ex index c5554e367e3b..4c97d20089bc 100644 --- a/apps/indexer/lib/indexer/fetcher/beacon/client.ex +++ b/apps/indexer/lib/indexer/fetcher/beacon/client.ex @@ -31,6 +31,12 @@ defmodule Indexer.Fetcher.Beacon.Client do end end + @doc """ + Fetches blob sidecars for multiple given beacon `slots` from the beacon RPC. + + Returns `{:ok, blob_sidecars_list, retry_indices_list}` + where `retry_indices_list` is the list of indices from `slots` for which the request failed and should be retried. + """ @spec get_blob_sidecars([integer()]) :: {:ok, list(), [integer()]} def get_blob_sidecars(slots) when is_list(slots) do {oks, errors_with_retries} = diff --git a/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs b/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs index 774ebfe3a5ab..0d33ad553fe1 100644 --- a/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs +++ b/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs @@ -11,6 +11,7 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do alias Indexer.BoundInterval alias Indexer.Block.Catchup alias Indexer.Block.Catchup.MissingRangesCollector + alias Indexer.Fetcher.Beacon.Blob alias Indexer.Fetcher.{ CoinBalance, @@ -32,8 +33,14 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do describe "start_link/1" do setup do - initial_env = Application.get_env(:indexer, :block_ranges) - on_exit(fn -> Application.put_env(:indexer, :block_ranges, initial_env) end) + initial_block_ranges = Application.get_env(:indexer, :block_ranges) + initial_blob_disabled = Application.get_env(:indexer, Blob.Supervisor)[:disabled?] + Application.put_env(:indexer, Blob.Supervisor, disabled?: true) + + on_exit(fn -> + Application.put_env(:indexer, :block_ranges, initial_block_ranges) + Application.put_env(:indexer, Blob.Supervisor, disabled?: initial_blob_disabled) + end) end # See https://github.com/poanetwork/blockscout/issues/597 diff --git a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs index 93f687907ca0..312304dbd408 100644 --- a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs @@ -13,6 +13,7 @@ defmodule Indexer.Block.Catchup.FetcherTest do alias Indexer.Block alias Indexer.Block.Catchup.Fetcher alias Indexer.Block.Catchup.MissingRangesCollector + alias Indexer.Fetcher.Beacon.Blob alias Indexer.Fetcher.{BlockReward, CoinBalance, InternalTransaction, Token, TokenBalance, UncleBlock} @moduletag capture_log: true @@ -37,11 +38,14 @@ defmodule Indexer.Block.Catchup.FetcherTest do describe "import/1" do setup do - configuration = Application.get_env(:indexer, :last_block) + initial_last_block = Application.get_env(:indexer, :last_block) + initial_blob_disabled = Application.get_env(:indexer, Blob.Supervisor)[:disabled?] Application.put_env(:indexer, :last_block, 0) + Application.put_env(:indexer, Blob.Supervisor, disabled?: true) on_exit(fn -> - Application.put_env(:indexer, :last_block, configuration) + Application.put_env(:indexer, :last_block, initial_last_block) + Application.put_env(:indexer, Blob.Supervisor, disabled?: initial_blob_disabled) end) end @@ -139,7 +143,13 @@ defmodule Indexer.Block.Catchup.FetcherTest do describe "task/1" do setup do initial_env = Application.get_env(:indexer, :block_ranges) - on_exit(fn -> Application.put_env(:indexer, :block_ranges, initial_env) end) + initial_blob_disabled = Application.get_env(:indexer, Blob.Supervisor)[:disabled?] + Application.put_env(:indexer, Blob.Supervisor, disabled?: true) + + on_exit(fn -> + Application.put_env(:indexer, :block_ranges, initial_env) + Application.put_env(:indexer, Blob.Supervisor, disabled?: initial_blob_disabled) + end) end test "ignores fetched beneficiaries with different hash for same number", %{ diff --git a/apps/indexer/test/indexer/block/fetcher_test.exs b/apps/indexer/test/indexer/block/fetcher_test.exs index 0498cfd37ecb..54124a1025f5 100644 --- a/apps/indexer/test/indexer/block/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/fetcher_test.exs @@ -11,6 +11,8 @@ defmodule Indexer.Block.FetcherTest do alias Indexer.Block.Fetcher alias Indexer.BufferedTask + alias Indexer.Fetcher.Beacon.Blob + alias Indexer.Fetcher.{ CoinBalance, ContractCode, @@ -60,6 +62,13 @@ defmodule Indexer.Block.FetcherTest do block_fetcher: %Fetcher{json_rpc_named_arguments: json_rpc_named_arguments} ) + initial_blob_disabled = Application.get_env(:indexer, Blob.Supervisor)[:disabled?] + Application.put_env(:indexer, Blob.Supervisor, disabled?: true) + + on_exit(fn -> + Application.put_env(:indexer, Blob.Supervisor, disabled?: initial_blob_disabled) + end) + %{ block_fetcher: %Fetcher{ broadcast: false, diff --git a/apps/indexer/test/indexer/block/realtime/fetcher_test.exs b/apps/indexer/test/indexer/block/realtime/fetcher_test.exs index 9d0459e915bc..9685788d78ce 100644 --- a/apps/indexer/test/indexer/block/realtime/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/realtime/fetcher_test.exs @@ -8,6 +8,7 @@ defmodule Indexer.Block.Realtime.FetcherTest do alias Explorer.Chain.{Address, Transaction, Wei} alias Indexer.Block.Catchup.Sequence alias Indexer.Block.Realtime + alias Indexer.Fetcher.Beacon.Blob alias Indexer.Fetcher.{ContractCode, InternalTransaction, ReplacedTransaction, Token, TokenBalance, UncleBlock} @moduletag capture_log: true @@ -41,6 +42,15 @@ defmodule Indexer.Block.Realtime.FetcherTest do end describe "Indexer.Block.Fetcher.fetch_and_import_range/1" do + setup do + initial_blob_disabled = Application.get_env(:indexer, Blob.Supervisor)[:disabled?] + Application.put_env(:indexer, Blob.Supervisor, disabled?: true) + + on_exit(fn -> + Application.put_env(:indexer, Blob.Supervisor, disabled?: initial_blob_disabled) + end) + end + @tag :no_geth test "in range with internal transactions", %{ block_fetcher: %Indexer.Block.Fetcher{} = block_fetcher, @@ -1057,6 +1067,15 @@ defmodule Indexer.Block.Realtime.FetcherTest do end describe "start_fetch_and_import" do + setup do + initial_blob_disabled = Application.get_env(:indexer, Blob.Supervisor)[:disabled?] + Application.put_env(:indexer, Blob.Supervisor, disabled?: true) + + on_exit(fn -> + Application.put_env(:indexer, Blob.Supervisor, disabled?: initial_blob_disabled) + end) + end + @tag :no_geth test "reorg", %{ block_fetcher: block_fetcher, diff --git a/apps/indexer/test/support/indexer/fetcher/beacon_blob_supervisor_case.ex b/apps/indexer/test/support/indexer/fetcher/beacon_blob_supervisor_case.ex new file mode 100644 index 000000000000..661b7ce3133d --- /dev/null +++ b/apps/indexer/test/support/indexer/fetcher/beacon_blob_supervisor_case.ex @@ -0,0 +1,18 @@ +defmodule Indexer.Fetcher.Beacon.Blob.Supervisor.Case do + alias Indexer.Fetcher.Beacon.Blob + + def start_supervised!(fetcher_arguments \\ []) when is_list(fetcher_arguments) do + merged_fetcher_arguments = + Keyword.merge( + fetcher_arguments, + flush_interval: 50, + max_batch_size: 1, + max_concurrency: 1, + poll: false + ) + + [merged_fetcher_arguments] + |> Blob.Supervisor.child_spec() + |> ExUnit.Callbacks.start_supervised!() + end +end diff --git a/config/runtime.exs b/config/runtime.exs index 9292a8dbd8a1..f2b742be9649 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -679,7 +679,7 @@ config :indexer, Indexer.Fetcher.RootstockData, max_concurrency: ConfigHelper.parse_integer_env_var("INDEXER_ROOTSTOCK_DATA_FETCHER_CONCURRENCY", 5), db_batch_size: ConfigHelper.parse_integer_env_var("INDEXER_ROOTSTOCK_DATA_FETCHER_DB_BATCH_SIZE", 300) -config :indexer, Indexer.Fetcher.Beacon, beacon_rpc: System.get_env("INDEXER_BEACON_RPC_URL") +config :indexer, Indexer.Fetcher.Beacon, beacon_rpc: System.get_env("INDEXER_BEACON_RPC_URL") || "http://localhost:5052" config :indexer, Indexer.Fetcher.Beacon.Blob.Supervisor, disabled?: From c7724f51dfa408739fbf8d724e0031b5758ffefe Mon Sep 17 00:00:00 2001 From: varasev <33550681+varasev@users.noreply.github.com> Date: Wed, 24 Jan 2024 12:36:15 +0300 Subject: [PATCH 326/607] Shibarium Bridge indexer and API v2 extension (#8929) * Define shibarium_bridge table * Add ShibariumBridgeOperations runner * Add init for Indexer.Fetcher.Shibarium.L1 * Draft for Indexer.Fetcher.Shibarium.L1 * mix format for Indexer.Fetcher.Shibarium.L1 * Fix Indexer.Fetcher.Shibarium.L1 * Refactor Indexer.Fetcher.Shibarium.L1 * Add draft (incomplete) for Indexer.Fetcher.Shibarium.L2 * Extend draft (incomplete) for Indexer.Fetcher.Shibarium.L2 * Extend draft (incomplete) for Indexer.Fetcher.Shibarium.L2 * Complete unrefactored Indexer.Fetcher.Shibarium.L2 * Improve Indexer.Fetcher.Shibarium.L2 * Prepare Indexer.Fetcher.Shibarium.L2 for transformer * Add unrefactored Indexer.Transform.Shibarium.Bridge * Small refactoring of Shibarium modules * Refactoring * Add API v2 for Shibarium Deposits and Withdrawals * Small fixes * Update changelog * Fixes and refactoring * Partially add specs and docs * Partially add specs and docs * Log topic type changed to bytea * Small improvements * Cache deposits and withdrawals counters for Shibarium * Fixes for credo * Fixes for Chain.import related to CHAIN_TYPE * Reset GA cache * Small refactoring * Update mix.exs * Remove unnecessary credo ignore --------- Co-authored-by: POA <33550681+poa@users.noreply.github.com> Co-authored-by: Viktor Baranov --- .dialyzer-ignore | 10 +- .github/workflows/config.yml | 26 +- CHANGELOG.md | 1 + .../lib/block_scout_web/api_router.ex | 9 + .../lib/block_scout_web/chain.ex | 10 + .../api/v2/shibarium_controller.ex | 79 ++ .../views/api/v2/shibarium_view.ex | 46 ++ apps/block_scout_web/test/test_helper.exs | 1 + apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex | 6 +- apps/explorer/config/dev.exs | 2 + apps/explorer/config/prod.exs | 4 + apps/explorer/config/test.exs | 8 +- apps/explorer/lib/explorer/application.ex | 8 +- apps/explorer/lib/explorer/chain.ex | 10 + .../explorer/chain/cache/shibarium_counter.ex | 58 ++ apps/explorer/lib/explorer/chain/import.ex | 14 +- .../runner/shibarium/bridge_operations.ex | 119 +++ .../lib/explorer/chain/import/stage.ex | 10 +- .../stage/addresses_blocks_coin_balances.ex | 3 + .../chain/import/stage/block_following.ex | 4 + .../chain/import/stage/block_pending.ex | 4 + .../chain/import/stage/block_referencing.ex | 40 +- .../lib/explorer/chain/shibarium/bridge.ex | 85 +++ .../lib/explorer/chain/shibarium/reader.ex | 108 +++ apps/explorer/lib/explorer/repo.ex | 102 +-- .../lib/explorer/repo/config_helper.ex | 18 + .../20231024091228_add_bridge_table.exs | 34 + apps/explorer/test/support/data_case.ex | 2 + apps/explorer/test/test_helper.exs | 1 + apps/indexer/lib/indexer/block/fetcher.ex | 26 +- .../lib/indexer/block/realtime/fetcher.ex | 10 + .../lib/indexer/fetcher/polygon_edge.ex | 151 +--- .../fetcher/polygon_edge/deposit_execute.ex | 22 +- .../fetcher/polygon_edge/withdrawal.ex | 22 +- .../lib/indexer/fetcher/shibarium/helper.ex | 136 ++++ .../lib/indexer/fetcher/shibarium/l1.ex | 722 ++++++++++++++++++ .../lib/indexer/fetcher/shibarium/l2.ex | 536 +++++++++++++ .../fetcher/zkevm/transaction_batch.ex | 45 +- apps/indexer/lib/indexer/helper.ex | 154 ++++ apps/indexer/lib/indexer/supervisor.ex | 20 +- .../lib/indexer/transform/addresses.ex | 10 + .../lib/indexer/transform/shibarium/bridge.ex | 99 +++ config/config_helper.exs | 1 + config/runtime.exs | 35 +- config/runtime/dev.exs | 7 + config/runtime/prod.exs | 6 + cspell.json | 2 + 47 files changed, 2505 insertions(+), 321 deletions(-) create mode 100644 apps/block_scout_web/lib/block_scout_web/controllers/api/v2/shibarium_controller.ex create mode 100644 apps/block_scout_web/lib/block_scout_web/views/api/v2/shibarium_view.ex create mode 100644 apps/explorer/lib/explorer/chain/cache/shibarium_counter.ex create mode 100644 apps/explorer/lib/explorer/chain/import/runner/shibarium/bridge_operations.ex create mode 100644 apps/explorer/lib/explorer/chain/shibarium/bridge.ex create mode 100644 apps/explorer/lib/explorer/chain/shibarium/reader.ex create mode 100644 apps/explorer/priv/shibarium/migrations/20231024091228_add_bridge_table.exs create mode 100644 apps/indexer/lib/indexer/fetcher/shibarium/helper.ex create mode 100644 apps/indexer/lib/indexer/fetcher/shibarium/l1.ex create mode 100644 apps/indexer/lib/indexer/fetcher/shibarium/l2.ex create mode 100644 apps/indexer/lib/indexer/transform/shibarium/bridge.ex diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 15662b23302e..8441fdc9d929 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -13,14 +13,8 @@ lib/block_scout_web/schema/types.ex:31 lib/phoenix/router.ex:324 lib/phoenix/router.ex:402 lib/explorer/smart_contract/reader.ex:435 -lib/indexer/fetcher/polygon_edge.ex:737 -lib/indexer/fetcher/polygon_edge/deposit_execute.ex:151 -lib/indexer/fetcher/polygon_edge/deposit_execute.ex:195 -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/explorer/exchange_rates/source.ex:139 +lib/explorer/exchange_rates/source.ex:142 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:170 diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 14bb38b8d2ea..4c59f5a582d6 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -75,7 +75,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_35-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_36-${{ 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_35-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_36-${{ 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_35-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_36-${{ 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_35-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_36-${{ 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_35-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_36-${{ 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_35-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_36-${{ 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_35-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_36-${{ 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_35-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_36-${{ 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_35-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_36-${{ 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_35-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_36-${{ 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_35-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_36-${{ 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_35-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_36-${{ 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_35-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_36-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" diff --git a/CHANGELOG.md b/CHANGELOG.md index b55a562d3104..d376041de47a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,6 +90,7 @@ - [#8972](https://github.com/blockscout/blockscout/pull/8972) - BENS integration - [#8960](https://github.com/blockscout/blockscout/pull/8960) - TRACE_BLOCK_RANGES env var - [#8957](https://github.com/blockscout/blockscout/pull/8957) - Add Tx Interpreter Service integration +- [#8929](https://github.com/blockscout/blockscout/pull/8929) - Shibarium Bridge indexer and API v2 extension ### Fixes diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index d029916e2dcd..6899c96f3e72 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -288,6 +288,15 @@ defmodule BlockScoutWeb.ApiRouter do end end + scope "/shibarium" do + if System.get_env("CHAIN_TYPE") == "shibarium" do + get("/deposits", V2.ShibariumController, :deposits) + get("/deposits/count", V2.ShibariumController, :deposits_count) + get("/withdrawals", V2.ShibariumController, :withdrawals) + get("/withdrawals/count", V2.ShibariumController, :withdrawals_count) + end + end + scope "/withdrawals" do get("/", V2.WithdrawalController, :withdrawals_list) get("/counters", V2.WithdrawalController, :withdrawals_counters) diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index 8bb9d4d0eb2c..e988dec2bf54 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -649,6 +649,16 @@ defmodule BlockScoutWeb.Chain do %{"id" => msg_id} end + # clause for Shibarium Deposits + defp paging_params(%{l1_block_number: block_number}) do + %{"block_number" => block_number} + end + + # clause for Shibarium Withdrawals + defp paging_params(%{l2_block_number: block_number}) do + %{"block_number" => block_number} + end + @spec paging_params_with_fiat_value(CurrentTokenBalance.t()) :: %{ required(String.t()) => Decimal.t() | non_neg_integer() | nil } diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/shibarium_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/shibarium_controller.ex new file mode 100644 index 000000000000..9a57424fece2 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/shibarium_controller.ex @@ -0,0 +1,79 @@ +defmodule BlockScoutWeb.API.V2.ShibariumController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Chain, + only: [ + next_page_params: 3, + paging_options: 1, + split_list_by_page: 1 + ] + + alias Explorer.Chain.Cache.ShibariumCounter + alias Explorer.Chain.Shibarium.Reader + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + @spec deposits(Plug.Conn.t(), map()) :: Plug.Conn.t() + def deposits(conn, params) do + {deposits, next_page} = + params + |> paging_options() + |> Keyword.put(:api?, true) + |> Reader.deposits() + |> split_list_by_page() + + next_page_params = next_page_params(next_page, deposits, params) + + conn + |> put_status(200) + |> render(:shibarium_deposits, %{ + deposits: deposits, + next_page_params: next_page_params + }) + end + + @spec deposits_count(Plug.Conn.t(), map()) :: Plug.Conn.t() + def deposits_count(conn, _params) do + count = + case ShibariumCounter.deposits_count(api?: true) do + 0 -> Reader.deposits_count(api?: true) + value -> value + end + + conn + |> put_status(200) + |> render(:shibarium_items_count, %{count: count}) + end + + @spec withdrawals(Plug.Conn.t(), map()) :: Plug.Conn.t() + def withdrawals(conn, params) do + {withdrawals, next_page} = + params + |> paging_options() + |> Keyword.put(:api?, true) + |> Reader.withdrawals() + |> split_list_by_page() + + next_page_params = next_page_params(next_page, withdrawals, params) + + conn + |> put_status(200) + |> render(:shibarium_withdrawals, %{ + withdrawals: withdrawals, + next_page_params: next_page_params + }) + end + + @spec withdrawals_count(Plug.Conn.t(), map()) :: Plug.Conn.t() + def withdrawals_count(conn, _params) do + count = + case ShibariumCounter.withdrawals_count(api?: true) do + 0 -> Reader.withdrawals_count(api?: true) + value -> value + end + + conn + |> put_status(200) + |> render(:shibarium_items_count, %{count: count}) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/shibarium_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/shibarium_view.ex new file mode 100644 index 000000000000..d8f273fe62c4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/shibarium_view.ex @@ -0,0 +1,46 @@ +defmodule BlockScoutWeb.API.V2.ShibariumView do + use BlockScoutWeb, :view + + @spec render(String.t(), map()) :: map() + def render("shibarium_deposits.json", %{ + deposits: deposits, + next_page_params: next_page_params + }) do + %{ + items: + Enum.map(deposits, fn deposit -> + %{ + "l1_block_number" => deposit.l1_block_number, + "l1_transaction_hash" => deposit.l1_transaction_hash, + "l2_transaction_hash" => deposit.l2_transaction_hash, + "user" => deposit.user, + "timestamp" => deposit.timestamp + } + end), + next_page_params: next_page_params + } + end + + def render("shibarium_withdrawals.json", %{ + withdrawals: withdrawals, + next_page_params: next_page_params + }) do + %{ + items: + Enum.map(withdrawals, fn withdrawal -> + %{ + "l2_block_number" => withdrawal.l2_block_number, + "l2_transaction_hash" => withdrawal.l2_transaction_hash, + "l1_transaction_hash" => withdrawal.l1_transaction_hash, + "user" => withdrawal.user, + "timestamp" => withdrawal.timestamp + } + end), + next_page_params: next_page_params + } + end + + def render("shibarium_items_count.json", %{count: count}) do + count + end +end diff --git a/apps/block_scout_web/test/test_helper.exs b/apps/block_scout_web/test/test_helper.exs index b92840d3a20c..8a9fe648fed4 100644 --- a/apps/block_scout_web/test/test_helper.exs +++ b/apps/block_scout_web/test/test_helper.exs @@ -29,6 +29,7 @@ Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Account, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonEdge, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonZkevm, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.RSK, :manual) +Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Shibarium, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, :manual) Absinthe.Test.prime(BlockScoutWeb.Schema) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex index 74d37fe187df..3636d4ff4680 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex @@ -479,11 +479,15 @@ defmodule EthereumJSONRPC do @doc """ Converts `t:non_neg_integer/0` to `t:quantity/0` """ - @spec integer_to_quantity(non_neg_integer) :: quantity + @spec integer_to_quantity(non_neg_integer | binary) :: quantity def integer_to_quantity(integer) when is_integer(integer) and integer >= 0 do "0x" <> Integer.to_string(integer, 16) end + def integer_to_quantity(integer) when is_binary(integer) do + integer + end + @doc """ A request payload for a JSONRPC. """ diff --git a/apps/explorer/config/dev.exs b/apps/explorer/config/dev.exs index 0aa303a2bfab..8996b7e72cbe 100644 --- a/apps/explorer/config/dev.exs +++ b/apps/explorer/config/dev.exs @@ -19,6 +19,8 @@ config :explorer, Explorer.Repo.PolygonZkevm, timeout: :timer.seconds(80) config :explorer, Explorer.Repo.RSK, timeout: :timer.seconds(80) +config :explorer, Explorer.Repo.Shibarium, timeout: :timer.seconds(80) + config :explorer, Explorer.Repo.Suave, timeout: :timer.seconds(80) config :explorer, Explorer.Tracer, env: "dev", disabled?: true diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs index e14afe322c04..e8184837df98 100644 --- a/apps/explorer/config/prod.exs +++ b/apps/explorer/config/prod.exs @@ -28,6 +28,10 @@ config :explorer, Explorer.Repo.RSK, prepare: :unnamed, timeout: :timer.seconds(60) +config :explorer, Explorer.Repo.Shibarium, + prepare: :unnamed, + timeout: :timer.seconds(60) + config :explorer, Explorer.Repo.Suave, prepare: :unnamed, timeout: :timer.seconds(60) diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index b292598e17ff..0da1447c6e26 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -43,7 +43,13 @@ config :explorer, Explorer.Repo.Account, queue_target: 1000, log: false -for repo <- [Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Suave] do +for repo <- [ + Explorer.Repo.PolygonEdge, + Explorer.Repo.PolygonZkevm, + Explorer.Repo.RSK, + Explorer.Repo.Shibarium, + Explorer.Repo.Suave + ] do config :explorer, repo, database: "explorer_test", hostname: "localhost", diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 52b196489d07..85a94c6a82b3 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -138,7 +138,13 @@ defmodule Explorer.Application do defp repos_by_chain_type do if Mix.env() == :test do - [Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Suave] + [ + Explorer.Repo.PolygonEdge, + Explorer.Repo.PolygonZkevm, + Explorer.Repo.RSK, + Explorer.Repo.Shibarium, + Explorer.Repo.Suave + ] else [] end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 1e7d51f3cacc..945e37b35ddf 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -2174,6 +2174,16 @@ defmodule Explorer.Chain do end end + @spec increment_last_fetched_counter(binary(), non_neg_integer()) :: {non_neg_integer(), nil} + def increment_last_fetched_counter(type, value) do + query = + from(counter in LastFetchedCounter, + where: counter.counter_type == ^type + ) + + Repo.update_all(query, [inc: [value: value]], timeout: :infinity) + end + @spec upsert_last_fetched_counter(map()) :: {:ok, LastFetchedCounter.t()} | {:error, Ecto.Changeset.t()} def upsert_last_fetched_counter(params) do changeset = LastFetchedCounter.changeset(%LastFetchedCounter{}, params) diff --git a/apps/explorer/lib/explorer/chain/cache/shibarium_counter.ex b/apps/explorer/lib/explorer/chain/cache/shibarium_counter.ex new file mode 100644 index 000000000000..6a5ed7780f25 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/cache/shibarium_counter.ex @@ -0,0 +1,58 @@ +defmodule Explorer.Chain.Cache.ShibariumCounter do + @moduledoc """ + Caches the number of deposits and withdrawals for Shibarium Bridge. + """ + + alias Explorer.Chain + + @deposits_counter_type "shibarium_deposits_counter" + @withdrawals_counter_type "shibarium_withdrawals_counter" + + @doc """ + Fetches the cached deposits count from the `last_fetched_counters` table. + """ + def deposits_count(options \\ []) do + Chain.get_last_fetched_counter(@deposits_counter_type, options) + end + + @doc """ + Fetches the cached withdrawals count from the `last_fetched_counters` table. + """ + def withdrawals_count(options \\ []) do + Chain.get_last_fetched_counter(@withdrawals_counter_type, options) + end + + @doc """ + Stores or increments the current deposits count in the `last_fetched_counters` table. + """ + def deposits_count_save(count, just_increment \\ false) do + if just_increment do + Chain.increment_last_fetched_counter( + @deposits_counter_type, + count + ) + else + Chain.upsert_last_fetched_counter(%{ + counter_type: @deposits_counter_type, + value: count + }) + end + end + + @doc """ + Stores or increments the current withdrawals count in the `last_fetched_counters` table. + """ + def withdrawals_count_save(count, just_increment \\ false) do + if just_increment do + Chain.increment_last_fetched_counter( + @withdrawals_counter_type, + count + ) + else + Chain.upsert_last_fetched_counter(%{ + counter_type: @withdrawals_counter_type, + value: count + }) + end + end +end diff --git a/apps/explorer/lib/explorer/chain/import.ex b/apps/explorer/lib/explorer/chain/import.ex index 9372a55c01e8..149649cd03c7 100644 --- a/apps/explorer/lib/explorer/chain/import.ex +++ b/apps/explorer/lib/explorer/chain/import.ex @@ -19,7 +19,8 @@ defmodule Explorer.Chain.Import do ] # in order so that foreign keys are inserted before being referenced - @runners Enum.flat_map(@stages, fn stage -> stage.runners() end) + @configured_runners Enum.flat_map(@stages, fn stage -> stage.runners() end) + @all_runners Enum.flat_map(@stages, fn stage -> stage.all_runners() end) quoted_runner_option_value = quote do @@ -27,7 +28,7 @@ defmodule Explorer.Chain.Import do end quoted_runner_options = - for runner <- @runners do + for runner <- @all_runners do quoted_key = quote do optional(unquote(runner.option_key())) @@ -43,7 +44,7 @@ defmodule Explorer.Chain.Import do } quoted_runner_imported = - for runner <- @runners do + for runner <- @all_runners do quoted_key = quote do optional(unquote(runner.option_key())) @@ -68,7 +69,7 @@ defmodule Explorer.Chain.Import do # milliseconds @transaction_timeout :timer.minutes(4) - @imported_table_rows @runners + @imported_table_rows @all_runners |> Stream.map(&Map.put(&1.imported_table_row(), :key, &1.option_key())) |> Enum.map_join("\n", fn %{ key: key, @@ -77,7 +78,7 @@ defmodule Explorer.Chain.Import do } -> "| `#{inspect(key)}` | `#{value_type}` | #{value_description} |" end) - @runner_options_doc Enum.map_join(@runners, fn runner -> + @runner_options_doc Enum.map_join(@all_runners, fn runner -> ecto_schema_module = runner.ecto_schema_module() """ @@ -187,7 +188,8 @@ defmodule Explorer.Chain.Import do local_options = Map.drop(options, @global_options) {reverse_runner_options_pairs, unknown_options} = - Enum.reduce(@runners, {[], local_options}, fn runner, {acc_runner_options_pairs, unknown_options} = acc -> + Enum.reduce(@configured_runners, {[], local_options}, fn runner, + {acc_runner_options_pairs, unknown_options} = acc -> option_key = runner.option_key() case local_options do diff --git a/apps/explorer/lib/explorer/chain/import/runner/shibarium/bridge_operations.ex b/apps/explorer/lib/explorer/chain/import/runner/shibarium/bridge_operations.ex new file mode 100644 index 000000000000..b7cd680ae231 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/shibarium/bridge_operations.ex @@ -0,0 +1,119 @@ +defmodule Explorer.Chain.Import.Runner.Shibarium.BridgeOperations do + @moduledoc """ + Bulk imports `t:Explorer.Chain.Shibarium.Bridge.t/0`. + """ + + require Ecto.Query + + import Ecto.Query, only: [from: 2] + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.Shibarium.Bridge, as: ShibariumBridge + alias Explorer.Prometheus.Instrumenter + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [ShibariumBridge.t()] + + @impl Import.Runner + def ecto_schema_module, do: ShibariumBridge + + @impl Import.Runner + def option_key, do: :shibarium_bridge_operations + + @impl Import.Runner + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + Multi.run(multi, :insert_shibarium_bridge_operations, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :shibarium_bridge_operations, + :shibarium_bridge_operations + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [ShibariumBridge.t()]} + | {:error, [Changeset.t()]} + def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) + + # Enforce ShibariumBridge ShareLocks order (see docs: sharelock.md) + ordered_changes_list = + Enum.sort_by(changes_list, &{&1.operation_hash, &1.l1_transaction_hash, &1.l2_transaction_hash}) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + conflict_target: [:operation_hash, :l1_transaction_hash, :l2_transaction_hash], + on_conflict: on_conflict, + for: ShibariumBridge, + returning: true, + timeout: timeout, + timestamps: timestamps + ) + + {:ok, inserted} + end + + defp default_on_conflict do + from( + op in ShibariumBridge, + update: [ + set: [ + # Don't update `operation_hash` as it is part of the composite primary key and used for the conflict target + # Don't update `l1_transaction_hash` as it is part of the composite primary key and used for the conflict target + # Don't update `l2_transaction_hash` as it is part of the composite primary key and used for the conflict target + # Don't update `operation_type` as it is not changed + user: fragment("EXCLUDED.user"), + amount_or_id: fragment("EXCLUDED.amount_or_id"), + erc1155_ids: fragment("EXCLUDED.erc1155_ids"), + erc1155_amounts: fragment("EXCLUDED.erc1155_amounts"), + l1_block_number: fragment("EXCLUDED.l1_block_number"), + l2_block_number: fragment("EXCLUDED.l2_block_number"), + token_type: fragment("EXCLUDED.token_type"), + timestamp: fragment("EXCLUDED.timestamp"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", op.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", op.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.user, EXCLUDED.amount_or_id, EXCLUDED.erc1155_ids, EXCLUDED.erc1155_amounts, EXCLUDED.operation_type, EXCLUDED.l1_block_number, EXCLUDED.l2_block_number, EXCLUDED.token_type, EXCLUDED.timestamp) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?)", + op.user, + op.amount_or_id, + op.erc1155_ids, + op.erc1155_amounts, + op.operation_type, + op.l1_block_number, + op.l2_block_number, + op.token_type, + op.timestamp + ) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/stage.ex b/apps/explorer/lib/explorer/chain/import/stage.ex index dcd3da1cc13e..ed000760ca9b 100644 --- a/apps/explorer/lib/explorer/chain/import/stage.ex +++ b/apps/explorer/lib/explorer/chain/import/stage.ex @@ -14,10 +14,18 @@ defmodule Explorer.Chain.Import.Stage do @type runner_to_changes_list :: %{Runner.t() => Runner.changes_list()} @doc """ - The runners consumed by this stage in `c:multis/0`. The list should be in the order that the runners are executed. + The configured runners consumed by this stage in `c:multis/0`. + The list should be in the order that the runners are executed and depends on chain type. """ @callback runners() :: [Runner.t(), ...] + @doc """ + Returns a list of all possible runners provided by the module. + This list is intended to include all runners, irrespective of chain type or + other configuration options. + """ + @callback all_runners() :: [Runner.t(), ...] + @doc """ Chunks `changes_list` into 1 or more `t:Ecto.Multi.t/0` that can be run in separate transactions. diff --git a/apps/explorer/lib/explorer/chain/import/stage/addresses_blocks_coin_balances.ex b/apps/explorer/lib/explorer/chain/import/stage/addresses_blocks_coin_balances.ex index bdd8ae478e84..cfa42beaf5f6 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/addresses_blocks_coin_balances.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/addresses_blocks_coin_balances.ex @@ -19,6 +19,9 @@ defmodule Explorer.Chain.Import.Stage.AddressesBlocksCoinBalances do @impl Stage def runners, do: [@addresses_runner | @rest_runners] + @impl Stage + def all_runners, do: runners() + @addresses_chunk_size 50 @impl Stage diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_following.ex b/apps/explorer/lib/explorer/chain/import/stage/block_following.ex index 2a533c1b680b..c8ed699d2af7 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_following.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_following.ex @@ -17,6 +17,10 @@ defmodule Explorer.Chain.Import.Stage.BlockFollowing do Runner.TokenInstances ] + @impl Stage + def all_runners, + do: runners() + @impl Stage def multis(runner_to_changes_list, options) do {final_multi, final_remaining_runner_to_changes_list} = diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_pending.ex b/apps/explorer/lib/explorer/chain/import/stage/block_pending.ex index 824a4dc3ce0a..6dccdfdf5d10 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_pending.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_pending.ex @@ -15,6 +15,10 @@ defmodule Explorer.Chain.Import.Stage.BlockPending do Runner.InternalTransactions ] + @impl Stage + def all_runners, + do: runners() + @impl Stage def multis(runner_to_changes_list, options) do {final_multi, final_remaining_runner_to_changes_list} = diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex index 142a2326545a..feb13329e394 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex @@ -18,31 +18,45 @@ defmodule Explorer.Chain.Import.Stage.BlockReferencing do Runner.Withdrawals ] + @polygon_edge_runners [ + Runner.PolygonEdge.Deposits, + Runner.PolygonEdge.DepositExecutes, + Runner.PolygonEdge.Withdrawals, + Runner.PolygonEdge.WithdrawalExits + ] + + @polygon_zkevm_runners [ + Runner.Zkevm.LifecycleTransactions, + Runner.Zkevm.TransactionBatches, + Runner.Zkevm.BatchTransactions + ] + + @shibarium_runners [ + Runner.Shibarium.BridgeOperations + ] + @impl Stage def runners do case System.get_env("CHAIN_TYPE") do "polygon_edge" -> - @default_runners ++ - [ - Runner.PolygonEdge.Deposits, - Runner.PolygonEdge.DepositExecutes, - Runner.PolygonEdge.Withdrawals, - Runner.PolygonEdge.WithdrawalExits - ] + @default_runners ++ @polygon_edge_runners "polygon_zkevm" -> - @default_runners ++ - [ - Runner.Zkevm.LifecycleTransactions, - Runner.Zkevm.TransactionBatches, - Runner.Zkevm.BatchTransactions - ] + @default_runners ++ @polygon_zkevm_runners + + "shibarium" -> + @default_runners ++ @shibarium_runners _ -> @default_runners end end + @impl Stage + def all_runners do + @default_runners ++ @polygon_edge_runners ++ @polygon_zkevm_runners ++ @shibarium_runners + end + @impl Stage def multis(runner_to_changes_list, options) do {final_multi, final_remaining_runner_to_changes_list} = diff --git a/apps/explorer/lib/explorer/chain/shibarium/bridge.ex b/apps/explorer/lib/explorer/chain/shibarium/bridge.ex new file mode 100644 index 000000000000..9cc123bfc2db --- /dev/null +++ b/apps/explorer/lib/explorer/chain/shibarium/bridge.ex @@ -0,0 +1,85 @@ +defmodule Explorer.Chain.Shibarium.Bridge do + @moduledoc "Models Shibarium Bridge operation." + + use Explorer.Schema + + alias Explorer.Chain.{ + Address, + Hash, + Transaction + } + + @optional_attrs ~w(amount_or_id erc1155_ids erc1155_amounts l1_transaction_hash l1_block_number l2_transaction_hash l2_block_number timestamp)a + + @required_attrs ~w(user operation_hash operation_type token_type)a + + @allowed_attrs @optional_attrs ++ @required_attrs + + @typedoc """ + * `user_address` - address of the user that initiated operation + * `user` - foreign key of `user_address` + * `amount_or_id` - amount of the operation or NTF id (in case of ERC-721 token) + * `erc1155_ids` - an array of ERC-1155 token ids (when batch ERC-1155 token transfer) + * `erc1155_amounts` - an array of corresponding ERC-1155 token amounts (when batch ERC-1155 token transfer) + * `l1_transaction_hash` - transaction hash for L1 side + * `l1_block_number` - block number of `l1_transaction` + * `l2_transaction` - transaction hash for L2 side + * `l2_transaction_hash` - foreign key of `l2_transaction` + * `l2_block_number` - block number of `l2_transaction` + * `operation_hash` - keccak256 hash of the operation calculated as follows: ExKeccak.hash_256(user, amount_or_id, erc1155_ids, erc1155_amounts, operation_id) + * `operation_type` - `deposit` or `withdrawal` + * `token_type` - `bone` or `eth` or `other` + * `timestamp` - timestamp of the operation block (L1 block for deposit, L2 block - for withdrawal) + """ + @type t :: %__MODULE__{ + user_address: %Ecto.Association.NotLoaded{} | Address.t(), + user: Hash.Address.t(), + amount_or_id: Decimal.t() | nil, + erc1155_ids: [non_neg_integer()] | nil, + erc1155_amounts: [Decimal.t()] | nil, + l1_transaction_hash: Hash.t(), + l1_block_number: non_neg_integer() | nil, + l2_transaction: %Ecto.Association.NotLoaded{} | Transaction.t() | nil, + l2_transaction_hash: Hash.t(), + l2_block_number: non_neg_integer() | nil, + operation_hash: Hash.t(), + operation_type: String.t(), + token_type: String.t(), + timestamp: DateTime.t(), + inserted_at: DateTime.t(), + updated_at: DateTime.t() + } + + @primary_key false + schema "shibarium_bridge" do + belongs_to(:user_address, Address, foreign_key: :user, references: :hash, type: Hash.Address) + field(:amount_or_id, :decimal) + field(:erc1155_ids, {:array, :decimal}) + field(:erc1155_amounts, {:array, :decimal}) + field(:operation_hash, Hash.Full, primary_key: true) + field(:operation_type, Ecto.Enum, values: [:deposit, :withdrawal]) + field(:l1_transaction_hash, Hash.Full, primary_key: true) + field(:l1_block_number, :integer) + + belongs_to(:l2_transaction, Transaction, + foreign_key: :l2_transaction_hash, + references: :hash, + type: Hash.Full, + primary_key: true + ) + + field(:l2_block_number, :integer) + field(:token_type, Ecto.Enum, values: [:bone, :eth, :other]) + field(:timestamp, :utc_datetime_usec) + + timestamps() + end + + @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() + def changeset(%__MODULE__{} = module, attrs \\ %{}) do + module + |> cast(attrs, @allowed_attrs) + |> validate_required(@required_attrs) + |> unique_constraint([:operation_hash, :l1_transaction_hash, :l2_transaction_hash]) + end +end diff --git a/apps/explorer/lib/explorer/chain/shibarium/reader.ex b/apps/explorer/lib/explorer/chain/shibarium/reader.ex new file mode 100644 index 000000000000..f452c5dced7f --- /dev/null +++ b/apps/explorer/lib/explorer/chain/shibarium/reader.ex @@ -0,0 +1,108 @@ +defmodule Explorer.Chain.Shibarium.Reader do + @moduledoc "Contains read functions for Shibarium modules." + + import Ecto.Query, + only: [ + from: 2, + limit: 2 + ] + + import Explorer.Chain, only: [default_paging_options: 0, select_repo: 1] + + alias Explorer.Chain.Shibarium.Bridge + alias Explorer.PagingOptions + + @doc """ + Returns a list of completed Shibarium deposits to display them in UI. + """ + @spec deposits(list()) :: list() + def deposits(options \\ []) do + paging_options = Keyword.get(options, :paging_options, default_paging_options()) + + base_query = + from( + sb in Bridge, + where: sb.operation_type == :deposit and not is_nil(sb.l1_block_number) and not is_nil(sb.l2_block_number), + select: %{ + l1_block_number: sb.l1_block_number, + l1_transaction_hash: sb.l1_transaction_hash, + l2_transaction_hash: sb.l2_transaction_hash, + user: sb.user, + timestamp: sb.timestamp + }, + order_by: [desc: sb.l1_block_number] + ) + + base_query + |> page_deposits(paging_options) + |> limit(^paging_options.page_size) + |> select_repo(options).all() + end + + @doc """ + Returns a total number of completed Shibarium deposits. + """ + @spec deposits_count(list()) :: term() | nil + def deposits_count(options \\ []) do + query = + from( + sb in Bridge, + where: sb.operation_type == :deposit and not is_nil(sb.l1_block_number) and not is_nil(sb.l2_block_number) + ) + + select_repo(options).aggregate(query, :count, timeout: :infinity) + end + + @doc """ + Returns a list of completed Shibarium withdrawals to display them in UI. + """ + @spec withdrawals(list()) :: list() + def withdrawals(options \\ []) do + paging_options = Keyword.get(options, :paging_options, default_paging_options()) + + base_query = + from( + sb in Bridge, + where: sb.operation_type == :withdrawal and not is_nil(sb.l1_block_number) and not is_nil(sb.l2_block_number), + select: %{ + l2_block_number: sb.l2_block_number, + l2_transaction_hash: sb.l2_transaction_hash, + l1_transaction_hash: sb.l1_transaction_hash, + user: sb.user, + timestamp: sb.timestamp + }, + order_by: [desc: sb.l2_block_number] + ) + + base_query + |> page_withdrawals(paging_options) + |> limit(^paging_options.page_size) + |> select_repo(options).all() + end + + @doc """ + Returns a total number of completed Shibarium withdrawals. + """ + @spec withdrawals_count(list()) :: term() | nil + def withdrawals_count(options \\ []) do + query = + from( + sb in Bridge, + where: sb.operation_type == :withdrawal and not is_nil(sb.l1_block_number) and not is_nil(sb.l2_block_number) + ) + + select_repo(options).aggregate(query, :count, timeout: :infinity) + end + + defp page_deposits(query, %PagingOptions{key: nil}), do: query + + defp page_deposits(query, %PagingOptions{key: {block_number}}) do + from(item in query, where: item.l1_block_number < ^block_number) + end + + defp page_withdrawals(query, %PagingOptions{key: nil}), do: query + + defp page_withdrawals(query, %PagingOptions{key: {block_number}}) do + from(item in query, where: item.l2_block_number < ^block_number) + end +end diff --git a/apps/explorer/lib/explorer/repo.ex b/apps/explorer/lib/explorer/repo.ex index c4d0c8f3f489..fd0ad4778b8b 100644 --- a/apps/explorer/lib/explorer/repo.ex +++ b/apps/explorer/lib/explorer/repo.ex @@ -137,21 +137,7 @@ defmodule Explorer.Repo do read_only: true def init(_, opts) do - db_url = Application.get_env(:explorer, Explorer.Repo.Replica1)[:url] - repo_conf = Application.get_env(:explorer, Explorer.Repo.Replica1) - - merged = - %{url: db_url} - |> ConfigHelper.get_db_config() - |> Keyword.merge(repo_conf, fn - _key, v1, nil -> v1 - _key, nil, v2 -> v2 - _, _, v2 -> v2 - end) - - Application.put_env(:explorer, Explorer.Repo.Replica1, merged) - - {:ok, Keyword.put(opts, :url, db_url)} + ConfigHelper.init_repo_module(__MODULE__, opts) end end @@ -161,21 +147,7 @@ defmodule Explorer.Repo do adapter: Ecto.Adapters.Postgres def init(_, opts) do - db_url = Application.get_env(:explorer, Explorer.Repo.Account)[:url] - repo_conf = Application.get_env(:explorer, Explorer.Repo.Account) - - merged = - %{url: db_url} - |> ConfigHelper.get_db_config() - |> Keyword.merge(repo_conf, fn - _key, v1, nil -> v1 - _key, nil, v2 -> v2 - _, _, v2 -> v2 - end) - - Application.put_env(:explorer, Explorer.Repo.Account, merged) - - {:ok, Keyword.put(opts, :url, db_url)} + ConfigHelper.init_repo_module(__MODULE__, opts) end end @@ -185,21 +157,7 @@ defmodule Explorer.Repo do adapter: Ecto.Adapters.Postgres def init(_, opts) do - db_url = Application.get_env(:explorer, Explorer.Repo.PolygonEdge)[:url] - repo_conf = Application.get_env(:explorer, Explorer.Repo.PolygonEdge) - - merged = - %{url: db_url} - |> ConfigHelper.get_db_config() - |> Keyword.merge(repo_conf, fn - _key, v1, nil -> v1 - _key, nil, v2 -> v2 - _, _, v2 -> v2 - end) - - Application.put_env(:explorer, Explorer.Repo.PolygonEdge, merged) - - {:ok, Keyword.put(opts, :url, db_url)} + ConfigHelper.init_repo_module(__MODULE__, opts) end end @@ -209,21 +167,7 @@ defmodule Explorer.Repo do adapter: Ecto.Adapters.Postgres def init(_, opts) do - db_url = Application.get_env(:explorer, __MODULE__)[:url] - repo_conf = Application.get_env(:explorer, __MODULE__) - - merged = - %{url: db_url} - |> ConfigHelper.get_db_config() - |> Keyword.merge(repo_conf, fn - _key, v1, nil -> v1 - _key, nil, v2 -> v2 - _, _, v2 -> v2 - end) - - Application.put_env(:explorer, __MODULE__, merged) - - {:ok, Keyword.put(opts, :url, db_url)} + ConfigHelper.init_repo_module(__MODULE__, opts) end end @@ -233,21 +177,17 @@ defmodule Explorer.Repo do adapter: Ecto.Adapters.Postgres def init(_, opts) do - db_url = Application.get_env(:explorer, __MODULE__)[:url] - repo_conf = Application.get_env(:explorer, __MODULE__) - - merged = - %{url: db_url} - |> ConfigHelper.get_db_config() - |> Keyword.merge(repo_conf, fn - _key, v1, nil -> v1 - _key, nil, v2 -> v2 - _, _, v2 -> v2 - end) + ConfigHelper.init_repo_module(__MODULE__, opts) + end + end - Application.put_env(:explorer, __MODULE__, merged) + defmodule Shibarium do + use Ecto.Repo, + otp_app: :explorer, + adapter: Ecto.Adapters.Postgres - {:ok, Keyword.put(opts, :url, db_url)} + def init(_, opts) do + ConfigHelper.init_repo_module(__MODULE__, opts) end end @@ -257,21 +197,7 @@ defmodule Explorer.Repo do adapter: Ecto.Adapters.Postgres def init(_, opts) do - db_url = Application.get_env(:explorer, __MODULE__)[:url] - repo_conf = Application.get_env(:explorer, __MODULE__) - - merged = - %{url: db_url} - |> ConfigHelper.get_db_config() - |> Keyword.merge(repo_conf, fn - _key, v1, nil -> v1 - _key, nil, v2 -> v2 - _, _, v2 -> v2 - end) - - Application.put_env(:explorer, __MODULE__, merged) - - {:ok, Keyword.put(opts, :url, db_url)} + ConfigHelper.init_repo_module(__MODULE__, opts) end end end diff --git a/apps/explorer/lib/explorer/repo/config_helper.ex b/apps/explorer/lib/explorer/repo/config_helper.ex index abfe46c958ef..e1edad2bc230 100644 --- a/apps/explorer/lib/explorer/repo/config_helper.ex +++ b/apps/explorer/lib/explorer/repo/config_helper.ex @@ -32,6 +32,24 @@ defmodule Explorer.Repo.ConfigHelper do def get_api_db_url, do: System.get_env("DATABASE_READ_ONLY_API_URL") || System.get_env("DATABASE_URL") + def init_repo_module(module, opts) do + db_url = Application.get_env(:explorer, module)[:url] + repo_conf = Application.get_env(:explorer, module) + + merged = + %{url: db_url} + |> get_db_config() + |> Keyword.merge(repo_conf, fn + _key, v1, nil -> v1 + _key, nil, v2 -> v2 + _, _, v2 -> v2 + end) + + Application.put_env(:explorer, module, merged) + + {:ok, Keyword.put(opts, :url, db_url)} + end + def ssl_enabled?, do: String.equivalent?(System.get_env("ECTO_USE_SSL") || "true", "true") defp extract_parameters(empty) when empty == nil or empty == "", do: [] diff --git a/apps/explorer/priv/shibarium/migrations/20231024091228_add_bridge_table.exs b/apps/explorer/priv/shibarium/migrations/20231024091228_add_bridge_table.exs new file mode 100644 index 000000000000..4fb2fe71c99a --- /dev/null +++ b/apps/explorer/priv/shibarium/migrations/20231024091228_add_bridge_table.exs @@ -0,0 +1,34 @@ +defmodule Explorer.Repo.Shibarium.Migrations.AddBridgeTable do + use Ecto.Migration + + def change do + execute( + "CREATE TYPE shibarium_bridge_operation_type AS ENUM ('deposit', 'withdrawal')", + "DROP TYPE shibarium_bridge_operation_type" + ) + + execute( + "CREATE TYPE shibarium_bridge_token_type AS ENUM ('bone', 'eth', 'other')", + "DROP TYPE shibarium_bridge_token_type" + ) + + create table(:shibarium_bridge, primary_key: false) do + add(:user, :bytea, null: false) + add(:amount_or_id, :numeric, precision: 100, null: true) + add(:erc1155_ids, {:array, :numeric}, precision: 78, scale: 0, null: true) + add(:erc1155_amounts, {:array, :decimal}, null: true) + add(:operation_hash, :bytea, primary_key: true) + add(:operation_type, :shibarium_bridge_operation_type, null: false) + add(:l1_transaction_hash, :bytea, primary_key: true) + add(:l1_block_number, :bigint, null: true) + add(:l2_transaction_hash, :bytea, primary_key: true) + add(:l2_block_number, :bigint, null: true) + add(:token_type, :shibarium_bridge_token_type, null: false) + add(:timestamp, :"timestamp without time zone", null: true) + timestamps(null: false, type: :utc_datetime_usec) + end + + create(index(:shibarium_bridge, [:l1_block_number, :operation_type])) + create(index(:shibarium_bridge, [:l2_block_number, :operation_type])) + end +end diff --git a/apps/explorer/test/support/data_case.ex b/apps/explorer/test/support/data_case.ex index f93e1bcf7aa7..579ea23a096a 100644 --- a/apps/explorer/test/support/data_case.ex +++ b/apps/explorer/test/support/data_case.ex @@ -38,6 +38,7 @@ defmodule Explorer.DataCase do :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.PolygonEdge) :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.PolygonZkevm) :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.RSK) + :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.Shibarium) :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.Suave) unless tags[:async] do @@ -46,6 +47,7 @@ defmodule Explorer.DataCase do Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonEdge, {:shared, self()}) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonZkevm, {:shared, self()}) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.RSK, {:shared, self()}) + Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Shibarium, {:shared, self()}) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, {:shared, self()}) end diff --git a/apps/explorer/test/test_helper.exs b/apps/explorer/test/test_helper.exs index 938420e729a0..fd2de129984e 100644 --- a/apps/explorer/test/test_helper.exs +++ b/apps/explorer/test/test_helper.exs @@ -16,6 +16,7 @@ Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Account, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonEdge, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonZkevm, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.RSK, :auto) +Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Shibarium, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, :auto) Mox.defmock(Explorer.ExchangeRates.Source.TestSource, for: Explorer.ExchangeRates.Source) diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 11261231247d..7065bf8c3da4 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -43,6 +43,8 @@ defmodule Indexer.Block.Fetcher do alias Indexer.Transform.PolygonEdge.{DepositExecutes, Withdrawals} + alias Indexer.Transform.Shibarium.Bridge, as: ShibariumBridge + alias Indexer.Transform.Blocks, as: TransformBlocks @type address_hash_to_fetched_balance_block_number :: %{String.t() => Block.block_number()} @@ -150,6 +152,11 @@ defmodule Indexer.Block.Fetcher do do: DepositExecutes.parse(logs), else: [] ), + shibarium_bridge_operations = + if(callback_module == Indexer.Block.Realtime.Fetcher, + do: ShibariumBridge.parse(blocks, transactions_with_receipts, logs), + else: [] + ), %FetchedBeneficiaries{params_set: beneficiary_params_set, errors: beneficiaries_errors} = fetch_beneficiaries(blocks, transactions_with_receipts, json_rpc_named_arguments), addresses = @@ -158,6 +165,7 @@ defmodule Indexer.Block.Fetcher do blocks: blocks, logs: logs, mint_transfers: mint_transfers, + shibarium_bridge_operations: shibarium_bridge_operations, token_transfers: token_transfers, transactions: transactions_with_receipts, transaction_actions: transaction_actions, @@ -196,12 +204,18 @@ defmodule Indexer.Block.Fetcher do token_instances: %{params: token_instances} }, import_options = - (if Application.get_env(:explorer, :chain_type) == "polygon_edge" do - basic_import_options - |> Map.put_new(:polygon_edge_withdrawals, %{params: polygon_edge_withdrawals}) - |> Map.put_new(:polygon_edge_deposit_executes, %{params: polygon_edge_deposit_executes}) - else - basic_import_options + (case Application.get_env(:explorer, :chain_type) do + "polygon_edge" -> + basic_import_options + |> Map.put_new(:polygon_edge_withdrawals, %{params: polygon_edge_withdrawals}) + |> Map.put_new(:polygon_edge_deposit_executes, %{params: polygon_edge_deposit_executes}) + + "shibarium" -> + basic_import_options + |> Map.put_new(:shibarium_bridge_operations, %{params: shibarium_bridge_operations}) + + _ -> + basic_import_options end), {:ok, inserted} <- __MODULE__.import( diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index 9f989d017f96..bb8583d83d53 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -35,6 +35,7 @@ defmodule Indexer.Block.Realtime.Fetcher do alias Indexer.Block.Realtime.TaskSupervisor alias Indexer.Fetcher.{CoinBalance, CoinBalanceDailyUpdater} alias Indexer.Fetcher.PolygonEdge.{DepositExecute, Withdrawal} + alias Indexer.Fetcher.Shibarium.L2, as: ShibariumBridgeL2 alias Indexer.Prometheus alias Indexer.Transform.Addresses alias Timex.Duration @@ -287,6 +288,9 @@ defmodule Indexer.Block.Realtime.Fetcher do # we need to remove all rows from `polygon_edge_withdrawals` and `polygon_edge_deposit_executes` tables previously written starting from reorg block number remove_polygon_edge_assets_by_number(block_number_to_fetch) + # we need to remove all rows from `shibarium_bridge` table previously written starting from reorg block number + remove_shibarium_assets_by_number(block_number_to_fetch) + # give previous fetch attempt (for same block number) a chance to finish # before fetching again, to reduce block consensus mistakes :timer.sleep(@reorg_delay) @@ -306,6 +310,12 @@ defmodule Indexer.Block.Realtime.Fetcher do end end + defp remove_shibarium_assets_by_number(block_number_to_fetch) do + if Application.get_env(:explorer, :chain_type) == "shibarium" do + ShibariumBridgeL2.reorg_handle(block_number_to_fetch) + end + end + @decorate span(tracer: Tracer) defp do_fetch_and_import_block(block_number_to_fetch, block_fetcher, retry) do time_before = Timex.now() diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge.ex index 29cb94def706..387894825948 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge.ex @@ -11,11 +11,10 @@ defmodule Indexer.Fetcher.PolygonEdge do import Ecto.Query import EthereumJSONRPC, - only: [fetch_block_number_by_tag: 2, json_rpc: 2, integer_to_quantity: 1, quantity_to_integer: 1, request: 1] + only: [json_rpc: 2, integer_to_quantity: 1, request: 1] import Explorer.Helper, only: [parse_integer: 1] - alias EthereumJSONRPC.Block.ByNumber alias Explorer.Chain.Events.Publisher alias Explorer.{Chain, Repo} alias Indexer.{BoundQueue, Helper} @@ -92,7 +91,7 @@ defmodule Indexer.Fetcher.PolygonEdge do {:start_block_l1_valid, start_block_l1 <= last_l1_block_number || last_l1_block_number == 0}, json_rpc_named_arguments = json_rpc_named_arguments(polygon_edge_l1_rpc), {:ok, last_l1_tx} <- - get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments, 100_000_000), + Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments, 100_000_000), {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)}, {:ok, block_check_interval, last_safe_block} <- get_block_check_interval(json_rpc_named_arguments) do @@ -172,7 +171,8 @@ defmodule Indexer.Fetcher.PolygonEdge do {:start_block_l2_valid, true} <- {:start_block_l2_valid, (start_block_l2 <= last_l2_block_number || last_l2_block_number == 0) && start_block_l2 <= safe_block}, - {:ok, last_l2_tx} <- get_transaction_by_hash(last_l2_transaction_hash, json_rpc_named_arguments, 100_000_000), + {:ok, last_l2_tx} <- + Helper.get_transaction_by_hash(last_l2_transaction_hash, json_rpc_named_arguments, 100_000_000), {:l2_tx_not_found, false} <- {:l2_tx_not_found, !is_nil(last_l2_transaction_hash) && is_nil(last_l2_tx)} do Process.send(pid, :continue, []) @@ -226,7 +226,7 @@ defmodule Indexer.Fetcher.PolygonEdge do prev_latest: prev_latest } = state ) do - {:ok, latest} = get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + {:ok, latest} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) if latest < prev_latest do Logger.warning("Reorg detected: previous latest block ##{prev_latest}, current latest block ##{latest}.") @@ -268,7 +268,7 @@ defmodule Indexer.Fetcher.PolygonEdge do chunk_end = min(chunk_start + eth_get_logs_range_size - 1, end_block) if chunk_end >= chunk_start do - log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, "L1") + Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, "L1") {:ok, result} = get_logs( @@ -285,7 +285,7 @@ defmodule Indexer.Fetcher.PolygonEdge do |> calling_module.prepare_events(json_rpc_named_arguments) |> import_events(calling_module) - log_blocks_chunk_handling( + Helper.log_blocks_chunk_handling( chunk_start, chunk_end, start_block, @@ -306,7 +306,7 @@ defmodule Indexer.Fetcher.PolygonEdge do end) new_start_block = last_written_block + 1 - {:ok, new_end_block} = get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + {:ok, new_end_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) delay = if new_end_block == last_written_block do @@ -356,7 +356,7 @@ defmodule Indexer.Fetcher.PolygonEdge do min(chunk_start + eth_get_logs_range_size - 1, l2_block_end) end - log_blocks_chunk_handling(chunk_start, chunk_end, l2_block_start, l2_block_end, nil, "L2") + Helper.log_blocks_chunk_handling(chunk_start, chunk_end, l2_block_start, l2_block_end, nil, "L2") count = calling_module.find_and_save_entities( @@ -374,7 +374,7 @@ defmodule Indexer.Fetcher.PolygonEdge do "L2StateSynced" end - log_blocks_chunk_handling( + Helper.log_blocks_chunk_handling( chunk_start, chunk_end, l2_block_start, @@ -546,9 +546,9 @@ defmodule Indexer.Fetcher.PolygonEdge do first_block = max(last_safe_block - @block_check_interval_range_size, 1) with {:ok, first_block_timestamp} <- - get_block_timestamp_by_number(first_block, json_rpc_named_arguments, 100_000_000), + Helper.get_block_timestamp_by_number(first_block, json_rpc_named_arguments, 100_000_000), {:ok, last_safe_block_timestamp} <- - get_block_timestamp_by_number(last_safe_block, json_rpc_named_arguments, 100_000_000) do + Helper.get_block_timestamp_by_number(last_safe_block, json_rpc_named_arguments, 100_000_000) do block_check_interval = ceil((last_safe_block_timestamp - first_block_timestamp) / (last_safe_block - first_block) * 1000 / 2) @@ -560,46 +560,13 @@ defmodule Indexer.Fetcher.PolygonEdge do end end - @spec get_block_number_by_tag(binary(), list(), integer()) :: {:ok, non_neg_integer()} | {:error, atom()} - def get_block_number_by_tag(tag, json_rpc_named_arguments, retries \\ 3) do - error_message = &"Cannot fetch #{tag} block number. Error: #{inspect(&1)}" - repeated_call(&fetch_block_number_by_tag/2, [tag, json_rpc_named_arguments], error_message, retries) - end - - defp get_block_timestamp_by_number_inner(number, json_rpc_named_arguments) do - result = - %{id: 0, number: number} - |> ByNumber.request(false) - |> json_rpc(json_rpc_named_arguments) - - with {:ok, block} <- result, - false <- is_nil(block), - timestamp <- Map.get(block, "timestamp"), - false <- is_nil(timestamp) do - {:ok, quantity_to_integer(timestamp)} - else - {:error, message} -> - {:error, message} - - true -> - {:error, "RPC returned nil."} - end - end - - defp get_block_timestamp_by_number(number, json_rpc_named_arguments, retries) do - func = &get_block_timestamp_by_number_inner/2 - args = [number, json_rpc_named_arguments] - error_message = &"Cannot fetch block ##{number} or its timestamp. Error: #{inspect(&1)}" - repeated_call(func, args, error_message, retries) - end - defp get_safe_block(json_rpc_named_arguments) do - case get_block_number_by_tag("safe", json_rpc_named_arguments) do + case Helper.get_block_number_by_tag("safe", json_rpc_named_arguments) do {:ok, safe_block} -> {safe_block, false} {:error, :not_found} -> - {:ok, latest_block} = get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + {:ok, latest_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) {latest_block, true} end end @@ -632,22 +599,7 @@ defmodule Indexer.Fetcher.PolygonEdge do error_message = &"Cannot fetch logs for the block range #{from_block}..#{to_block}. Error: #{inspect(&1)}" - repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) - end - - defp get_transaction_by_hash(hash, _json_rpc_named_arguments, _retries_left) when is_nil(hash), do: {:ok, nil} - - defp get_transaction_by_hash(hash, json_rpc_named_arguments, retries) do - req = - request(%{ - id: 0, - method: "eth_getTransactionByHash", - params: [hash] - }) - - error_message = &"eth_getTransactionByHash failed. Error: #{inspect(&1)}" - - repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) + Helper.repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) end defp get_last_l1_item(table) do @@ -696,50 +648,18 @@ defmodule Indexer.Fetcher.PolygonEdge do Repo.one(from(item in table, select: item.l2_block_number, where: item.msg_id == ^id)) end - defp log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, items_count, layer) do - is_start = is_nil(items_count) - - {type, found} = - if is_start do - {"Start", ""} - else - {"Finish", " Found #{items_count}."} - end - - target_range = - if chunk_start != start_block or chunk_end != end_block do - progress = - if is_start do - "" - else - percentage = - (chunk_end - start_block + 1) - |> Decimal.div(end_block - start_block + 1) - |> Decimal.mult(100) - |> Decimal.round(2) - |> Decimal.to_string() - - " Progress: #{percentage}%" - end - - " Target range: #{start_block}..#{end_block}.#{progress}" - else - "" - end - - if chunk_start == chunk_end do - Logger.info("#{type} handling #{layer} block ##{chunk_start}.#{found}#{target_range}") - else - Logger.info("#{type} handling #{layer} block range #{chunk_start}..#{chunk_end}.#{found}#{target_range}") - end - end - defp import_events(events, calling_module) do + # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise {import_data, event_name} = - if calling_module == Deposit do - {%{polygon_edge_deposits: %{params: events}, timeout: :infinity}, "StateSynced"} - else - {%{polygon_edge_withdrawal_exits: %{params: events}, timeout: :infinity}, "ExitProcessed"} + case System.get_env("CHAIN_TYPE") == "polygon_edge" && calling_module do + Deposit -> + {%{polygon_edge_deposits: %{params: events}, timeout: :infinity}, "StateSynced"} + + WithdrawalExit -> + {%{polygon_edge_withdrawal_exits: %{params: events}, timeout: :infinity}, "ExitProcessed"} + + _ -> + {%{}, ""} end {:ok, _} = Chain.import(import_data) @@ -755,28 +675,9 @@ defmodule Indexer.Fetcher.PolygonEdge do end end - defp repeated_call(func, args, error_message, retries_left) do - case apply(func, args) do - {:ok, _} = res -> - res - - {:error, message} = err -> - retries_left = retries_left - 1 - - if retries_left <= 0 do - Logger.error(error_message.(message)) - err - else - Logger.error("#{error_message.(message)} Retrying...") - :timer.sleep(3000) - repeated_call(func, args, error_message, retries_left) - end - end - end - @spec repeated_request(list(), any(), list(), non_neg_integer()) :: {:ok, any()} | {:error, atom()} def repeated_request(req, error_message, json_rpc_named_arguments, retries) do - repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) + Helper.repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) end defp reorg_block_pop(fetcher_name) do diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex index 0807c1bc8dfd..8367883ee144 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex @@ -11,13 +11,14 @@ defmodule Indexer.Fetcher.PolygonEdge.DepositExecute do import Ecto.Query import EthereumJSONRPC, only: [quantity_to_integer: 1] - import Indexer.Fetcher.PolygonEdge, only: [fill_block_range: 5, get_block_number_by_tag: 3] + import Indexer.Fetcher.PolygonEdge, only: [fill_block_range: 5] import Indexer.Helper, only: [log_topic_to_string: 1] alias Explorer.{Chain, Repo} alias Explorer.Chain.Log alias Explorer.Chain.PolygonEdge.DepositExecute alias Indexer.Fetcher.PolygonEdge + alias Indexer.Helper @fetcher_name :polygon_edge_deposit_execute @@ -102,7 +103,7 @@ defmodule Indexer.Fetcher.PolygonEdge.DepositExecute do if not safe_block_is_latest do # find and fill all events between "safe" and "latest" block (excluding "safe") - {:ok, latest_block} = get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + {:ok, latest_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) fill_block_range( safe_block + 1, @@ -191,11 +192,18 @@ defmodule Indexer.Fetcher.PolygonEdge.DepositExecute do end) end - {:ok, _} = - Chain.import(%{ - polygon_edge_deposit_executes: %{params: executes}, - timeout: :infinity - }) + # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise + import_options = + if System.get_env("CHAIN_TYPE") == "polygon_edge" do + %{ + polygon_edge_deposit_executes: %{params: executes}, + timeout: :infinity + } + else + %{} + end + + {:ok, _} = Chain.import(import_options) Enum.count(executes) end diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex index 8520cce2c155..4a8ae47d220b 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex @@ -12,7 +12,7 @@ defmodule Indexer.Fetcher.PolygonEdge.Withdrawal do import EthereumJSONRPC, only: [quantity_to_integer: 1] import Explorer.Helper, only: [decode_data: 2] - import Indexer.Fetcher.PolygonEdge, only: [fill_block_range: 5, get_block_number_by_tag: 3] + import Indexer.Fetcher.PolygonEdge, only: [fill_block_range: 5] import Indexer.Helper, only: [log_topic_to_string: 1] alias ABI.TypeDecoder @@ -20,6 +20,7 @@ defmodule Indexer.Fetcher.PolygonEdge.Withdrawal do alias Explorer.Chain.Log alias Explorer.Chain.PolygonEdge.Withdrawal alias Indexer.Fetcher.PolygonEdge + alias Indexer.Helper @fetcher_name :polygon_edge_withdrawal @@ -107,7 +108,7 @@ defmodule Indexer.Fetcher.PolygonEdge.Withdrawal do if not safe_block_is_latest do # find and fill all events between "safe" and "latest" block (excluding "safe") - {:ok, latest_block} = get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + {:ok, latest_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) fill_block_range( safe_block + 1, @@ -206,11 +207,18 @@ defmodule Indexer.Fetcher.PolygonEdge.Withdrawal do end) end - {:ok, _} = - Chain.import(%{ - polygon_edge_withdrawals: %{params: withdrawals}, - timeout: :infinity - }) + # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise + import_options = + if System.get_env("CHAIN_TYPE") == "polygon_edge" do + %{ + polygon_edge_withdrawals: %{params: withdrawals}, + timeout: :infinity + } + else + %{} + end + + {:ok, _} = Chain.import(import_options) Enum.count(withdrawals) end diff --git a/apps/indexer/lib/indexer/fetcher/shibarium/helper.ex b/apps/indexer/lib/indexer/fetcher/shibarium/helper.ex new file mode 100644 index 000000000000..b8cafcdb1525 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/shibarium/helper.ex @@ -0,0 +1,136 @@ +defmodule Indexer.Fetcher.Shibarium.Helper do + @moduledoc """ + Common functions for Indexer.Fetcher.Shibarium.* modules. + """ + + import Ecto.Query + + alias Explorer.Chain.Cache.ShibariumCounter + alias Explorer.Chain.Shibarium.{Bridge, Reader} + alias Explorer.Repo + + @empty_hash "0x0000000000000000000000000000000000000000000000000000000000000000" + + @doc """ + Calculates Shibarium Bridge operation hash as hash_256(user_address, amount_or_id, erc1155_ids, erc1155_amounts, operation_id). + """ + @spec calc_operation_hash(binary(), non_neg_integer() | nil, list(), list(), non_neg_integer()) :: binary() + def calc_operation_hash(user, amount_or_id, erc1155_ids, erc1155_amounts, operation_id) do + user_binary = + user + |> String.trim_leading("0x") + |> Base.decode16!(case: :mixed) + + amount_or_id = + if is_nil(amount_or_id) and not Enum.empty?(erc1155_ids) do + 0 + else + amount_or_id + end + + operation_encoded = + ABI.encode("(address,uint256,uint256[],uint256[],uint256)", [ + { + user_binary, + amount_or_id, + erc1155_ids, + erc1155_amounts, + operation_id + } + ]) + + "0x" <> + (operation_encoded + |> ExKeccak.hash_256() + |> Base.encode16(case: :lower)) + end + + @doc """ + Prepares a list of Shibarium Bridge operations to import them into database. + Tries to bind the given operations to the existing ones in DB first. + If they don't exist, prepares the insertion list and returns it. + """ + @spec prepare_insert_items(list(), module()) :: list() + def prepare_insert_items(operations, calling_module) do + operations + |> Enum.reduce([], fn op, acc -> + if bind_existing_operation_in_db(op, calling_module) == 0 do + [op | acc] + else + acc + end + end) + |> Enum.reverse() + |> Enum.reduce(%{}, fn item, acc -> + Map.put(acc, {item.operation_hash, item.l1_transaction_hash, item.l2_transaction_hash}, item) + end) + |> Map.values() + end + + @doc """ + Recalculate the cached count of complete rows for deposits and withdrawals. + """ + @spec recalculate_cached_count() :: no_return() + def recalculate_cached_count do + ShibariumCounter.deposits_count_save(Reader.deposits_count()) + ShibariumCounter.withdrawals_count_save(Reader.withdrawals_count()) + end + + defp bind_existing_operation_in_db(op, calling_module) do + {query, set} = make_query_for_bind(op, calling_module) + + {updated_count, _} = + Repo.update_all( + from(b in Bridge, + join: s in subquery(query), + on: + b.operation_hash == s.operation_hash and b.l1_transaction_hash == s.l1_transaction_hash and + b.l2_transaction_hash == s.l2_transaction_hash + ), + set: set + ) + + # increment the cached count of complete rows + case updated_count > 0 && op.operation_type do + :deposit -> ShibariumCounter.deposits_count_save(updated_count, true) + :withdrawal -> ShibariumCounter.withdrawals_count_save(updated_count, true) + false -> nil + end + + updated_count + end + + defp make_query_for_bind(op, calling_module) when calling_module == Indexer.Fetcher.Shibarium.L1 do + query = + from(sb in Bridge, + where: + sb.operation_hash == ^op.operation_hash and sb.operation_type == ^op.operation_type and + sb.l2_transaction_hash != ^@empty_hash and sb.l1_transaction_hash == ^@empty_hash, + order_by: [asc: sb.l2_block_number], + limit: 1 + ) + + set = + [l1_transaction_hash: op.l1_transaction_hash, l1_block_number: op.l1_block_number] ++ + if(op.operation_type == :deposit, do: [timestamp: op.timestamp], else: []) + + {query, set} + end + + defp make_query_for_bind(op, calling_module) when calling_module == Indexer.Fetcher.Shibarium.L2 do + query = + from(sb in Bridge, + where: + sb.operation_hash == ^op.operation_hash and sb.operation_type == ^op.operation_type and + sb.l1_transaction_hash != ^@empty_hash and sb.l2_transaction_hash == ^@empty_hash, + order_by: [asc: sb.l1_block_number], + limit: 1 + ) + + set = + [l2_transaction_hash: op.l2_transaction_hash, l2_block_number: op.l2_block_number] ++ + if(op.operation_type == :withdrawal, do: [timestamp: op.timestamp], else: []) + + {query, set} + end +end diff --git a/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex b/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex new file mode 100644 index 000000000000..c34b8a5acdde --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex @@ -0,0 +1,722 @@ +defmodule Indexer.Fetcher.Shibarium.L1 do + @moduledoc """ + Fills shibarium_bridge DB table. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + import Ecto.Query + + import EthereumJSONRPC, + only: [ + integer_to_quantity: 1, + json_rpc: 2, + quantity_to_integer: 1, + request: 1 + ] + + import Explorer.Helper, only: [parse_integer: 1, decode_data: 2] + + import Indexer.Fetcher.Shibarium.Helper, + only: [calc_operation_hash: 5, prepare_insert_items: 2, recalculate_cached_count: 0] + + alias EthereumJSONRPC.Block.ByNumber + alias EthereumJSONRPC.Blocks + alias Explorer.Chain.Shibarium.Bridge + alias Explorer.{Chain, Repo} + alias Indexer.{BoundQueue, Helper} + + @block_check_interval_range_size 100 + @eth_get_logs_range_size 1000 + @fetcher_name :shibarium_bridge_l1 + @empty_hash "0x0000000000000000000000000000000000000000000000000000000000000000" + + # 32-byte signature of the event NewDepositBlock(address indexed owner, address indexed token, uint256 amountOrNFTId, uint256 depositBlockId) + @new_deposit_block_event "0x1dadc8d0683c6f9824e885935c1bec6f76816730dcec148dda8cf25a7b9f797b" + + # 32-byte signature of the event LockedEther(address indexed depositor, address indexed depositReceiver, uint256 amount) + @locked_ether_event "0x3e799b2d61372379e767ef8f04d65089179b7a6f63f9be3065806456c7309f1b" + + # 32-byte signature of the event LockedERC20(address indexed depositor, address indexed depositReceiver, address indexed rootToken, uint256 amount) + @locked_erc20_event "0x9b217a401a5ddf7c4d474074aff9958a18d48690d77cc2151c4706aa7348b401" + + # 32-byte signature of the event LockedERC721(address indexed depositor, address indexed depositReceiver, address indexed rootToken, uint256 tokenId) + @locked_erc721_event "0x8357472e13612a8c3d6f3e9d71fbba8a78ab77dbdcc7fcf3b7b645585f0bbbfc" + + # 32-byte signature of the event LockedERC721Batch(address indexed depositor, address indexed depositReceiver, address indexed rootToken, uint256[] tokenIds) + @locked_erc721_batch_event "0x5345c2beb0e49c805f42bb70c4ec5c3c3d9680ce45b8f4529c028d5f3e0f7a0d" + + # 32-byte signature of the event LockedBatchERC1155(address indexed depositor, address indexed depositReceiver, address indexed rootToken, uint256[] ids, uint256[] amounts) + @locked_batch_erc1155_event "0x5a921678b5779e4471b77219741a417a6ad6ec5d89fa5c8ce8cd7bd2d9f34186" + + # 32-byte signature of the event Withdraw(uint256 indexed exitId, address indexed user, address indexed token, uint256 amount) + @withdraw_event "0xfeb2000dca3e617cd6f3a8bbb63014bb54a124aac6ccbf73ee7229b4cd01f120" + + # 32-byte signature of the event ExitedEther(address indexed exitor, uint256 amount) + @exited_ether_event "0x0fc0eed41f72d3da77d0f53b9594fc7073acd15ee9d7c536819a70a67c57ef3c" + + # 32-byte signature of the event Transfer(address indexed from, address indexed to, uint256 value) + @transfer_event "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + + # 32-byte signature of the event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value) + @transfer_single_event "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62" + + # 32-byte signature of the event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values) + @transfer_batch_event "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb" + + def child_spec(start_link_arguments) do + spec = %{ + id: __MODULE__, + start: {__MODULE__, :start_link, start_link_arguments}, + restart: :transient, + type: :worker + } + + Supervisor.child_spec(spec, []) + end + + def start_link(args, gen_server_options \\ []) do + GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__)) + end + + @impl GenServer + def init(_args) do + {:ok, %{}, {:continue, :ok}} + end + + @impl GenServer + def handle_continue(_, state) do + Logger.metadata(fetcher: @fetcher_name) + # two seconds pause needed to avoid exceeding Supervisor restart intensity when DB issues + Process.send_after(self(), :wait_for_l2, 2000) + {:noreply, state} + end + + @impl GenServer + def handle_info(:wait_for_l2, state) do + if is_nil(Process.whereis(Indexer.Fetcher.Shibarium.L2)) do + Process.send(self(), :init_with_delay, []) + else + Process.send_after(self(), :wait_for_l2, 2000) + end + + {:noreply, state} + end + + @impl GenServer + def handle_info(:init_with_delay, _state) do + env = Application.get_all_env(:indexer)[__MODULE__] + + with {:start_block_undefined, false} <- {:start_block_undefined, is_nil(env[:start_block])}, + rpc = env[:rpc], + {:rpc_undefined, false} <- {:rpc_undefined, is_nil(rpc)}, + {:deposit_manager_address_is_valid, true} <- + {:deposit_manager_address_is_valid, Helper.address_correct?(env[:deposit_manager_proxy])}, + {:ether_predicate_address_is_valid, true} <- + {:ether_predicate_address_is_valid, Helper.address_correct?(env[:ether_predicate_proxy])}, + {:erc20_predicate_address_is_valid, true} <- + {:erc20_predicate_address_is_valid, Helper.address_correct?(env[:erc20_predicate_proxy])}, + {:erc721_predicate_address_is_valid, true} <- + {:erc721_predicate_address_is_valid, + is_nil(env[:erc721_predicate_proxy]) or Helper.address_correct?(env[:erc721_predicate_proxy])}, + {:erc1155_predicate_address_is_valid, true} <- + {:erc1155_predicate_address_is_valid, + is_nil(env[:erc1155_predicate_proxy]) or Helper.address_correct?(env[:erc1155_predicate_proxy])}, + {:withdraw_manager_address_is_valid, true} <- + {:withdraw_manager_address_is_valid, Helper.address_correct?(env[:withdraw_manager_proxy])}, + start_block = parse_integer(env[:start_block]), + false <- is_nil(start_block), + true <- start_block > 0, + {last_l1_block_number, last_l1_transaction_hash} <- get_last_l1_item(), + {:start_block_valid, true} <- + {:start_block_valid, start_block <= last_l1_block_number || last_l1_block_number == 0}, + json_rpc_named_arguments = json_rpc_named_arguments(rpc), + {:ok, last_l1_tx} <- Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), + {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)}, + {:ok, block_check_interval, latest_block} <- get_block_check_interval(json_rpc_named_arguments), + {:start_block_valid, true} <- {:start_block_valid, start_block <= latest_block} do + recalculate_cached_count() + + Process.send(self(), :reorg_monitor, []) + Process.send(self(), :continue, []) + + {:noreply, + %{ + deposit_manager_proxy: env[:deposit_manager_proxy], + ether_predicate_proxy: env[:ether_predicate_proxy], + erc20_predicate_proxy: env[:erc20_predicate_proxy], + erc721_predicate_proxy: env[:erc721_predicate_proxy], + erc1155_predicate_proxy: env[:erc1155_predicate_proxy], + withdraw_manager_proxy: env[:withdraw_manager_proxy], + block_check_interval: block_check_interval, + start_block: max(start_block, last_l1_block_number), + end_block: latest_block, + json_rpc_named_arguments: json_rpc_named_arguments, + reorg_monitor_prev_latest: 0 + }} + else + {:start_block_undefined, true} -> + # the process shouldn't start if the start block is not defined + {:stop, :normal, %{}} + + {:rpc_undefined, true} -> + Logger.error("L1 RPC URL is not defined.") + {:stop, :normal, %{}} + + {:deposit_manager_address_is_valid, false} -> + Logger.error("DepositManagerProxy contract address is invalid or not defined.") + {:stop, :normal, %{}} + + {:ether_predicate_address_is_valid, false} -> + Logger.error("EtherPredicateProxy contract address is invalid or not defined.") + {:stop, :normal, %{}} + + {:erc20_predicate_address_is_valid, false} -> + Logger.error("ERC20PredicateProxy contract address is invalid or not defined.") + {:stop, :normal, %{}} + + {:erc721_predicate_address_is_valid, false} -> + Logger.error("ERC721PredicateProxy contract address is invalid.") + {:stop, :normal, %{}} + + {:erc1155_predicate_address_is_valid, false} -> + Logger.error("ERC1155PredicateProxy contract address is invalid.") + {:stop, :normal, %{}} + + {:withdraw_manager_address_is_valid, false} -> + Logger.error("WithdrawManagerProxy contract address is invalid or not defined.") + {:stop, :normal, %{}} + + {:start_block_valid, false} -> + Logger.error("Invalid L1 Start Block value. Please, check the value and shibarium_bridge table.") + {:stop, :normal, %{}} + + {:error, error_data} -> + Logger.error( + "Cannot get last L1 transaction from RPC by its hash, latest block, or block timestamp by its number due to RPC error: #{inspect(error_data)}" + ) + + {:stop, :normal, %{}} + + {:l1_tx_not_found, true} -> + Logger.error( + "Cannot find last L1 transaction from RPC by its hash. Probably, there was a reorg on L1 chain. Please, check shibarium_bridge table." + ) + + {:stop, :normal, %{}} + + _ -> + Logger.error("L1 Start Block is invalid or zero.") + {:stop, :normal, %{}} + end + end + + @impl GenServer + def handle_info( + :reorg_monitor, + %{ + block_check_interval: block_check_interval, + json_rpc_named_arguments: json_rpc_named_arguments, + reorg_monitor_prev_latest: prev_latest + } = state + ) do + {:ok, latest} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + + if latest < prev_latest do + Logger.warning("Reorg detected: previous latest block ##{prev_latest}, current latest block ##{latest}.") + reorg_block_push(latest) + end + + Process.send_after(self(), :reorg_monitor, block_check_interval) + + {:noreply, %{state | reorg_monitor_prev_latest: latest}} + end + + @impl GenServer + def handle_info( + :continue, + %{ + deposit_manager_proxy: deposit_manager_proxy, + ether_predicate_proxy: ether_predicate_proxy, + erc20_predicate_proxy: erc20_predicate_proxy, + erc721_predicate_proxy: erc721_predicate_proxy, + erc1155_predicate_proxy: erc1155_predicate_proxy, + withdraw_manager_proxy: withdraw_manager_proxy, + block_check_interval: block_check_interval, + start_block: start_block, + end_block: end_block, + json_rpc_named_arguments: json_rpc_named_arguments + } = state + ) do + time_before = Timex.now() + + last_written_block = + start_block..end_block + |> Enum.chunk_every(@eth_get_logs_range_size) + |> Enum.reduce_while(start_block - 1, fn current_chunk, _ -> + chunk_start = List.first(current_chunk) + chunk_end = List.last(current_chunk) + + if chunk_start <= chunk_end do + Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, "L1") + + operations = + {chunk_start, chunk_end} + |> get_logs_all( + deposit_manager_proxy, + ether_predicate_proxy, + erc20_predicate_proxy, + erc721_predicate_proxy, + erc1155_predicate_proxy, + withdraw_manager_proxy, + json_rpc_named_arguments + ) + |> prepare_operations(json_rpc_named_arguments) + + {:ok, _} = + Chain.import(%{ + shibarium_bridge_operations: %{params: prepare_insert_items(operations, __MODULE__)}, + timeout: :infinity + }) + + Helper.log_blocks_chunk_handling( + chunk_start, + chunk_end, + start_block, + end_block, + "#{Enum.count(operations)} L1 operation(s)", + "L1" + ) + end + + reorg_block = reorg_block_pop() + + if !is_nil(reorg_block) && reorg_block > 0 do + reorg_handle(reorg_block) + {:halt, if(reorg_block <= chunk_end, do: reorg_block - 1, else: chunk_end)} + else + {:cont, chunk_end} + end + end) + + new_start_block = last_written_block + 1 + {:ok, new_end_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + + delay = + if new_end_block == last_written_block do + # there is no new block, so wait for some time to let the chain issue the new block + max(block_check_interval - Timex.diff(Timex.now(), time_before, :milliseconds), 0) + else + 0 + end + + Process.send_after(self(), :continue, delay) + + {:noreply, %{state | start_block: new_start_block, end_block: new_end_block}} + end + + @impl GenServer + def handle_info({ref, _result}, state) do + Process.demonitor(ref, [:flush]) + {:noreply, state} + end + + defp filter_deposit_events(events) do + Enum.filter(events, fn event -> + topic0 = Enum.at(event["topics"], 0) + deposit?(topic0) + end) + end + + defp get_block_check_interval(json_rpc_named_arguments) do + with {:ok, latest_block} <- Helper.get_block_number_by_tag("latest", json_rpc_named_arguments), + first_block = max(latest_block - @block_check_interval_range_size, 1), + {:ok, first_block_timestamp} <- Helper.get_block_timestamp_by_number(first_block, json_rpc_named_arguments), + {:ok, last_safe_block_timestamp} <- + Helper.get_block_timestamp_by_number(latest_block, json_rpc_named_arguments) do + block_check_interval = + ceil((last_safe_block_timestamp - first_block_timestamp) / (latest_block - first_block) * 1000 / 2) + + Logger.info("Block check interval is calculated as #{block_check_interval} ms.") + {:ok, block_check_interval, latest_block} + else + {:error, error} -> + {:error, "Failed to calculate block check interval due to #{inspect(error)}"} + end + end + + defp get_blocks_by_events(events, json_rpc_named_arguments, retries) do + request = + events + |> Enum.reduce(%{}, fn event, acc -> + Map.put(acc, event["blockNumber"], 0) + end) + |> Stream.map(fn {block_number, _} -> %{number: block_number} end) + |> Stream.with_index() + |> Enum.into(%{}, fn {params, id} -> {id, params} end) + |> Blocks.requests(&ByNumber.request(&1, false, false)) + + error_message = &"Cannot fetch blocks with batch request. Error: #{inspect(&1)}. Request: #{inspect(request)}" + + case Helper.repeated_call(&json_rpc/2, [request, json_rpc_named_arguments], error_message, retries) do + {:ok, results} -> Enum.map(results, fn %{result: result} -> result end) + {:error, _} -> [] + end + end + + defp get_last_l1_item do + query = + from(sb in Bridge, + select: {sb.l1_block_number, sb.l1_transaction_hash}, + where: not is_nil(sb.l1_block_number), + order_by: [desc: sb.l1_block_number], + limit: 1 + ) + + query + |> Repo.one() + |> Kernel.||({0, nil}) + end + + defp get_logs(from_block, to_block, address, topics, json_rpc_named_arguments, retries \\ 100_000_000) do + processed_from_block = integer_to_quantity(from_block) + processed_to_block = integer_to_quantity(to_block) + + req = + request(%{ + id: 0, + method: "eth_getLogs", + params: [ + %{ + :fromBlock => processed_from_block, + :toBlock => processed_to_block, + :address => address, + :topics => topics + } + ] + }) + + error_message = &"Cannot fetch logs for the block range #{from_block}..#{to_block}. Error: #{inspect(&1)}" + + Helper.repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) + end + + defp get_logs_all( + {chunk_start, chunk_end}, + deposit_manager_proxy, + ether_predicate_proxy, + erc20_predicate_proxy, + erc721_predicate_proxy, + erc1155_predicate_proxy, + withdraw_manager_proxy, + json_rpc_named_arguments + ) do + {:ok, known_tokens_result} = + get_logs( + chunk_start, + chunk_end, + [deposit_manager_proxy, ether_predicate_proxy, erc20_predicate_proxy, withdraw_manager_proxy], + [ + [ + @new_deposit_block_event, + @locked_ether_event, + @locked_erc20_event, + @locked_erc721_event, + @locked_erc721_batch_event, + @locked_batch_erc1155_event, + @withdraw_event, + @exited_ether_event + ] + ], + json_rpc_named_arguments + ) + + contract_addresses = + if is_nil(erc721_predicate_proxy) do + [pad_address_hash(erc20_predicate_proxy)] + else + [pad_address_hash(erc20_predicate_proxy), pad_address_hash(erc721_predicate_proxy)] + end + + {:ok, unknown_erc20_erc721_tokens_result} = + get_logs( + chunk_start, + chunk_end, + nil, + [ + @transfer_event, + contract_addresses + ], + json_rpc_named_arguments + ) + + {:ok, unknown_erc1155_tokens_result} = + if is_nil(erc1155_predicate_proxy) do + {:ok, []} + else + get_logs( + chunk_start, + chunk_end, + nil, + [ + [@transfer_single_event, @transfer_batch_event], + nil, + pad_address_hash(erc1155_predicate_proxy) + ], + json_rpc_named_arguments + ) + end + + known_tokens_result ++ unknown_erc20_erc721_tokens_result ++ unknown_erc1155_tokens_result + end + + defp get_op_user(topic0, event) do + cond do + Enum.member?([@new_deposit_block_event, @exited_ether_event], topic0) -> + truncate_address_hash(Enum.at(event["topics"], 1)) + + Enum.member?( + [ + @locked_ether_event, + @locked_erc20_event, + @locked_erc721_event, + @locked_erc721_batch_event, + @locked_batch_erc1155_event, + @withdraw_event, + @transfer_event + ], + topic0 + ) -> + truncate_address_hash(Enum.at(event["topics"], 2)) + + Enum.member?([@transfer_single_event, @transfer_batch_event], topic0) -> + truncate_address_hash(Enum.at(event["topics"], 3)) + end + end + + defp get_op_amounts(topic0, event) do + cond do + topic0 == @new_deposit_block_event -> + [amount_or_nft_id, deposit_block_id] = decode_data(event["data"], [{:uint, 256}, {:uint, 256}]) + {[amount_or_nft_id], deposit_block_id} + + topic0 == @transfer_event -> + indexed_token_id = Enum.at(event["topics"], 3) + + if is_nil(indexed_token_id) do + {decode_data(event["data"], [{:uint, 256}]), 0} + else + {[quantity_to_integer(indexed_token_id)], 0} + end + + Enum.member?( + [ + @locked_ether_event, + @locked_erc20_event, + @locked_erc721_event, + @withdraw_event, + @exited_ether_event + ], + topic0 + ) -> + {decode_data(event["data"], [{:uint, 256}]), 0} + + topic0 == @locked_erc721_batch_event -> + [ids] = decode_data(event["data"], [{:array, {:uint, 256}}]) + {ids, 0} + + true -> + {[nil], 0} + end + end + + defp get_op_erc1155_data(topic0, event) do + cond do + Enum.member?([@locked_batch_erc1155_event, @transfer_batch_event], topic0) -> + [ids, amounts] = decode_data(event["data"], [{:array, {:uint, 256}}, {:array, {:uint, 256}}]) + {ids, amounts} + + Enum.member?([@transfer_single_event], topic0) -> + [id, amount] = decode_data(event["data"], [{:uint, 256}, {:uint, 256}]) + {[id], [amount]} + + true -> + {[], []} + end + end + + defp deposit?(topic0) do + Enum.member?( + [ + @new_deposit_block_event, + @locked_ether_event, + @locked_erc20_event, + @locked_erc721_event, + @locked_erc721_batch_event, + @locked_batch_erc1155_event + ], + topic0 + ) + end + + defp json_rpc_named_arguments(rpc_url) do + [ + transport: EthereumJSONRPC.HTTP, + transport_options: [ + http: EthereumJSONRPC.HTTP.HTTPoison, + url: rpc_url, + http_options: [ + recv_timeout: :timer.minutes(10), + timeout: :timer.minutes(10), + hackney: [pool: :ethereum_jsonrpc] + ] + ] + ] + end + + defp prepare_operations(events, json_rpc_named_arguments) do + timestamps = + events + |> filter_deposit_events() + |> get_blocks_by_events(json_rpc_named_arguments, 100_000_000) + |> Enum.reduce(%{}, fn block, acc -> + block_number = quantity_to_integer(Map.get(block, "number")) + {:ok, timestamp} = DateTime.from_unix(quantity_to_integer(Map.get(block, "timestamp"))) + Map.put(acc, block_number, timestamp) + end) + + events + |> Enum.map(fn event -> + topic0 = Enum.at(event["topics"], 0) + + user = get_op_user(topic0, event) + {amounts_or_ids, operation_id} = get_op_amounts(topic0, event) + {erc1155_ids, erc1155_amounts} = get_op_erc1155_data(topic0, event) + + l1_block_number = quantity_to_integer(event["blockNumber"]) + + {operation_type, timestamp} = + if deposit?(topic0) do + {:deposit, Map.get(timestamps, l1_block_number)} + else + {:withdrawal, nil} + end + + token_type = + cond do + Enum.member?([@new_deposit_block_event, @withdraw_event], topic0) -> + "bone" + + Enum.member?([@locked_ether_event, @exited_ether_event], topic0) -> + "eth" + + true -> + "other" + end + + Enum.map(amounts_or_ids, fn amount_or_id -> + %{ + user: user, + amount_or_id: amount_or_id, + erc1155_ids: if(Enum.empty?(erc1155_ids), do: nil, else: erc1155_ids), + erc1155_amounts: if(Enum.empty?(erc1155_amounts), do: nil, else: erc1155_amounts), + l1_transaction_hash: event["transactionHash"], + l1_block_number: l1_block_number, + l2_transaction_hash: @empty_hash, + operation_hash: calc_operation_hash(user, amount_or_id, erc1155_ids, erc1155_amounts, operation_id), + operation_type: operation_type, + token_type: token_type, + timestamp: timestamp + } + end) + end) + |> List.flatten() + end + + defp pad_address_hash(address) do + "0x" <> + (address + |> String.trim_leading("0x") + |> String.pad_leading(64, "0")) + end + + defp truncate_address_hash("0x000000000000000000000000" <> truncated_hash) do + "0x#{truncated_hash}" + end + + defp reorg_block_pop do + table_name = reorg_table_name(@fetcher_name) + + case BoundQueue.pop_front(reorg_queue_get(table_name)) do + {:ok, {block_number, updated_queue}} -> + :ets.insert(table_name, {:queue, updated_queue}) + block_number + + {:error, :empty} -> + nil + end + end + + defp reorg_block_push(block_number) do + table_name = reorg_table_name(@fetcher_name) + {:ok, updated_queue} = BoundQueue.push_back(reorg_queue_get(table_name), block_number) + :ets.insert(table_name, {:queue, updated_queue}) + end + + defp reorg_handle(reorg_block) do + {deleted_count, _} = + Repo.delete_all(from(sb in Bridge, where: sb.l1_block_number >= ^reorg_block and is_nil(sb.l2_transaction_hash))) + + {updated_count1, _} = + Repo.update_all( + from(sb in Bridge, + where: + sb.l1_block_number >= ^reorg_block and not is_nil(sb.l2_transaction_hash) and + sb.operation_type == :deposit + ), + set: [timestamp: nil] + ) + + {updated_count2, _} = + Repo.update_all( + from(sb in Bridge, where: sb.l1_block_number >= ^reorg_block and not is_nil(sb.l2_transaction_hash)), + set: [l1_transaction_hash: nil, l1_block_number: nil] + ) + + updated_count = max(updated_count1, updated_count2) + + if deleted_count > 0 or updated_count > 0 do + recalculate_cached_count() + + Logger.warning( + "As L1 reorg was detected, some rows with l1_block_number >= #{reorg_block} were affected (removed or updated) in the shibarium_bridge table. Number of removed rows: #{deleted_count}. Number of updated rows: >= #{updated_count}." + ) + end + end + + defp reorg_queue_get(table_name) do + if :ets.whereis(table_name) == :undefined do + :ets.new(table_name, [ + :set, + :named_table, + :public, + read_concurrency: true, + write_concurrency: true + ]) + end + + with info when info != :undefined <- :ets.info(table_name), + [{_, value}] <- :ets.lookup(table_name, :queue) do + value + else + _ -> %BoundQueue{} + end + end + + defp reorg_table_name(fetcher_name) do + :"#{fetcher_name}#{:_reorgs}" + end +end diff --git a/apps/indexer/lib/indexer/fetcher/shibarium/l2.ex b/apps/indexer/lib/indexer/fetcher/shibarium/l2.ex new file mode 100644 index 000000000000..6a31962d6483 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/shibarium/l2.ex @@ -0,0 +1,536 @@ +defmodule Indexer.Fetcher.Shibarium.L2 do + @moduledoc """ + Fills shibarium_bridge DB table. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + import Ecto.Query + + import EthereumJSONRPC, + only: [ + json_rpc: 2, + quantity_to_integer: 1, + request: 1 + ] + + import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] + + import Explorer.Helper, only: [decode_data: 2, parse_integer: 1] + + import Indexer.Fetcher.Shibarium.Helper, + only: [calc_operation_hash: 5, prepare_insert_items: 2, recalculate_cached_count: 0] + + alias EthereumJSONRPC.Block.ByNumber + alias EthereumJSONRPC.{Blocks, Logs, Receipt} + alias Explorer.{Chain, Repo} + alias Explorer.Chain.Shibarium.Bridge + alias Indexer.Helper + + @eth_get_logs_range_size 100 + @fetcher_name :shibarium_bridge_l2 + @empty_hash "0x0000000000000000000000000000000000000000000000000000000000000000" + + # 32-byte signature of the event TokenDeposited(address indexed rootToken, address indexed childToken, address indexed user, uint256 amount, uint256 depositCount) + @token_deposited_event "0xec3afb067bce33c5a294470ec5b29e6759301cd3928550490c6d48816cdc2f5d" + + # 32-byte signature of the event Transfer(address indexed from, address indexed to, uint256 value) + @transfer_event "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + + # 32-byte signature of the event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value) + @transfer_single_event "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62" + + # 32-byte signature of the event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values) + @transfer_batch_event "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb" + + # 32-byte signature of the event Withdraw(address indexed rootToken, address indexed from, uint256 amount, uint256, uint256) + @withdraw_event "0xebff2602b3f468259e1e99f613fed6691f3a6526effe6ef3e768ba7ae7a36c4f" + + # 4-byte signature of the method withdraw(uint256 amount) + @withdraw_method "0x2e1a7d4d" + + def child_spec(start_link_arguments) do + spec = %{ + id: __MODULE__, + start: {__MODULE__, :start_link, start_link_arguments}, + restart: :transient, + type: :worker + } + + Supervisor.child_spec(spec, []) + end + + def start_link(args, gen_server_options \\ []) do + GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__)) + end + + @impl GenServer + def init(args) do + json_rpc_named_arguments = args[:json_rpc_named_arguments] + {:ok, %{}, {:continue, json_rpc_named_arguments}} + end + + @impl GenServer + def handle_continue(json_rpc_named_arguments, _state) do + Logger.metadata(fetcher: @fetcher_name) + # two seconds pause needed to avoid exceeding Supervisor restart intensity when DB issues + Process.send_after(self(), :init_with_delay, 2000) + {:noreply, %{json_rpc_named_arguments: json_rpc_named_arguments}} + end + + @impl GenServer + def handle_info(:init_with_delay, %{json_rpc_named_arguments: json_rpc_named_arguments} = state) do + env = Application.get_all_env(:indexer)[__MODULE__] + + with {:start_block_undefined, false} <- {:start_block_undefined, is_nil(env[:start_block])}, + {:child_chain_address_is_valid, true} <- + {:child_chain_address_is_valid, Helper.address_correct?(env[:child_chain])}, + {:weth_address_is_valid, true} <- {:weth_address_is_valid, Helper.address_correct?(env[:weth])}, + {:bone_withdraw_address_is_valid, true} <- + {:bone_withdraw_address_is_valid, Helper.address_correct?(env[:bone_withdraw])}, + start_block = parse_integer(env[:start_block]), + false <- is_nil(start_block), + true <- start_block > 0, + {last_l2_block_number, last_l2_transaction_hash} <- get_last_l2_item(), + {:ok, latest_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments), + {:start_block_valid, true} <- + {:start_block_valid, + (start_block <= last_l2_block_number || last_l2_block_number == 0) && start_block <= latest_block}, + {:ok, last_l2_tx} <- Helper.get_transaction_by_hash(last_l2_transaction_hash, json_rpc_named_arguments), + {:l2_tx_not_found, false} <- {:l2_tx_not_found, !is_nil(last_l2_transaction_hash) && is_nil(last_l2_tx)} do + recalculate_cached_count() + + Process.send(self(), :continue, []) + + {:noreply, + %{ + start_block: max(start_block, last_l2_block_number), + latest_block: latest_block, + child_chain: String.downcase(env[:child_chain]), + weth: String.downcase(env[:weth]), + bone_withdraw: String.downcase(env[:bone_withdraw]), + json_rpc_named_arguments: json_rpc_named_arguments + }} + else + {:start_block_undefined, true} -> + # the process shouldn't start if the start block is not defined + {:stop, :normal, state} + + {:child_chain_address_is_valid, false} -> + Logger.error("ChildChain contract address is invalid or not defined.") + {:stop, :normal, state} + + {:weth_address_is_valid, false} -> + Logger.error("WETH contract address is invalid or not defined.") + {:stop, :normal, state} + + {:bone_withdraw_address_is_valid, false} -> + Logger.error("Bone Withdraw contract address is invalid or not defined.") + {:stop, :normal, state} + + {:start_block_valid, false} -> + Logger.error("Invalid L2 Start Block value. Please, check the value and shibarium_bridge table.") + {:stop, :normal, state} + + {:error, error_data} -> + Logger.error( + "Cannot get last L2 transaction by its hash or latest block from RPC due to RPC error: #{inspect(error_data)}" + ) + + {:stop, :normal, state} + + {:l2_tx_not_found, true} -> + Logger.error( + "Cannot find last L2 transaction from RPC by its hash. Probably, there was a reorg on L2 chain. Please, check shibarium_bridge table." + ) + + {:stop, :normal, state} + + _ -> + Logger.error("L2 Start Block is invalid or zero.") + {:stop, :normal, state} + end + end + + @impl GenServer + def handle_info( + :continue, + %{ + start_block: start_block, + latest_block: end_block, + child_chain: child_chain, + weth: weth, + bone_withdraw: bone_withdraw, + json_rpc_named_arguments: json_rpc_named_arguments + } = state + ) do + start_block..end_block + |> Enum.chunk_every(@eth_get_logs_range_size) + |> Enum.each(fn current_chunk -> + chunk_start = List.first(current_chunk) + chunk_end = List.last(current_chunk) + + Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, "L2") + + operations = + chunk_start..chunk_end + |> get_logs_all(child_chain, bone_withdraw, json_rpc_named_arguments) + |> prepare_operations(weth) + + {:ok, _} = + Chain.import(%{ + shibarium_bridge_operations: %{params: prepare_insert_items(operations, __MODULE__)}, + timeout: :infinity + }) + + Helper.log_blocks_chunk_handling( + chunk_start, + chunk_end, + start_block, + end_block, + "#{Enum.count(operations)} L2 operation(s)", + "L2" + ) + end) + + {:stop, :normal, state} + end + + @impl GenServer + def handle_info({ref, _result}, state) do + Process.demonitor(ref, [:flush]) + {:noreply, state} + end + + def filter_deposit_events(events, child_chain) do + Enum.filter(events, fn event -> + address = String.downcase(event.address_hash) + first_topic = Helper.log_topic_to_string(event.first_topic) + second_topic = Helper.log_topic_to_string(event.second_topic) + third_topic = Helper.log_topic_to_string(event.third_topic) + fourth_topic = Helper.log_topic_to_string(event.fourth_topic) + + (first_topic == @token_deposited_event and address == child_chain) or + (first_topic == @transfer_event and second_topic == @empty_hash and third_topic != @empty_hash) or + (Enum.member?([@transfer_single_event, @transfer_batch_event], first_topic) and + third_topic == @empty_hash and fourth_topic != @empty_hash) + end) + end + + def filter_withdrawal_events(events, bone_withdraw) do + Enum.filter(events, fn event -> + address = String.downcase(event.address_hash) + first_topic = Helper.log_topic_to_string(event.first_topic) + second_topic = Helper.log_topic_to_string(event.second_topic) + third_topic = Helper.log_topic_to_string(event.third_topic) + fourth_topic = Helper.log_topic_to_string(event.fourth_topic) + + (first_topic == @withdraw_event and address == bone_withdraw) or + (first_topic == @transfer_event and second_topic != @empty_hash and third_topic == @empty_hash) or + (Enum.member?([@transfer_single_event, @transfer_batch_event], first_topic) and + third_topic != @empty_hash and fourth_topic == @empty_hash) + end) + end + + def prepare_operations({events, timestamps}, weth) do + events + |> Enum.map(&prepare_operation(&1, timestamps, weth)) + |> List.flatten() + end + + def reorg_handle(reorg_block) do + {deleted_count, _} = + Repo.delete_all(from(sb in Bridge, where: sb.l2_block_number >= ^reorg_block and is_nil(sb.l1_transaction_hash))) + + {updated_count1, _} = + Repo.update_all( + from(sb in Bridge, + where: + sb.l2_block_number >= ^reorg_block and not is_nil(sb.l1_transaction_hash) and + sb.operation_type == :withdrawal + ), + set: [timestamp: nil] + ) + + {updated_count2, _} = + Repo.update_all( + from(sb in Bridge, where: sb.l2_block_number >= ^reorg_block and not is_nil(sb.l1_transaction_hash)), + set: [l2_transaction_hash: nil, l2_block_number: nil] + ) + + updated_count = max(updated_count1, updated_count2) + + if deleted_count > 0 or updated_count > 0 do + recalculate_cached_count() + + Logger.warning( + "As L2 reorg was detected, some rows with l2_block_number >= #{reorg_block} were affected (removed or updated) in the shibarium_bridge table. Number of removed rows: #{deleted_count}. Number of updated rows: >= #{updated_count}." + ) + end + end + + def withdraw_method_signature do + @withdraw_method + end + + defp get_blocks_by_range(range, json_rpc_named_arguments, retries) do + request = + range + |> Stream.map(fn block_number -> %{number: block_number} end) + |> Stream.with_index() + |> Enum.into(%{}, fn {params, id} -> {id, params} end) + |> Blocks.requests(&ByNumber.request(&1)) + + error_message = &"Cannot fetch blocks with batch request. Error: #{inspect(&1)}. Request: #{inspect(request)}" + + case Helper.repeated_call(&json_rpc/2, [request, json_rpc_named_arguments], error_message, retries) do + {:ok, results} -> Enum.map(results, fn %{result: result} -> result end) + {:error, _} -> [] + end + end + + defp get_last_l2_item do + query = + from(sb in Bridge, + select: {sb.l2_block_number, sb.l2_transaction_hash}, + where: not is_nil(sb.l2_block_number), + order_by: [desc: sb.l2_block_number], + limit: 1 + ) + + query + |> Repo.one() + |> Kernel.||({0, nil}) + end + + defp get_logs_all(block_range, child_chain, bone_withdraw, json_rpc_named_arguments) do + blocks = get_blocks_by_range(block_range, json_rpc_named_arguments, 100_000_000) + + deposit_logs = get_deposit_logs_from_receipts(blocks, child_chain, json_rpc_named_arguments) + + withdrawal_logs = get_withdrawal_logs_from_receipts(blocks, bone_withdraw, json_rpc_named_arguments) + + timestamps = + blocks + |> Enum.reduce(%{}, fn block, acc -> + block_number = + block + |> Map.get("number") + |> quantity_to_integer() + + {:ok, timestamp} = + block + |> Map.get("timestamp") + |> quantity_to_integer() + |> DateTime.from_unix() + + Map.put(acc, block_number, timestamp) + end) + + {deposit_logs ++ withdrawal_logs, timestamps} + end + + defp get_deposit_logs_from_receipts(blocks, child_chain, json_rpc_named_arguments) do + blocks + |> Enum.reduce([], fn block, acc -> + hashes = + block + |> Map.get("transactions", []) + |> Enum.filter(fn t -> Map.get(t, "from") == burn_address_hash_string() end) + |> Enum.map(fn t -> Map.get(t, "hash") end) + + acc ++ hashes + end) + |> Enum.chunk_every(@eth_get_logs_range_size) + |> Enum.reduce([], fn hashes, acc -> + acc ++ get_receipt_logs(hashes, json_rpc_named_arguments, 100_000_000) + end) + |> filter_deposit_events(child_chain) + end + + defp get_withdrawal_logs_from_receipts(blocks, bone_withdraw, json_rpc_named_arguments) do + blocks + |> Enum.reduce([], fn block, acc -> + hashes = + block + |> Map.get("transactions", []) + |> Enum.filter(fn t -> + # filter by `withdraw(uint256 amount)` signature + String.downcase(String.slice(Map.get(t, "input", ""), 0..9)) == @withdraw_method + end) + |> Enum.map(fn t -> Map.get(t, "hash") end) + + acc ++ hashes + end) + |> Enum.chunk_every(@eth_get_logs_range_size) + |> Enum.reduce([], fn hashes, acc -> + acc ++ get_receipt_logs(hashes, json_rpc_named_arguments, 100_000_000) + end) + |> filter_withdrawal_events(bone_withdraw) + end + + defp get_op_amounts(event) do + cond do + event.first_topic == @token_deposited_event -> + [amount, deposit_count] = decode_data(event.data, [{:uint, 256}, {:uint, 256}]) + {[amount], deposit_count} + + event.first_topic == @transfer_event -> + indexed_token_id = event.fourth_topic + + if is_nil(indexed_token_id) do + {decode_data(event.data, [{:uint, 256}]), 0} + else + {[quantity_to_integer(indexed_token_id)], 0} + end + + event.first_topic == @withdraw_event -> + [amount, _arg3, _arg4] = decode_data(event.data, [{:uint, 256}, {:uint, 256}, {:uint, 256}]) + {[amount], 0} + + true -> + {[nil], 0} + end + end + + defp get_op_erc1155_data(event) do + cond do + event.first_topic == @transfer_single_event -> + [id, amount] = decode_data(event.data, [{:uint, 256}, {:uint, 256}]) + {[id], [amount]} + + event.first_topic == @transfer_batch_event -> + [ids, amounts] = decode_data(event.data, [{:array, {:uint, 256}}, {:array, {:uint, 256}}]) + {ids, amounts} + + true -> + {[], []} + end + end + + # credo:disable-for-next-line /Complexity/ + defp get_op_user(event) do + cond do + event.first_topic == @transfer_event and event.third_topic == @empty_hash -> + truncate_address_hash(event.second_topic) + + event.first_topic == @transfer_event and event.second_topic == @empty_hash -> + truncate_address_hash(event.third_topic) + + event.first_topic == @withdraw_event -> + truncate_address_hash(event.third_topic) + + Enum.member?([@transfer_single_event, @transfer_batch_event], event.first_topic) and + event.fourth_topic == @empty_hash -> + truncate_address_hash(event.third_topic) + + Enum.member?([@transfer_single_event, @transfer_batch_event], event.first_topic) and + event.third_topic == @empty_hash -> + truncate_address_hash(event.fourth_topic) + + event.first_topic == @token_deposited_event -> + truncate_address_hash(event.fourth_topic) + end + end + + defp get_receipt_logs(tx_hashes, json_rpc_named_arguments, retries) do + reqs = + tx_hashes + |> Enum.with_index() + |> Enum.map(fn {hash, id} -> + request(%{ + id: id, + method: "eth_getTransactionReceipt", + params: [hash] + }) + end) + + error_message = &"eth_getTransactionReceipt failed. Error: #{inspect(&1)}" + + {:ok, receipts} = Helper.repeated_call(&json_rpc/2, [reqs, json_rpc_named_arguments], error_message, retries) + + receipts + |> Enum.map(&Receipt.elixir_to_logs(&1.result)) + |> List.flatten() + |> Logs.elixir_to_params() + end + + defp withdrawal?(event) do + cond do + event.first_topic == @withdraw_event -> + true + + event.first_topic == @transfer_event and event.third_topic == @empty_hash -> + true + + Enum.member?([@transfer_single_event, @transfer_batch_event], event.first_topic) and + event.fourth_topic == @empty_hash -> + true + + true -> + false + end + end + + defp prepare_operation(event, timestamps, weth) do + event = + event + |> Map.put(:first_topic, Helper.log_topic_to_string(event.first_topic)) + |> Map.put(:second_topic, Helper.log_topic_to_string(event.second_topic)) + |> Map.put(:third_topic, Helper.log_topic_to_string(event.third_topic)) + |> Map.put(:fourth_topic, Helper.log_topic_to_string(event.fourth_topic)) + + user = get_op_user(event) + + if user == burn_address_hash_string() do + [] + else + {amounts_or_ids, operation_id} = get_op_amounts(event) + {erc1155_ids, erc1155_amounts} = get_op_erc1155_data(event) + + l2_block_number = quantity_to_integer(event.block_number) + + {operation_type, timestamp} = + if withdrawal?(event) do + {:withdrawal, Map.get(timestamps, l2_block_number)} + else + {:deposit, nil} + end + + token_type = + cond do + Enum.member?([@token_deposited_event, @withdraw_event], event.first_topic) -> + "bone" + + event.first_topic == @transfer_event and String.downcase(event.address_hash) == weth -> + "eth" + + true -> + "other" + end + + Enum.map(amounts_or_ids, fn amount_or_id -> + %{ + user: user, + amount_or_id: amount_or_id, + erc1155_ids: if(Enum.empty?(erc1155_ids), do: nil, else: erc1155_ids), + erc1155_amounts: if(Enum.empty?(erc1155_amounts), do: nil, else: erc1155_amounts), + l2_transaction_hash: event.transaction_hash, + l2_block_number: l2_block_number, + l1_transaction_hash: @empty_hash, + operation_hash: calc_operation_hash(user, amount_or_id, erc1155_ids, erc1155_amounts, operation_id), + operation_type: operation_type, + token_type: token_type, + timestamp: timestamp + } + end) + end + end + + defp truncate_address_hash("0x000000000000000000000000" <> truncated_hash) do + "0x#{truncated_hash}" + end +end diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/transaction_batch.ex b/apps/indexer/lib/indexer/fetcher/zkevm/transaction_batch.ex index d59da1203e63..25220dd7dbb0 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/transaction_batch.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/transaction_batch.ex @@ -13,6 +13,7 @@ defmodule Indexer.Fetcher.Zkevm.TransactionBatch do alias Explorer.Chain alias Explorer.Chain.Events.Publisher alias Explorer.Chain.Zkevm.Reader + alias Indexer.Helper @zero_hash "0000000000000000000000000000000000000000000000000000000000000000" @@ -168,7 +169,7 @@ defmodule Indexer.Fetcher.Zkevm.TransactionBatch do error_message = &"Cannot call zkevm_getBatchByNumber for the batch range #{batch_start}..#{batch_end}. Error: #{inspect(&1)}" - {:ok, responses} = repeated_call(&json_rpc/2, [requests, json_rpc_named_arguments], error_message, 3) + {:ok, responses} = Helper.repeated_call(&json_rpc/2, [requests, json_rpc_named_arguments], error_message, 3) {sequence_hashes, verify_hashes} = responses @@ -248,13 +249,20 @@ defmodule Indexer.Fetcher.Zkevm.TransactionBatch do {[batch | batches], l2_txs ++ l2_txs_append, l1_txs, next_id, hash_to_id} end) - {:ok, _} = - Chain.import(%{ - zkevm_lifecycle_transactions: %{params: l1_txs_to_import}, - zkevm_transaction_batches: %{params: batches_to_import}, - zkevm_batch_transactions: %{params: l2_txs_to_import}, - timeout: :infinity - }) + # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise + import_options = + if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do + %{ + zkevm_lifecycle_transactions: %{params: l1_txs_to_import}, + zkevm_transaction_batches: %{params: batches_to_import}, + zkevm_batch_transactions: %{params: l2_txs_to_import}, + timeout: :infinity + } + else + %{} + end + + {:ok, _} = Chain.import(import_options) confirmed_batches = Enum.filter(batches_to_import, fn batch -> not is_nil(batch.sequence_id) and batch.sequence_id > 0 end) @@ -273,7 +281,7 @@ defmodule Indexer.Fetcher.Zkevm.TransactionBatch do error_message = &"Cannot call zkevm_batchNumber. Error: #{inspect(&1)}" - {:ok, responses} = repeated_call(&json_rpc/2, [requests, json_rpc_named_arguments], error_message, 3) + {:ok, responses} = Helper.repeated_call(&json_rpc/2, [requests, json_rpc_named_arguments], error_message, 3) latest_batch_number = Enum.find_value(responses, fn resp -> if resp.id == 0, do: quantity_to_integer(resp.result) end) @@ -310,23 +318,4 @@ defmodule Indexer.Fetcher.Zkevm.TransactionBatch do {nil, l1_txs, next_id, hash_to_id} end end - - defp repeated_call(func, args, error_message, retries_left) do - case apply(func, args) do - {:ok, _} = res -> - res - - {:error, message} = err -> - retries_left = retries_left - 1 - - if retries_left <= 0 do - Logger.error(error_message.(message)) - err - else - Logger.error("#{error_message.(message)} Retrying...") - :timer.sleep(3000) - repeated_call(func, args, error_message, retries_left) - end - end - end end diff --git a/apps/indexer/lib/indexer/helper.ex b/apps/indexer/lib/indexer/helper.ex index f0d1a3154e38..1c37af8f4589 100644 --- a/apps/indexer/lib/indexer/helper.ex +++ b/apps/indexer/lib/indexer/helper.ex @@ -3,6 +3,17 @@ defmodule Indexer.Helper do Auxiliary common functions for indexers. """ + require Logger + + import EthereumJSONRPC, + only: [ + fetch_block_number_by_tag: 2, + json_rpc: 2, + quantity_to_integer: 1, + request: 1 + ] + + alias EthereumJSONRPC.Block.ByNumber alias Explorer.Chain.Hash @spec address_hash_to_string(binary(), boolean()) :: binary() @@ -33,6 +44,149 @@ defmodule Indexer.Helper do false end + @doc """ + Fetches block number by its tag (e.g. `latest` or `safe`) using RPC request. + Performs a specified number of retries (up to) if the first attempt returns error. + """ + @spec get_block_number_by_tag(binary(), list(), non_neg_integer()) :: {:ok, non_neg_integer()} | {:error, atom()} + def get_block_number_by_tag(tag, json_rpc_named_arguments, retries \\ 3) do + error_message = &"Cannot fetch #{tag} block number. Error: #{inspect(&1)}" + repeated_call(&fetch_block_number_by_tag/2, [tag, json_rpc_named_arguments], error_message, retries) + end + + @doc """ + Fetches transaction data by its hash using RPC request. + Performs a specified number of retries (up to) if the first attempt returns error. + """ + @spec get_transaction_by_hash(binary() | nil, list(), non_neg_integer()) :: {:ok, any()} | {:error, any()} + def get_transaction_by_hash(hash, json_rpc_named_arguments, retries_left \\ 3) + + def get_transaction_by_hash(hash, _json_rpc_named_arguments, _retries_left) when is_nil(hash), do: {:ok, nil} + + def get_transaction_by_hash(hash, json_rpc_named_arguments, retries) do + req = + request(%{ + id: 0, + method: "eth_getTransactionByHash", + params: [hash] + }) + + error_message = &"eth_getTransactionByHash failed. Error: #{inspect(&1)}" + + repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) + end + + @doc """ + Prints a log of progress when handling something splitted to block chunks. + """ + @spec log_blocks_chunk_handling( + non_neg_integer(), + non_neg_integer(), + non_neg_integer(), + non_neg_integer(), + binary() | nil, + binary() + ) :: :ok + def log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, items_count, layer) do + is_start = is_nil(items_count) + + {type, found} = + if is_start do + {"Start", ""} + else + {"Finish", " Found #{items_count}."} + end + + target_range = + if chunk_start != start_block or chunk_end != end_block do + progress = + if is_start do + "" + else + percentage = + (chunk_end - start_block + 1) + |> Decimal.div(end_block - start_block + 1) + |> Decimal.mult(100) + |> Decimal.round(2) + |> Decimal.to_string() + + " Progress: #{percentage}%" + end + + " Target range: #{start_block}..#{end_block}.#{progress}" + else + "" + end + + if chunk_start == chunk_end do + Logger.info("#{type} handling #{layer} block ##{chunk_start}.#{found}#{target_range}") + else + Logger.info("#{type} handling #{layer} block range #{chunk_start}..#{chunk_end}.#{found}#{target_range}") + end + end + + @doc """ + Calls the given function with the given arguments + until it returns {:ok, any()} or the given attempts number is reached. + Pauses execution between invokes for 3 seconds. + """ + @spec repeated_call((... -> any()), list(), (... -> any()), non_neg_integer()) :: + {:ok, any()} | {:error, binary() | atom()} + def repeated_call(func, args, error_message, retries_left) do + case apply(func, args) do + {:ok, _} = res -> + res + + {:error, message} = err -> + retries_left = retries_left - 1 + + if retries_left <= 0 do + Logger.error(error_message.(message)) + err + else + Logger.error("#{error_message.(message)} Retrying...") + :timer.sleep(3000) + repeated_call(func, args, error_message, retries_left) + end + end + end + + @doc """ + Fetches block timestamp by its number using RPC request. + Performs a specified number of retries (up to) if the first attempt returns error. + """ + @spec get_block_timestamp_by_number(non_neg_integer(), list(), non_neg_integer()) :: + {:ok, non_neg_integer()} | {:error, any()} + def get_block_timestamp_by_number(number, json_rpc_named_arguments, retries \\ 3) do + func = &get_block_timestamp_by_number_inner/2 + args = [number, json_rpc_named_arguments] + error_message = &"Cannot fetch block ##{number} or its timestamp. Error: #{inspect(&1)}" + repeated_call(func, args, error_message, retries) + end + + defp get_block_timestamp_by_number_inner(number, json_rpc_named_arguments) do + result = + %{id: 0, number: number} + |> ByNumber.request(false) + |> json_rpc(json_rpc_named_arguments) + + with {:ok, block} <- result, + false <- is_nil(block), + timestamp <- Map.get(block, "timestamp"), + false <- is_nil(timestamp) do + {:ok, quantity_to_integer(timestamp)} + else + {:error, message} -> + {:error, message} + + true -> + {:error, "RPC returned nil."} + end + end + + @doc """ + Converts a log topic from Hash.Full representation to string one. + """ @spec log_topic_to_string(any()) :: binary() | nil def log_topic_to_string(topic) do if is_binary(topic) or is_nil(topic) do diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index 10a745512bd0..7dd38efb31a3 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -129,13 +129,19 @@ defmodule Indexer.Supervisor do {TokenUpdater.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, {ReplacedTransaction.Supervisor, [[memory_monitor: memory_monitor]]}, - {PolygonEdge.Supervisor, [[memory_monitor: memory_monitor]]}, - {Indexer.Fetcher.PolygonEdge.Deposit.Supervisor, [[memory_monitor: memory_monitor]]}, - {Indexer.Fetcher.PolygonEdge.DepositExecute.Supervisor, - [[memory_monitor: memory_monitor, json_rpc_named_arguments: json_rpc_named_arguments]]}, - {Indexer.Fetcher.PolygonEdge.Withdrawal.Supervisor, - [[memory_monitor: memory_monitor, json_rpc_named_arguments: json_rpc_named_arguments]]}, - {Indexer.Fetcher.PolygonEdge.WithdrawalExit.Supervisor, [[memory_monitor: memory_monitor]]}, + configure(PolygonEdge.Supervisor, [[memory_monitor: memory_monitor]]), + configure(Indexer.Fetcher.PolygonEdge.Deposit.Supervisor, [[memory_monitor: memory_monitor]]), + configure(Indexer.Fetcher.PolygonEdge.DepositExecute.Supervisor, [ + [memory_monitor: memory_monitor, json_rpc_named_arguments: json_rpc_named_arguments] + ]), + configure(Indexer.Fetcher.PolygonEdge.Withdrawal.Supervisor, [ + [memory_monitor: memory_monitor, json_rpc_named_arguments: json_rpc_named_arguments] + ]), + configure(Indexer.Fetcher.PolygonEdge.WithdrawalExit.Supervisor, [[memory_monitor: memory_monitor]]), + configure(Indexer.Fetcher.Shibarium.L2.Supervisor, [ + [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] + ]), + configure(Indexer.Fetcher.Shibarium.L1.Supervisor, [[memory_monitor: memory_monitor]]), configure(TransactionBatch.Supervisor, [ [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), diff --git a/apps/indexer/lib/indexer/transform/addresses.ex b/apps/indexer/lib/indexer/transform/addresses.ex index 5e3a0e227999..b46b0b4dc3a7 100644 --- a/apps/indexer/lib/indexer/transform/addresses.ex +++ b/apps/indexer/lib/indexer/transform/addresses.ex @@ -108,6 +108,11 @@ defmodule Indexer.Transform.Addresses do %{from: :address_hash, to: :hash} ] ], + shibarium_bridge_operations: [ + [ + %{from: :user, to: :hash} + ] + ], token_transfers: [ [ %{from: :block_number, to: :fetched_coin_balance_block_number}, @@ -414,6 +419,11 @@ defmodule Indexer.Transform.Addresses do required(:block_number) => non_neg_integer() } ], + optional(:shibarium_bridge_operations) => [ + %{ + required(:user) => String.t() + } + ], optional(:token_transfers) => [ %{ required(:from_address_hash) => String.t(), diff --git a/apps/indexer/lib/indexer/transform/shibarium/bridge.ex b/apps/indexer/lib/indexer/transform/shibarium/bridge.ex new file mode 100644 index 000000000000..950d187d8bff --- /dev/null +++ b/apps/indexer/lib/indexer/transform/shibarium/bridge.ex @@ -0,0 +1,99 @@ +defmodule Indexer.Transform.Shibarium.Bridge do + @moduledoc """ + Helper functions for transforming data for Shibarium Bridge operations. + """ + + require Logger + + import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] + + import Indexer.Fetcher.Shibarium.Helper, only: [prepare_insert_items: 2] + + import Indexer.Fetcher.Shibarium.L2, only: [withdraw_method_signature: 0] + + alias Indexer.Fetcher.Shibarium.L2 + alias Indexer.Helper + + @doc """ + Returns a list of operations given a list of blocks and their transactions. + """ + @spec parse(list(), list(), list()) :: list() + def parse(blocks, transactions_with_receipts, logs) do + prev_metadata = Logger.metadata() + Logger.metadata(fetcher: :shibarium_bridge_l2_realtime) + + items = + with false <- is_nil(Application.get_env(:indexer, Indexer.Fetcher.Shibarium.L2)[:start_block]), + false <- System.get_env("CHAIN_TYPE") != "shibarium", + child_chain = Application.get_env(:indexer, Indexer.Fetcher.Shibarium.L2)[:child_chain], + weth = Application.get_env(:indexer, Indexer.Fetcher.Shibarium.L2)[:weth], + bone_withdraw = Application.get_env(:indexer, Indexer.Fetcher.Shibarium.L2)[:bone_withdraw], + true <- Helper.address_correct?(child_chain), + true <- Helper.address_correct?(weth), + true <- Helper.address_correct?(bone_withdraw) do + child_chain = String.downcase(child_chain) + weth = String.downcase(weth) + bone_withdraw = String.downcase(bone_withdraw) + + block_numbers = Enum.map(blocks, fn block -> block.number end) + start_block = Enum.min(block_numbers) + end_block = Enum.max(block_numbers) + + Helper.log_blocks_chunk_handling(start_block, end_block, start_block, end_block, nil, "L2") + + deposit_transaction_hashes = + transactions_with_receipts + |> Enum.filter(fn tx -> tx.from_address_hash == burn_address_hash_string() end) + |> Enum.map(fn tx -> tx.hash end) + + deposit_events = + logs + |> Enum.filter(&Enum.member?(deposit_transaction_hashes, &1.transaction_hash)) + |> L2.filter_deposit_events(child_chain) + + withdrawal_transaction_hashes = + transactions_with_receipts + |> Enum.filter(fn tx -> + # filter by `withdraw(uint256 amount)` signature + String.downcase(String.slice(tx.input, 0..9)) == withdraw_method_signature() + end) + |> Enum.map(fn tx -> tx.hash end) + + withdrawal_events = + logs + |> Enum.filter(&Enum.member?(withdrawal_transaction_hashes, &1.transaction_hash)) + |> L2.filter_withdrawal_events(bone_withdraw) + + events = deposit_events ++ withdrawal_events + timestamps = Enum.reduce(blocks, %{}, fn block, acc -> Map.put(acc, block.number, block.timestamp) end) + + operations = L2.prepare_operations({events, timestamps}, weth) + items = prepare_insert_items(operations, L2) + + Helper.log_blocks_chunk_handling( + start_block, + end_block, + start_block, + end_block, + "#{Enum.count(operations)} L2 operation(s)", + "L2" + ) + + items + else + true -> + [] + + false -> + Logger.error( + "ChildChain or WETH or BoneWithdraw contract address is incorrect. Cannot use #{__MODULE__} for parsing logs." + ) + + [] + end + + Logger.reset_metadata(prev_metadata) + + items + end +end diff --git a/config/config_helper.exs b/config/config_helper.exs index d5f843bc3c70..645b20286c51 100644 --- a/config/config_helper.exs +++ b/config/config_helper.exs @@ -11,6 +11,7 @@ defmodule ConfigHelper do "polygon_edge" -> base_repos ++ [Explorer.Repo.PolygonEdge] "polygon_zkevm" -> base_repos ++ [Explorer.Repo.PolygonZkevm] "rsk" -> base_repos ++ [Explorer.Repo.RSK] + "shibarium" -> base_repos ++ [Explorer.Repo.Shibarium] "suave" -> base_repos ++ [Explorer.Repo.Suave] _ -> base_repos end diff --git a/config/runtime.exs b/config/runtime.exs index 624910593baf..f6494acbeb70 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -629,19 +629,17 @@ config :indexer, Indexer.Fetcher.Withdrawal.Supervisor, config :indexer, Indexer.Fetcher.Withdrawal, first_block: System.get_env("WITHDRAWALS_FIRST_BLOCK") -config :indexer, Indexer.Fetcher.PolygonEdge.Supervisor, disabled?: !(ConfigHelper.chain_type() == "polygon_edge") +config :indexer, Indexer.Fetcher.PolygonEdge.Supervisor, enabled: ConfigHelper.chain_type() == "polygon_edge" -config :indexer, Indexer.Fetcher.PolygonEdge.Deposit.Supervisor, - disabled?: !(ConfigHelper.chain_type() == "polygon_edge") +config :indexer, Indexer.Fetcher.PolygonEdge.Deposit.Supervisor, enabled: ConfigHelper.chain_type() == "polygon_edge" config :indexer, Indexer.Fetcher.PolygonEdge.DepositExecute.Supervisor, - disabled?: !(ConfigHelper.chain_type() == "polygon_edge") + enabled: ConfigHelper.chain_type() == "polygon_edge" -config :indexer, Indexer.Fetcher.PolygonEdge.Withdrawal.Supervisor, - disabled?: !(ConfigHelper.chain_type() == "polygon_edge") +config :indexer, Indexer.Fetcher.PolygonEdge.Withdrawal.Supervisor, enabled: ConfigHelper.chain_type() == "polygon_edge" config :indexer, Indexer.Fetcher.PolygonEdge.WithdrawalExit.Supervisor, - disabled?: !(ConfigHelper.chain_type() == "polygon_edge") + enabled: ConfigHelper.chain_type() == "polygon_edge" config :indexer, Indexer.Fetcher.PolygonEdge, polygon_edge_l1_rpc: System.get_env("INDEXER_POLYGON_EDGE_L1_RPC"), @@ -670,8 +668,7 @@ config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch, config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch.Supervisor, enabled: - System.get_env("CHAIN_TYPE", "ethereum") == "polygon_zkevm" && - ConfigHelper.parse_bool_env_var("INDEXER_ZKEVM_BATCHES_ENABLED") + ConfigHelper.chain_type() == "polygon_zkevm" && ConfigHelper.parse_bool_env_var("INDEXER_ZKEVM_BATCHES_ENABLED") config :indexer, Indexer.Fetcher.RootstockData.Supervisor, disabled?: @@ -683,6 +680,26 @@ config :indexer, Indexer.Fetcher.RootstockData, max_concurrency: ConfigHelper.parse_integer_env_var("INDEXER_ROOTSTOCK_DATA_FETCHER_CONCURRENCY", 5), db_batch_size: ConfigHelper.parse_integer_env_var("INDEXER_ROOTSTOCK_DATA_FETCHER_DB_BATCH_SIZE", 300) +config :indexer, Indexer.Fetcher.Shibarium.L1, + rpc: System.get_env("INDEXER_SHIBARIUM_L1_RPC"), + start_block: System.get_env("INDEXER_SHIBARIUM_L1_START_BLOCK"), + deposit_manager_proxy: System.get_env("INDEXER_SHIBARIUM_L1_DEPOSIT_MANAGER_CONTRACT"), + ether_predicate_proxy: System.get_env("INDEXER_SHIBARIUM_L1_ETHER_PREDICATE_CONTRACT"), + erc20_predicate_proxy: System.get_env("INDEXER_SHIBARIUM_L1_ERC20_PREDICATE_CONTRACT"), + erc721_predicate_proxy: System.get_env("INDEXER_SHIBARIUM_L1_ERC721_PREDICATE_CONTRACT"), + erc1155_predicate_proxy: System.get_env("INDEXER_SHIBARIUM_L1_ERC1155_PREDICATE_CONTRACT"), + withdraw_manager_proxy: System.get_env("INDEXER_SHIBARIUM_L1_WITHDRAW_MANAGER_CONTRACT") + +config :indexer, Indexer.Fetcher.Shibarium.L2, + start_block: System.get_env("INDEXER_SHIBARIUM_L2_START_BLOCK"), + child_chain: System.get_env("INDEXER_SHIBARIUM_L2_CHILD_CHAIN_CONTRACT"), + weth: System.get_env("INDEXER_SHIBARIUM_L2_WETH_CONTRACT"), + bone_withdraw: System.get_env("INDEXER_SHIBARIUM_L2_BONE_WITHDRAW_CONTRACT") + +config :indexer, Indexer.Fetcher.Shibarium.L1.Supervisor, enabled: ConfigHelper.chain_type() == "shibarium" + +config :indexer, Indexer.Fetcher.Shibarium.L2.Supervisor, enabled: ConfigHelper.chain_type() == "shibarium" + Code.require_file("#{config_env()}.exs", "config/runtime") for config <- "../apps/*/config/runtime/#{config_env()}.exs" |> Path.expand(__DIR__) |> Path.wildcard() do diff --git a/config/runtime/dev.exs b/config/runtime/dev.exs index 3e4df28fb71e..d0e69f6937d4 100644 --- a/config/runtime/dev.exs +++ b/config/runtime/dev.exs @@ -108,6 +108,13 @@ config :explorer, Explorer.Repo.Suave, url: ExplorerConfigHelper.get_suave_db_url(), pool_size: 1 +# Configure Shibarium database +config :explorer, Explorer.Repo.Shibarium, + database: database, + hostname: hostname, + url: System.get_env("DATABASE_URL"), + pool_size: 1 + variant = Variant.get() Code.require_file("#{variant}.exs", "apps/explorer/config/dev") diff --git a/config/runtime/prod.exs b/config/runtime/prod.exs index ce4602522a1d..7abd88768122 100644 --- a/config/runtime/prod.exs +++ b/config/runtime/prod.exs @@ -81,6 +81,12 @@ config :explorer, Explorer.Repo.Suave, pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() +# Configures Shibarium database +config :explorer, Explorer.Repo.Shibarium, + url: System.get_env("DATABASE_URL"), + pool_size: 1, + ssl: ExplorerConfigHelper.ssl_enabled?() + variant = Variant.get() Code.require_file("#{variant}.exs", "apps/explorer/config/prod") diff --git a/cspell.json b/cspell.json index a88ef9a7a95f..1712455438d7 100644 --- a/cspell.json +++ b/cspell.json @@ -159,6 +159,7 @@ "etimedout", "eveem", "evenodd", + "exitor", "explorable", "exponention", "extcodehash", @@ -407,6 +408,7 @@ "Sérgio", "sharelock", "sharelocks", + "shibarium", "shortdoc", "shortify", "SJONRPC", From f63668cd58496361f883217eae9fbd3cf48f64d7 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Wed, 24 Jan 2024 13:06:00 +0300 Subject: [PATCH 327/607] Change internal txs tracer type to opcode for Hardhat node (#9178) --- CHANGELOG.md | 1 + docker-compose/envs/common-blockscout.env | 10 +++++----- docker-compose/ganache.yml | 3 +++ docker-compose/hardhat-network.yml | 5 +++++ 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d376041de47a..910d7353755f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - [#9229](https://github.com/blockscout/blockscout/pull/9229) - Add missing filter to txlist query - [#9139](https://github.com/blockscout/blockscout/pull/9139) - TokenBalanceOnDemand fixes +- [#9178](https://github.com/blockscout/blockscout/pull/9178) - Change internal txs tracer type to opcode for Hardhat node - [#9125](https://github.com/blockscout/blockscout/pull/9125) - Fix Explorer.Chain.Cache.GasPriceOracle.merge_fees - [#9124](https://github.com/blockscout/blockscout/pull/9124) - EIP-1167 display multiple sources of implementation - [#9110](https://github.com/blockscout/blockscout/pull/9110) - Improve update_in in gas tracker diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index d355548e60ce..f55cf1f1099c 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -14,7 +14,7 @@ LOGO=/images/blockscout_logo.svg # ETHEREUM_JSONRPC_WS_URL= ETHEREUM_JSONRPC_TRANSPORT=http ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES=false -#ETHEREUM_JSONRPC_ARCHIVE_BALANCES_WINDOW=200 +# ETHEREUM_JSONRPC_ARCHIVE_BALANCES_WINDOW=200 # ETHEREUM_JSONRPC_HTTP_HEADERS= # ETHEREUM_JSONRPC_WAIT_PER_TIMEOUT= # ETHEREUM_JSONRPC_GETH_TRACE_BY_BLOCK= @@ -57,7 +57,7 @@ ECTO_USE_SSL=false # SPANDEX_SYNC_THRESHOLD= HEART_BEAT_TIMEOUT=30 # HEART_COMMAND= -#BLOCKSCOUT_VERSION= +# BLOCKSCOUT_VERSION= RELEASE_LINK= BLOCK_TRANSFORMER=base # GRAPHIQL_TRANSACTION= @@ -101,7 +101,7 @@ DISABLE_WEBAPP=false API_V2_ENABLED=true API_V1_READ_METHODS_DISABLED=false API_V1_WRITE_METHODS_DISABLED=false -#API_RATE_LIMIT_DISABLED=true +# API_RATE_LIMIT_DISABLED=true # API_SENSITIVE_ENDPOINTS_KEY= API_RATE_LIMIT_TIME_INTERVAL=1s API_RATE_LIMIT_BY_IP_TIME_INTERVAL=5m @@ -226,8 +226,8 @@ JSON_RPC= API_RATE_LIMIT_UI_V2_TOKEN_TTL_IN_SECONDS=18000 FETCH_REWARDS_WAY=trace_block MICROSERVICE_SC_VERIFIER_ENABLED=true -#MICROSERVICE_SC_VERIFIER_URL=http://smart-contract-verifier:8050/ -#MICROSERVICE_SC_VERIFIER_TYPE=sc_verifier +# MICROSERVICE_SC_VERIFIER_URL=http://smart-contract-verifier:8050/ +# MICROSERVICE_SC_VERIFIER_TYPE=sc_verifier MICROSERVICE_SC_VERIFIER_URL=https://eth-bytecode-db.services.blockscout.com/ MICROSERVICE_SC_VERIFIER_TYPE=eth_bytecode_db MICROSERVICE_ETH_BYTECODE_DB_INTERVAL_BETWEEN_LOOKUPS=10m diff --git a/docker-compose/ganache.yml b/docker-compose/ganache.yml index 0aa51fce16a2..6edb596669f4 100644 --- a/docker-compose/ganache.yml +++ b/docker-compose/ganache.yml @@ -48,6 +48,9 @@ services: extends: file: ./services/frontend.yml service: frontend + environment: + NEXT_PUBLIC_NETWORK_ID: '1337' + NEXT_PUBLIC_NETWORK_RPC_URL: http://host.docker.internal:8545/ stats-db-init: extends: diff --git a/docker-compose/hardhat-network.yml b/docker-compose/hardhat-network.yml index 518a3b2af73b..b76b254a3e6f 100644 --- a/docker-compose/hardhat-network.yml +++ b/docker-compose/hardhat-network.yml @@ -29,6 +29,8 @@ services: ETHEREUM_JSONRPC_VARIANT: 'geth' ETHEREUM_JSONRPC_WS_URL: ws://host.docker.internal:8545/ INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER: 'true' + INDEXER_INTERNAL_TRANSACTIONS_TRACER_TYPE: 'opcode' + CHAIN_ID: '31337' visualizer: extends: @@ -46,6 +48,9 @@ services: extends: file: ./services/frontend.yml service: frontend + environment: + NEXT_PUBLIC_NETWORK_ID: '31337' + NEXT_PUBLIC_NETWORK_RPC_URL: http://host.docker.internal:8545/ stats-db-init: extends: From d3209f0e2e7176383af2aad813055e031b3a9966 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop <105209995+Qwerty5Uiop@users.noreply.github.com> Date: Wed, 24 Jan 2024 16:20:31 +0400 Subject: [PATCH 328/607] Handle nil token_ids in token transfers on render (#9143) --- CHANGELOG.md | 1 + .../views/api/v2/transaction_view.ex | 15 ++++++++++++--- .../lib/explorer/account/notifier/summary.ex | 4 +++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 910d7353755f..4d3dd5a34dfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ### Fixes - [#9229](https://github.com/blockscout/blockscout/pull/9229) - Add missing filter to txlist query +- [#9143](https://github.com/blockscout/blockscout/pull/9143) - Handle nil token_ids in token transfers on render - [#9139](https://github.com/blockscout/blockscout/pull/9139) - TokenBalanceOnDemand fixes - [#9178](https://github.com/blockscout/blockscout/pull/9178) - Change internal txs tracer type to opcode for Hardhat node - [#9125](https://github.com/blockscout/blockscout/pull/9125) - Fix Explorer.Chain.Cache.GasPriceOracle.merge_fees diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index 0db3dc1a8e68..34c340124950 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -246,16 +246,25 @@ defmodule BlockScoutWeb.API.V2.TransactionView do } end + # credo:disable-for-next-line /Complexity/ def prepare_token_transfer_total(token_transfer) do case TokensHelper.token_transfer_amount_for_api(token_transfer) do {:ok, :erc721_instance} -> - %{"token_id" => List.first(token_transfer.token_ids)} + %{"token_id" => token_transfer.token_ids && List.first(token_transfer.token_ids)} {:ok, :erc1155_instance, value, decimals} -> - %{"token_id" => List.first(token_transfer.token_ids), "value" => value, "decimals" => decimals} + %{ + "token_id" => token_transfer.token_ids && List.first(token_transfer.token_ids), + "value" => value, + "decimals" => decimals + } {:ok, :erc1155_instance, values, token_ids, decimals} -> - %{"token_id" => List.first(token_ids), "value" => List.first(values), "decimals" => decimals} + %{ + "token_id" => token_ids && List.first(token_ids), + "value" => values && List.first(values), + "decimals" => decimals + } {:ok, value, decimals} -> %{"value" => value, "decimals" => decimals} diff --git a/apps/explorer/lib/explorer/account/notifier/summary.ex b/apps/explorer/lib/explorer/account/notifier/summary.ex index db39423bb48c..09f370078c20 100644 --- a/apps/explorer/lib/explorer/account/notifier/summary.ex +++ b/apps/explorer/lib/explorer/account/notifier/summary.ex @@ -132,7 +132,7 @@ defmodule Explorer.Account.Notifier.Summary do from_address_hash: transfer.from_address_hash, to_address_hash: transfer.to_address_hash, block_number: transfer.block_number, - subject: to_string(List.first(transfer.token_ids)), + subject: to_string(transfer.token_ids && List.first(transfer.token_ids)), tx_fee: fee(transaction), name: transfer.token.name, type: transfer.token.type @@ -191,6 +191,8 @@ defmodule Explorer.Account.Notifier.Summary do ) end + def token_ids(%Chain.TokenTransfer{token_ids: nil}), do: "" + def token_ids(%Chain.TokenTransfer{token_ids: token_ids}) do Enum.map_join(token_ids, ", ", fn id -> to_string(id) end) end From ecde63be6c362a9e15b5a563869ca373f44a91f2 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Wed, 24 Jan 2024 20:19:22 +0300 Subject: [PATCH 329/607] Add `MARKET_HISTORY_FETCH_INTERVAL` env (#9197) --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/market/history/cataloger.ex | 7 +++---- config/runtime.exs | 4 +++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d3dd5a34dfb..56c6a4a8b7ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ ### Chore - [#9198](https://github.com/blockscout/blockscout/pull/9198) - Make Postgres@15 default option +- [#9197](https://github.com/blockscout/blockscout/pull/9197) - Add `MARKET_HISTORY_FETCH_INTERVAL` env - [#9196](https://github.com/blockscout/blockscout/pull/9196) - Compatibility with docker-compose 2.24 - [#9193](https://github.com/blockscout/blockscout/pull/9193) - Equalize elixir stack versions diff --git a/apps/explorer/lib/explorer/market/history/cataloger.ex b/apps/explorer/lib/explorer/market/history/cataloger.ex index f3c73de23d57..ad53c64ff7ed 100644 --- a/apps/explorer/lib/explorer/market/history/cataloger.ex +++ b/apps/explorer/lib/explorer/market/history/cataloger.ex @@ -4,10 +4,9 @@ defmodule Explorer.Market.History.Cataloger do Market grabs the last 365 day's worth of market history for the configured coin in the explorer. Once that data is fetched, current day's values are - checked every 60 minutes. Additionally, failed requests to the history - source will follow exponential backoff `100ms * 2^(n+1)` where `n` is the - number of failed requests. - + checked every `MARKET_HISTORY_FETCH_INTERVAL` or every 60 minutes by default. + Additionally, failed requests to the history source will follow exponential + backoff `100ms * 2^(n+1)` where `n` is the number of failed requests. """ use GenServer diff --git a/config/runtime.exs b/config/runtime.exs index f6494acbeb70..960c6bf4dc7e 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -312,7 +312,9 @@ config :explorer, Explorer.ExchangeRates.TokenExchangeRates, refetch_interval: ConfigHelper.parse_time_env_var("TOKEN_EXCHANGE_RATE_REFETCH_INTERVAL", "1h"), max_batch_size: ConfigHelper.parse_integer_env_var("TOKEN_EXCHANGE_RATE_MAX_BATCH_SIZE", 150) -config :explorer, Explorer.Market.History.Cataloger, enabled: !disable_indexer? && !disable_exchange_rates? +config :explorer, Explorer.Market.History.Cataloger, + enabled: !disable_indexer? && !disable_exchange_rates?, + history_fetch_interval: ConfigHelper.parse_time_env_var("MARKET_HISTORY_FETCH_INTERVAL", "1h") config :explorer, Explorer.Chain.Transaction, suave_bid_contracts: System.get_env("SUAVE_BID_CONTRACTS", "") From a44be8f21866ea9605c17482711137e2108aaca3 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Thu, 25 Jan 2024 00:20:12 +0400 Subject: [PATCH 330/607] feat: add basic blob fetcher tests --- .../lib/explorer/chain/beacon/reader.ex | 2 +- .../explorer/chain/beacon/reader_test.exs | 7 + apps/explorer/test/support/factory.ex | 8 +- apps/indexer/config/runtime/test.exs | 3 + .../lib/indexer/fetcher/beacon/client.ex | 2 +- .../bound_interval_supervisor_test.exs | 11 +- .../indexer/block/catchup/fetcher_test.exs | 16 +- .../test/indexer/block/fetcher_test.exs | 9 - .../indexer/block/realtime/fetcher_test.exs | 19 -- .../test/indexer/fetcher/beacon/blob_test.exs | 170 ++++++++++++++++++ 10 files changed, 189 insertions(+), 58 deletions(-) create mode 100644 apps/explorer/test/explorer/chain/beacon/reader_test.exs create mode 100644 apps/indexer/test/indexer/fetcher/beacon/blob_test.exs diff --git a/apps/explorer/lib/explorer/chain/beacon/reader.ex b/apps/explorer/lib/explorer/chain/beacon/reader.ex index b40ab894ab95..c90e2f08475d 100644 --- a/apps/explorer/lib/explorer/chain/beacon/reader.ex +++ b/apps/explorer/lib/explorer/chain/beacon/reader.ex @@ -101,7 +101,7 @@ defmodule Explorer.Chain.Beacon.Reader do """ @spec stream_missed_blob_transactions_timestamps( initial :: accumulator, - reducer :: (entry :: Hash.Address.t(), accumulator -> accumulator), + reducer :: (entry :: DateTime.t(), accumulator -> accumulator), min_block :: integer() | nil, max_block :: integer() | nil, options :: [] diff --git a/apps/explorer/test/explorer/chain/beacon/reader_test.exs b/apps/explorer/test/explorer/chain/beacon/reader_test.exs new file mode 100644 index 000000000000..e3f9d07baacb --- /dev/null +++ b/apps/explorer/test/explorer/chain/beacon/reader_test.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Chain.Beacon.ReaderTest do + use Explorer.DataCase + + alias Explorer.Chain.Beacon.Reader + + doctest Reader +end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 5ab4e0dad0b7..547b56831144 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -1099,16 +1099,12 @@ defmodule Explorer.Factory do end def blob_transaction_factory do - blob = build(:blob) - transaction = build(:transaction) - %BlobTransaction{ - hash: transaction.hash, - transaction: transaction, + hash: insert(:transaction) |> with_block() |> Map.get(:hash), max_fee_per_blob_gas: Decimal.new(1_000_000_000), blob_gas_price: Decimal.new(1_000_000_000), blob_gas_used: Decimal.new(131_072), - blob_versioned_hashes: [blob.hash] + blob_versioned_hashes: [] } end diff --git a/apps/indexer/config/runtime/test.exs b/apps/indexer/config/runtime/test.exs index e2043f6c1435..7c9daee034fc 100644 --- a/apps/indexer/config/runtime/test.exs +++ b/apps/indexer/config/runtime/test.exs @@ -2,6 +2,9 @@ import Config alias EthereumJSONRPC.Variant +config :indexer, Indexer.Fetcher.Beacon.Blob.Supervisor, disabled?: true +config :indexer, Indexer.Fetcher.Beacon.Blob, start_block: 0 + variant = Variant.get() Code.require_file("#{variant}.exs", "#{__DIR__}/../../../explorer/config/test") diff --git a/apps/indexer/lib/indexer/fetcher/beacon/client.ex b/apps/indexer/lib/indexer/fetcher/beacon/client.ex index 4c97d20089bc..92ac93c60f46 100644 --- a/apps/indexer/lib/indexer/fetcher/beacon/client.ex +++ b/apps/indexer/lib/indexer/fetcher/beacon/client.ex @@ -8,7 +8,7 @@ defmodule Indexer.Fetcher.Beacon.Client do @request_error_msg "Error while sending request to beacon rpc" def http_get_request(url) do - case HTTPoison.get(url) do + case Application.get_env(:explorer, :http_adapter).get(url) do {:ok, %Response{body: body, status_code: 200}} -> Jason.decode(body) diff --git a/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs b/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs index 0d33ad553fe1..774ebfe3a5ab 100644 --- a/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs +++ b/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs @@ -11,7 +11,6 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do alias Indexer.BoundInterval alias Indexer.Block.Catchup alias Indexer.Block.Catchup.MissingRangesCollector - alias Indexer.Fetcher.Beacon.Blob alias Indexer.Fetcher.{ CoinBalance, @@ -33,14 +32,8 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do describe "start_link/1" do setup do - initial_block_ranges = Application.get_env(:indexer, :block_ranges) - initial_blob_disabled = Application.get_env(:indexer, Blob.Supervisor)[:disabled?] - Application.put_env(:indexer, Blob.Supervisor, disabled?: true) - - on_exit(fn -> - Application.put_env(:indexer, :block_ranges, initial_block_ranges) - Application.put_env(:indexer, Blob.Supervisor, disabled?: initial_blob_disabled) - end) + initial_env = Application.get_env(:indexer, :block_ranges) + on_exit(fn -> Application.put_env(:indexer, :block_ranges, initial_env) end) end # See https://github.com/poanetwork/blockscout/issues/597 diff --git a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs index 312304dbd408..93f687907ca0 100644 --- a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs @@ -13,7 +13,6 @@ defmodule Indexer.Block.Catchup.FetcherTest do alias Indexer.Block alias Indexer.Block.Catchup.Fetcher alias Indexer.Block.Catchup.MissingRangesCollector - alias Indexer.Fetcher.Beacon.Blob alias Indexer.Fetcher.{BlockReward, CoinBalance, InternalTransaction, Token, TokenBalance, UncleBlock} @moduletag capture_log: true @@ -38,14 +37,11 @@ defmodule Indexer.Block.Catchup.FetcherTest do describe "import/1" do setup do - initial_last_block = Application.get_env(:indexer, :last_block) - initial_blob_disabled = Application.get_env(:indexer, Blob.Supervisor)[:disabled?] + configuration = Application.get_env(:indexer, :last_block) Application.put_env(:indexer, :last_block, 0) - Application.put_env(:indexer, Blob.Supervisor, disabled?: true) on_exit(fn -> - Application.put_env(:indexer, :last_block, initial_last_block) - Application.put_env(:indexer, Blob.Supervisor, disabled?: initial_blob_disabled) + Application.put_env(:indexer, :last_block, configuration) end) end @@ -143,13 +139,7 @@ defmodule Indexer.Block.Catchup.FetcherTest do describe "task/1" do setup do initial_env = Application.get_env(:indexer, :block_ranges) - initial_blob_disabled = Application.get_env(:indexer, Blob.Supervisor)[:disabled?] - Application.put_env(:indexer, Blob.Supervisor, disabled?: true) - - on_exit(fn -> - Application.put_env(:indexer, :block_ranges, initial_env) - Application.put_env(:indexer, Blob.Supervisor, disabled?: initial_blob_disabled) - end) + on_exit(fn -> Application.put_env(:indexer, :block_ranges, initial_env) end) end test "ignores fetched beneficiaries with different hash for same number", %{ diff --git a/apps/indexer/test/indexer/block/fetcher_test.exs b/apps/indexer/test/indexer/block/fetcher_test.exs index 54124a1025f5..0498cfd37ecb 100644 --- a/apps/indexer/test/indexer/block/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/fetcher_test.exs @@ -11,8 +11,6 @@ defmodule Indexer.Block.FetcherTest do alias Indexer.Block.Fetcher alias Indexer.BufferedTask - alias Indexer.Fetcher.Beacon.Blob - alias Indexer.Fetcher.{ CoinBalance, ContractCode, @@ -62,13 +60,6 @@ defmodule Indexer.Block.FetcherTest do block_fetcher: %Fetcher{json_rpc_named_arguments: json_rpc_named_arguments} ) - initial_blob_disabled = Application.get_env(:indexer, Blob.Supervisor)[:disabled?] - Application.put_env(:indexer, Blob.Supervisor, disabled?: true) - - on_exit(fn -> - Application.put_env(:indexer, Blob.Supervisor, disabled?: initial_blob_disabled) - end) - %{ block_fetcher: %Fetcher{ broadcast: false, diff --git a/apps/indexer/test/indexer/block/realtime/fetcher_test.exs b/apps/indexer/test/indexer/block/realtime/fetcher_test.exs index 9685788d78ce..9d0459e915bc 100644 --- a/apps/indexer/test/indexer/block/realtime/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/realtime/fetcher_test.exs @@ -8,7 +8,6 @@ defmodule Indexer.Block.Realtime.FetcherTest do alias Explorer.Chain.{Address, Transaction, Wei} alias Indexer.Block.Catchup.Sequence alias Indexer.Block.Realtime - alias Indexer.Fetcher.Beacon.Blob alias Indexer.Fetcher.{ContractCode, InternalTransaction, ReplacedTransaction, Token, TokenBalance, UncleBlock} @moduletag capture_log: true @@ -42,15 +41,6 @@ defmodule Indexer.Block.Realtime.FetcherTest do end describe "Indexer.Block.Fetcher.fetch_and_import_range/1" do - setup do - initial_blob_disabled = Application.get_env(:indexer, Blob.Supervisor)[:disabled?] - Application.put_env(:indexer, Blob.Supervisor, disabled?: true) - - on_exit(fn -> - Application.put_env(:indexer, Blob.Supervisor, disabled?: initial_blob_disabled) - end) - end - @tag :no_geth test "in range with internal transactions", %{ block_fetcher: %Indexer.Block.Fetcher{} = block_fetcher, @@ -1067,15 +1057,6 @@ defmodule Indexer.Block.Realtime.FetcherTest do end describe "start_fetch_and_import" do - setup do - initial_blob_disabled = Application.get_env(:indexer, Blob.Supervisor)[:disabled?] - Application.put_env(:indexer, Blob.Supervisor, disabled?: true) - - on_exit(fn -> - Application.put_env(:indexer, Blob.Supervisor, disabled?: initial_blob_disabled) - end) - end - @tag :no_geth test "reorg", %{ block_fetcher: block_fetcher, diff --git a/apps/indexer/test/indexer/fetcher/beacon/blob_test.exs b/apps/indexer/test/indexer/fetcher/beacon/blob_test.exs new file mode 100644 index 000000000000..bf51708221de --- /dev/null +++ b/apps/indexer/test/indexer/fetcher/beacon/blob_test.exs @@ -0,0 +1,170 @@ +defmodule Indexer.Fetcher.Beacon.BlobTest do + use Explorer.DataCase, async: false + + import Mox + + alias Explorer.Chain.Transaction + alias Explorer.Chain.Beacon.{Blob, Reader} + alias Indexer.Fetcher.Beacon.Blob.Supervisor, as: BlobSupervisor + + setup :verify_on_exit! + setup :set_mox_global + + if Application.compile_env(:explorer, :chain_type) == "ethereum" do + describe "init/1" do + setup do + initial_env = Application.get_env(:indexer, BlobSupervisor) + Application.put_env(:indexer, BlobSupervisor, initial_env |> Keyword.put(:disabled?, false)) + + on_exit(fn -> + Application.put_env(:indexer, BlobSupervisor, initial_env) + end) + end + + test "fetches all missed blob transactions" do + {:ok, now, _} = DateTime.from_iso8601("2024-01-24 00:00:00Z") + block_a = insert(:block, timestamp: now) + block_b = insert(:block, timestamp: now |> Timex.shift(seconds: -120)) + block_c = insert(:block, timestamp: now |> Timex.shift(seconds: -240)) + + blob_a = build(:blob) + blob_b = build(:blob) + blob_c = build(:blob) + blob_d = insert(:blob) + + %Transaction{hash: transaction_a_hash} = insert(:transaction, type: 3) |> with_block(block_a) + %Transaction{hash: transaction_b_hash} = insert(:transaction, type: 3) |> with_block(block_b) + %Transaction{hash: transaction_c_hash} = insert(:transaction, type: 3) |> with_block(block_c) + + insert(:blob_transaction, hash: transaction_a_hash, blob_versioned_hashes: [blob_a.hash, blob_b.hash]) + insert(:blob_transaction, hash: transaction_b_hash, blob_versioned_hashes: [blob_c.hash]) + insert(:blob_transaction, hash: transaction_c_hash, blob_versioned_hashes: [blob_d.hash]) + + assert {:error, :not_found} = Reader.blob(blob_a.hash) + assert {:error, :not_found} = Reader.blob(blob_b.hash) + assert {:error, :not_found} = Reader.blob(blob_c.hash) + assert {:ok, _} = Reader.blob(blob_d.hash) + + Application.put_env(:explorer, :http_adapter, Explorer.Mox.HTTPoison) + + result_ab = """ + { + "data": [ + { + "index": "0", + "blob": "#{to_string(blob_a.blob_data)}", + "kzg_commitment": "#{to_string(blob_a.kzg_commitment)}", + "kzg_proof": "#{to_string(blob_a.kzg_proof)}" + }, + { + "index": "1", + "blob": "#{to_string(blob_b.blob_data)}", + "kzg_commitment": "#{to_string(blob_b.kzg_commitment)}", + "kzg_proof": "#{to_string(blob_b.kzg_proof)}" + } + ] + } + """ + + result_c = """ + { + "data": [ + { + "index": "0", + "blob": "#{to_string(blob_c.blob_data)}", + "kzg_commitment": "#{to_string(blob_c.kzg_commitment)}", + "kzg_proof": "#{to_string(blob_c.kzg_proof)}" + } + ] + } + """ + + Explorer.Mox.HTTPoison + |> expect(:get, 2, fn url -> + case url do + "http://localhost:5052/eth/v1/beacon/blob_sidecars/8269188" -> + {:ok, %HTTPoison.Response{status_code: 200, body: result_c}} + + "http://localhost:5052/eth/v1/beacon/blob_sidecars/8269198" -> + {:ok, %HTTPoison.Response{status_code: 200, body: result_ab}} + end + end) + + BlobSupervisor.Case.start_supervised!() + + wait_for_results(fn -> + Repo.one!(from(blob in Blob, where: blob.hash == ^blob_a.hash)) + end) + + assert {:ok, _} = Reader.blob(blob_a.hash) + assert {:ok, _} = Reader.blob(blob_b.hash) + assert {:ok, _} = Reader.blob(blob_c.hash) + assert {:ok, _} = Reader.blob(blob_d.hash) + + Application.put_env(:explorer, :http_adapter, HTTPoison) + end + end + + describe "async_fetch/1" do + setup do + initial_env = Application.get_env(:indexer, BlobSupervisor) + Application.put_env(:indexer, BlobSupervisor, initial_env |> Keyword.put(:disabled?, false)) + + on_exit(fn -> + Application.put_env(:indexer, BlobSupervisor, initial_env) + end) + end + + test "fetches blobs for block timestamp" do + Application.put_env(:explorer, :http_adapter, Explorer.Mox.HTTPoison) + + {:ok, now, _} = DateTime.from_iso8601("2024-01-24 00:00:00Z") + block_a = insert(:block, timestamp: now) + + %Blob{ + hash: blob_hash_a, + blob_data: blob_data_a, + kzg_commitment: kzg_commitment_a, + kzg_proof: kzg_proof_a + } = build(:blob) + + result_a = """ + { + "data": [ + { + "index": "0", + "blob": "#{to_string(blob_data_a)}", + "kzg_commitment": "#{to_string(kzg_commitment_a)}", + "kzg_proof": "#{to_string(kzg_proof_a)}" + } + ] + } + """ + + Explorer.Mox.HTTPoison + |> expect(:get, fn "http://localhost:5052/eth/v1/beacon/blob_sidecars/8269198" -> + {:ok, %HTTPoison.Response{status_code: 200, body: result_a}} + end) + + BlobSupervisor.Case.start_supervised!() + + assert :ok = Indexer.Fetcher.Beacon.Blob.async_fetch([block_a.timestamp]) + + wait_for_results(fn -> + Repo.one!(from(blob in Blob, where: blob.hash == ^blob_hash_a)) + end) + + assert {:ok, blob} = Reader.blob(blob_hash_a) + + assert %{ + hash: ^blob_hash_a, + blob_data: ^blob_data_a, + kzg_commitment: ^kzg_commitment_a, + kzg_proof: ^kzg_proof_a + } = blob + + Application.put_env(:explorer, :http_adapter, HTTPoison) + end + end + end +end From 5b181b0a5cbb79cdfc551984e4060a20a274265a Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Thu, 25 Jan 2024 01:15:50 +0400 Subject: [PATCH 331/607] fix: hide doctest behind chain type --- apps/explorer/test/explorer/chain/beacon/reader_test.exs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/explorer/test/explorer/chain/beacon/reader_test.exs b/apps/explorer/test/explorer/chain/beacon/reader_test.exs index e3f9d07baacb..d6633364c231 100644 --- a/apps/explorer/test/explorer/chain/beacon/reader_test.exs +++ b/apps/explorer/test/explorer/chain/beacon/reader_test.exs @@ -3,5 +3,7 @@ defmodule Explorer.Chain.Beacon.ReaderTest do alias Explorer.Chain.Beacon.Reader - doctest Reader + if Application.compile_env(:explorer, :chain_type) == "ethereum" do + doctest Reader + end end From fbc943227f3c62c53cb6c68860baddc0b4de6bf7 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 25 Jan 2024 09:08:55 +0300 Subject: [PATCH 332/607] Fix flickering test --- .../controllers/api/v2/smart_contract_controller_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index c1a0011c5bb0..7a7d9918bad7 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -382,7 +382,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "is_fully_verified" => false, "is_verified_via_sourcify" => false, "is_vyper_contract" => implementation_contract.is_vyper_contract, - "minimal_proxy_address_hash" => "0x" <> implementation_contract_address_hash_string, + "minimal_proxy_address_hash" => Address.checksum("0x" <> implementation_contract_address_hash_string), "sourcify_repo_url" => nil, "can_be_visualized_via_sol2uml" => false, "name" => implementation_contract && implementation_contract.name, From d06d4f3cab9ada7b9cdba5688ecfc1903a5011e2 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 25 Jan 2024 09:10:13 +0300 Subject: [PATCH 333/607] Fix log decoding bug (#9241) * Fix log decoding bug * Add regression test * Process review comment --- CHANGELOG.md | 1 + .../views/api/v2/transaction_view_test.exs | 91 +++++++++++++++++++ apps/explorer/lib/explorer/chain/log.ex | 7 +- 3 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 apps/block_scout_web/test/block_scout_web/views/api/v2/transaction_view_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 56c6a4a8b7ac..c0c691c0a21a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Fixes +- [#9241](https://github.com/blockscout/blockscout/pull/9241) - Fix log decoding bug - [#9229](https://github.com/blockscout/blockscout/pull/9229) - Add missing filter to txlist query - [#9143](https://github.com/blockscout/blockscout/pull/9143) - Handle nil token_ids in token transfers on render - [#9139](https://github.com/blockscout/blockscout/pull/9139) - TokenBalanceOnDemand fixes diff --git a/apps/block_scout_web/test/block_scout_web/views/api/v2/transaction_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/api/v2/transaction_view_test.exs new file mode 100644 index 000000000000..a5aa2e63fa28 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/api/v2/transaction_view_test.exs @@ -0,0 +1,91 @@ +defmodule BlockScoutWeb.API.V2.TransactionViewTest do + use BlockScoutWeb.ConnCase, async: true + + import Mox + + alias BlockScoutWeb.API.V2.TransactionView + alias Explorer.Repo + + describe "decode_logs/2" do + test "doesn't use decoding candidate event with different 2nd, 3d or 4th topic" do + insert(:contract_method, + identifier: Base.decode16!("d20a68b2", case: :lower), + abi: %{ + "name" => "OptionSettled", + "type" => "event", + "inputs" => [ + %{"name" => "accountId", "type" => "uint256", "indexed" => true, "internalType" => "uint256"}, + %{"name" => "option", "type" => "address", "indexed" => false, "internalType" => "address"}, + %{"name" => "subId", "type" => "uint256", "indexed" => false, "internalType" => "uint256"}, + %{"name" => "amount", "type" => "int256", "indexed" => false, "internalType" => "int256"}, + %{"name" => "value", "type" => "int256", "indexed" => false, "internalType" => "int256"} + ], + "anonymous" => false + } + ) + + topic1_bytes = ExKeccak.hash_256("OptionSettled(uint256,address,uint256,int256,int256)") + topic1 = "0x" <> Base.encode16(topic1_bytes, case: :lower) + log1_topic2 = "0x0000000000000000000000000000000000000000000000000000000000005d19" + log2_topic2 = "0x000000000000000000000000000000000000000000000000000000000000634a" + + log1_data = + "0x000000000000000000000000aeb81cbe6b19ceeb0dbe0d230cffe35bb40a13a700000000000000000000000000000000000000000000045d964b80006597b700fffffffffffffffffffffffffffffffffffffffffffffffffe55aca2c2f40000ffffffffffffffffffffffffffffffffffffffffffffffe3a8289da3d7a13ef2" + + log2_data = + "0x000000000000000000000000aeb81cbe6b19ceeb0dbe0d230cffe35bb40a13a700000000000000000000000000000000000000000000045d964b80006597b700000000000000000000000000000000000000000000000000011227ebced227ae00000000000000000000000000000000000000000000001239fdf180a3d6bd85" + + transaction = insert(:transaction) + + log1 = + insert(:log, + transaction: transaction, + first_topic: topic(topic1), + second_topic: topic(log1_topic2), + third_topic: nil, + fourth_topic: nil, + data: log1_data + ) + + log2 = + insert(:log, + transaction: transaction, + first_topic: topic(topic1), + second_topic: topic(log2_topic2), + third_topic: nil, + fourth_topic: nil, + data: log2_data + ) + + logs = [log1, log2] + + assert [ + {:ok, "d20a68b2", + "OptionSettled(uint256 indexed accountId, address option, uint256 subId, int256 amount, int256 value)", + [ + {"accountId", "uint256", true, 23833}, + {"option", "address", false, + <<174, 184, 28, 190, 107, 25, 206, 235, 13, 190, 13, 35, 12, 255, 227, 91, 180, 10, 19, 167>>}, + {"subId", "uint256", false, 20_615_843_020_801_704_441_600}, + {"amount", "int256", false, -120_000_000_000_000_000}, + {"value", "int256", false, -522_838_470_013_113_778_446} + ]}, + {:ok, "d20a68b2", + "OptionSettled(uint256 indexed accountId, address option, uint256 subId, int256 amount, int256 value)", + [ + {"accountId", "uint256", true, 25418}, + {"option", "address", false, + <<174, 184, 28, 190, 107, 25, 206, 235, 13, 190, 13, 35, 12, 255, 227, 91, 180, 10, 19, 167>>}, + {"subId", "uint256", false, 20_615_843_020_801_704_441_600}, + {"amount", "int256", false, 77_168_037_359_396_782}, + {"value", "int256", false, 336_220_154_890_848_484_741} + ]} + ] = TransactionView.decode_logs(logs, false) + end + end + + defp topic(topic_hex_string) do + {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string) + topic + end +end diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index be27eead7641..57909184a04b 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -187,12 +187,13 @@ defmodule Explorer.Chain.Log do {{:error, :could_not_decode}, events_acc} else <> = log.first_topic.bytes + key = {method_id, log.second_topic, log.third_topic, log.fourth_topic} - if Map.has_key?(events_acc, method_id) do - {events_acc[method_id], events_acc} + if Map.has_key?(events_acc, key) do + {events_acc[key], events_acc} else result = find_method_candidates_from_db(method_id, log, transaction, options, skip_sig_provider?) - {result, Map.put(events_acc, method_id, result)} + {result, Map.put(events_acc, key, result)} end end end From 3d03945a7180a6554945213a3c4b2f2112f13f3d Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Thu, 25 Jan 2024 13:20:43 +0400 Subject: [PATCH 334/607] feat: add burn blob fee in tx view --- .../lib/block_scout_web/views/api/v2/transaction_view.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index baa757cf0c78..f36571040072 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -475,6 +475,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do |> Map.put("blob_versioned_hashes", item.blob_versioned_hashes) |> Map.put("blob_gas_used", item.blob_gas_used) |> Map.put("blob_gas_price", item.blob_gas_price) + |> Map.put("burnt_blob_fee", Decimal.mult(item.blob_gas_used, item.blob_gas_price)) end _ -> From 59c0056d6a7ab7c0ab4316d3d7339ab556469071 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 25 Jan 2024 12:21:22 +0300 Subject: [PATCH 335/607] Fetch token image from CoinGecko (#9132) * Fetch token image from CoinGecko * Process reviewer comments * Remove fetch_image function --- CHANGELOG.md | 1 + .../controllers/api/v2/stats_controller.ex | 9 ++++---- .../lib/block_scout_web/views/layout_view.ex | 15 +++---------- .../channels/exchange_rate_channel_test.exs | 3 ++- .../api/rpc/stats_controller_test.exs | 3 ++- .../exchange_rates/source/coin_gecko.ex | 21 +++++++++++++++++-- .../exchange_rates/source/coin_market_cap.ex | 3 ++- .../lib/explorer/exchange_rates/token.ex | 15 ++++++++----- apps/explorer/lib/explorer/helper.ex | 13 ++++++++++++ apps/explorer/lib/explorer/market/market.ex | 3 ++- .../explorer/market/market_history_cache.ex | 6 +++--- .../exchange_rates/exchange_rates_test.exs | 3 ++- .../exchange_rates/source/coin_gecko_test.exs | 3 ++- .../test/support/fakes/one_coin_source.ex | 3 ++- 14 files changed, 68 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0c691c0a21a..8c0a2d798290 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - [#9155](https://github.com/blockscout/blockscout/pull/9155) - Allow bypassing avg block time in proxy implementation re-fetch ttl calculation - [#9145](https://github.com/blockscout/blockscout/pull/9145) - Proxy for Account abstraction microservice +- [#9132](https://github.com/blockscout/blockscout/pull/9132) - Fetch token image from CoinGecko - [#9131](https://github.com/blockscout/blockscout/pull/9131) - Merge addresses stage with address referencing - [#9072](https://github.com/blockscout/blockscout/pull/9072) - Add tracing by block logic for geth - [#9068](https://github.com/blockscout/blockscout/pull/9068) - New RPC API v1 endpoints diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex index 451275586b28..6ba3b57e7b5c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex @@ -27,7 +27,7 @@ defmodule BlockScoutWeb.API.V2.StatsController do :standard end - exchange_rate_from_db = Market.get_coin_exchange_rate() + exchange_rate = Market.get_coin_exchange_rate() transaction_stats = Helper.get_transaction_stats() @@ -64,7 +64,8 @@ defmodule BlockScoutWeb.API.V2.StatsController do "total_addresses" => @api_true |> Counters.address_estimated_count() |> to_string(), "total_transactions" => TransactionCache.estimated_count() |> to_string(), "average_block_time" => AverageBlockTime.average_block_time() |> Duration.to_milliseconds(), - "coin_price" => exchange_rate_from_db.usd_value, + "coin_image" => exchange_rate.image_url, + "coin_price" => exchange_rate.usd_value, "coin_price_change_percentage" => coin_price_change, "total_gas_used" => GasUsage.total() |> to_string(), "transactions_today" => Enum.at(transaction_stats, 0).number_of_transactions |> to_string(), @@ -73,8 +74,8 @@ defmodule BlockScoutWeb.API.V2.StatsController do "gas_prices_update_in" => GasPriceOracle.update_in(), "gas_price_updated_at" => GasPriceOracle.get_updated_at(), "static_gas_price" => gas_price, - "market_cap" => Helper.market_cap(market_cap_type, exchange_rate_from_db), - "tvl" => exchange_rate_from_db.tvl_usd, + "market_cap" => Helper.market_cap(market_cap_type, exchange_rate), + "tvl" => exchange_rate.tvl_usd, "network_utilization_percentage" => network_utilization_percentage() } |> add_rootstock_locked_btc() diff --git a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex index 9cff33b73249..46977c4e1f51 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex @@ -2,7 +2,7 @@ defmodule BlockScoutWeb.LayoutView do use BlockScoutWeb, :view alias EthereumJSONRPC.Variant - alias Explorer.Chain + alias Explorer.{Chain, Helper} alias Poison.Parser import BlockScoutWeb.APIDocsView, only: [blockscout_url: 1] @@ -203,7 +203,7 @@ defmodule BlockScoutWeb.LayoutView do def webapp_url(conn) do :block_scout_web |> Application.get_env(:webapp_url) - |> validate_url() + |> Helper.validate_url() |> case do :error -> chain_path(conn, :show) {:ok, url} -> url @@ -213,7 +213,7 @@ defmodule BlockScoutWeb.LayoutView do def api_url do :block_scout_web |> Application.get_env(:api_url) - |> validate_url() + |> Helper.validate_url() |> case do :error -> "" {:ok, url} -> url @@ -236,15 +236,6 @@ defmodule BlockScoutWeb.LayoutView do end end - defp validate_url(url) when is_binary(url) do - case URI.parse(url) do - %URI{host: nil} -> :error - _ -> {:ok, url} - end - end - - defp validate_url(_), do: :error - def sign_in_link do if Mix.env() == :test do "/auth/auth0" diff --git a/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs b/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs index 34f26785694d..7ae814d1be84 100644 --- a/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs +++ b/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs @@ -31,7 +31,8 @@ defmodule BlockScoutWeb.ExchangeRateChannelTest do name: "test", symbol: Explorer.coin(), usd_value: Decimal.new("2.5"), - volume_24h_usd: Decimal.new("1000.0") + volume_24h_usd: Decimal.new("1000.0"), + image_url: nil } on_exit(fn -> diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs index 5731311f11b7..a883a8d96e16 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs @@ -173,7 +173,8 @@ defmodule BlockScoutWeb.API.RPC.StatsControllerTest do name: "test", symbol: symbol, usd_value: Decimal.new("1.0"), - volume_24h_usd: Decimal.new("1000.0") + volume_24h_usd: Decimal.new("1000.0"), + image_url: nil } ExchangeRates.handle_info({nil, {:ok, [eth]}}, %{}) diff --git a/apps/explorer/lib/explorer/exchange_rates/source/coin_gecko.ex b/apps/explorer/lib/explorer/exchange_rates/source/coin_gecko.ex index 1499e20e834b..a1282f525d65 100644 --- a/apps/explorer/lib/explorer/exchange_rates/source/coin_gecko.ex +++ b/apps/explorer/lib/explorer/exchange_rates/source/coin_gecko.ex @@ -3,7 +3,7 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do Adapter for fetching exchange rates from https://coingecko.com """ - alias Explorer.Chain + alias Explorer.{Chain, Helper} alias Explorer.ExchangeRates.{Source, Token} import Source, only: [to_decimal: 1] @@ -13,9 +13,11 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do @impl Source def format_data(%{"market_data" => _} = json_data) do market_data = json_data["market_data"] + image_data = json_data["image"] last_updated = get_last_updated(market_data) current_price = get_current_price(market_data) + image_url = get_coin_image(image_data) id = json_data["id"] @@ -39,7 +41,8 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do name: json_data["name"], symbol: String.upcase(json_data["symbol"]), usd_value: current_price, - volume_24h_usd: to_decimal(total_volume_data_usd) + volume_24h_usd: to_decimal(total_volume_data_usd), + image_url: image_url } ] end @@ -241,6 +244,20 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do end end + defp get_coin_image(image_data) do + image_url_raw = + if image_data do + image_data["thumb"] || image_data["small"] + else + nil + end + + case Helper.validate_url(image_url_raw) do + {:ok, url} -> url + _ -> nil + end + end + defp get_btc_value(id, market_data) do case get_btc_price() do {:ok, price} -> diff --git a/apps/explorer/lib/explorer/exchange_rates/source/coin_market_cap.ex b/apps/explorer/lib/explorer/exchange_rates/source/coin_market_cap.ex index 3f13d25ba464..2e153e4dee40 100644 --- a/apps/explorer/lib/explorer/exchange_rates/source/coin_market_cap.ex +++ b/apps/explorer/lib/explorer/exchange_rates/source/coin_market_cap.ex @@ -44,7 +44,8 @@ defmodule Explorer.ExchangeRates.Source.CoinMarketCap do name: token_properties["name"], symbol: String.upcase(token_properties["symbol"]), usd_value: current_price, - volume_24h_usd: to_decimal(total_volume_data_usd) + volume_24h_usd: to_decimal(total_volume_data_usd), + image_url: nil } ] end diff --git a/apps/explorer/lib/explorer/exchange_rates/token.ex b/apps/explorer/lib/explorer/exchange_rates/token.ex index 07405701e8cc..df262bc8c9ce 100644 --- a/apps/explorer/lib/explorer/exchange_rates/token.ex +++ b/apps/explorer/lib/explorer/exchange_rates/token.ex @@ -17,6 +17,7 @@ defmodule Explorer.ExchangeRates.Token do * `:symbol` - Trading symbol used to represent a currency * `:usd_value` - The USD value of the currency * `:volume_24h_usd` - The volume from the last 24 hours in USD + * `:image_url` - Token image URL """ @type t :: %__MODULE__{ available_supply: Decimal.t() | nil, @@ -29,12 +30,13 @@ defmodule Explorer.ExchangeRates.Token do name: String.t() | nil, symbol: String.t() | nil, usd_value: Decimal.t() | nil, - volume_24h_usd: Decimal.t() | nil + volume_24h_usd: Decimal.t() | nil, + image_url: String.t() | nil } @derive Jason.Encoder - @enforce_keys ~w(available_supply total_supply btc_value id last_updated market_cap_usd tvl_usd name symbol usd_value volume_24h_usd)a - defstruct ~w(available_supply total_supply btc_value id last_updated market_cap_usd tvl_usd name symbol usd_value volume_24h_usd)a + @enforce_keys ~w(available_supply total_supply btc_value id last_updated market_cap_usd tvl_usd name symbol usd_value volume_24h_usd image_url)a + defstruct ~w(available_supply total_supply btc_value id last_updated market_cap_usd tvl_usd name symbol usd_value volume_24h_usd image_url)a def null, do: %__MODULE__{ @@ -48,6 +50,7 @@ defmodule Explorer.ExchangeRates.Token do market_cap_usd: nil, tvl_usd: nil, btc_value: nil, + image_url: nil, last_updated: nil } @@ -64,16 +67,17 @@ defmodule Explorer.ExchangeRates.Token do market_cap_usd: market_cap_usd, tvl_usd: tvl_usd, btc_value: btc_value, + image_url: image_url, last_updated: last_updated }) do # symbol is first because it is the key used for lookup in `Explorer.ExchangeRates`'s ETS table {symbol, id, name, available_supply, total_supply, usd_value, volume_24h_usd, market_cap_usd, tvl_usd, btc_value, - last_updated} + image_url, last_updated} end def from_tuple( {symbol, id, name, available_supply, total_supply, usd_value, volume_24h_usd, market_cap_usd, tvl_usd, - btc_value, last_updated} + btc_value, image_url, last_updated} ) do %__MODULE__{ symbol: symbol, @@ -86,6 +90,7 @@ defmodule Explorer.ExchangeRates.Token do market_cap_usd: market_cap_usd, tvl_usd: tvl_usd, btc_value: btc_value, + image_url: image_url, last_updated: last_updated } end diff --git a/apps/explorer/lib/explorer/helper.ex b/apps/explorer/lib/explorer/helper.ex index 20d65314b23e..63687694ba0c 100644 --- a/apps/explorer/lib/explorer/helper.ex +++ b/apps/explorer/lib/explorer/helper.ex @@ -92,4 +92,17 @@ defmodule Explorer.Helper do decoded -> decoded end end + + @doc """ + Checks if input is a valid URL + """ + @spec validate_url(String.t() | nil) :: {:ok, String.t()} | :error + def validate_url(url) when is_binary(url) do + case URI.parse(url) do + %URI{host: nil} -> :error + _ -> {:ok, url} + end + end + + def validate_url(_), do: :error end diff --git a/apps/explorer/lib/explorer/market/market.ex b/apps/explorer/lib/explorer/market/market.ex index 6de43a1b75b6..a65b643fa5cb 100644 --- a/apps/explorer/lib/explorer/market/market.ex +++ b/apps/explorer/lib/explorer/market/market.ex @@ -42,7 +42,8 @@ defmodule Explorer.Market do last_updated: nil, name: nil, symbol: nil, - volume_24h_usd: nil + volume_24h_usd: nil, + image_url: nil } else Token.null() diff --git a/apps/explorer/lib/explorer/market/market_history_cache.ex b/apps/explorer/lib/explorer/market/market_history_cache.ex index 0a6a9a33ab43..81307bc34fa3 100644 --- a/apps/explorer/lib/explorer/market/market_history_cache.ex +++ b/apps/explorer/lib/explorer/market/market_history_cache.ex @@ -16,7 +16,7 @@ defmodule Explorer.Market.MarketHistoryCache do @recent_days 30 def fetch do - if cache_expired?() do + if cache_expired?(@last_update_key) do update_cache() else fetch_from_cache(@history_key) @@ -31,9 +31,9 @@ defmodule Explorer.Market.MarketHistoryCache do def recent_days_count, do: @recent_days - defp cache_expired? do + defp cache_expired?(key) do cache_period = Application.get_env(:explorer, __MODULE__)[:cache_period] - updated_at = fetch_from_cache(@last_update_key) + updated_at = fetch_from_cache(key) cond do is_nil(updated_at) -> true diff --git a/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs b/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs index 25faf397b422..e23e64f07744 100644 --- a/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs +++ b/apps/explorer/test/explorer/exchange_rates/exchange_rates_test.exs @@ -85,7 +85,8 @@ defmodule Explorer.ExchangeRatesTest do name: "test_name", symbol: "test_symbol", usd_value: Decimal.new("1.0"), - volume_24h_usd: Decimal.new("1000.0") + volume_24h_usd: Decimal.new("1000.0"), + image_url: nil } expected_symbol = expected_token.symbol diff --git a/apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs b/apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs index 044c84553f3e..e16d056422c2 100644 --- a/apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs +++ b/apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs @@ -120,7 +120,8 @@ defmodule Explorer.ExchangeRates.Source.CoinGeckoTest do name: "POA Network", symbol: "POA", usd_value: Decimal.new("0.01345698"), - volume_24h_usd: Decimal.new("119946") + volume_24h_usd: Decimal.new("119946"), + image_url: "https://assets.coingecko.com/coins/images/3157/thumb/poa-network.png?1548331565" } ] diff --git a/apps/explorer/test/support/fakes/one_coin_source.ex b/apps/explorer/test/support/fakes/one_coin_source.ex index 4301f24635f1..ee38c6f09ba4 100644 --- a/apps/explorer/test/support/fakes/one_coin_source.ex +++ b/apps/explorer/test/support/fakes/one_coin_source.ex @@ -19,7 +19,8 @@ defmodule Explorer.ExchangeRates.Source.OneCoinSource do tvl_usd: Decimal.new(100_500_000), symbol: Explorer.coin(), usd_value: Decimal.new(1), - volume_24h_usd: Decimal.new(1) + volume_24h_usd: Decimal.new(1), + image_url: nil } [pseudo_token] From 400403fb1fd88e2fe562297aceebdaaf0eaa4714 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 25 Jan 2024 12:26:50 +0300 Subject: [PATCH 336/607] Enhanced unfetched token balances index (#9153) * Enhanced unfetched token balances index * Change token_type to id in the index --- CHANGELOG.md | 1 + ...enhanced_unfetched_token_balances_index.exs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 apps/explorer/priv/repo/migrations/20240114181404_enhanced_unfetched_token_balances_index.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c0a2d798290..0ffc23469c40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ - [#9197](https://github.com/blockscout/blockscout/pull/9197) - Add `MARKET_HISTORY_FETCH_INTERVAL` env - [#9196](https://github.com/blockscout/blockscout/pull/9196) - Compatibility with docker-compose 2.24 - [#9193](https://github.com/blockscout/blockscout/pull/9193) - Equalize elixir stack versions +- [#9153](https://github.com/blockscout/blockscout/pull/9153) - Enhanced unfetched token balances index

Dependencies version bumps diff --git a/apps/explorer/priv/repo/migrations/20240114181404_enhanced_unfetched_token_balances_index.exs b/apps/explorer/priv/repo/migrations/20240114181404_enhanced_unfetched_token_balances_index.exs new file mode 100644 index 000000000000..287d5ae42efa --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240114181404_enhanced_unfetched_token_balances_index.exs @@ -0,0 +1,18 @@ +defmodule Explorer.Repo.Migrations.EnhancedUnfetchedTokenBalancesIndex do + use Ecto.Migration + + def up do + execute(""" + CREATE INDEX unfetched_address_token_balances_index on address_token_balances(id) + WHERE ( + ((address_hash != '\\x0000000000000000000000000000000000000000' AND token_type = 'ERC-721') OR token_type = 'ERC-20' OR token_type = 'ERC-1155') AND (value_fetched_at IS NULL OR value IS NULL) + ); + """) + end + + def down do + execute(""" + DROP INDEX unfetched_address_token_balances_index; + """) + end +end From f258b075714f85e1a35385e517c8a35dc1d75bd8 Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Thu, 25 Jan 2024 12:36:53 +0300 Subject: [PATCH 337/607] Add /api/v2/utils/decode-calldata endpoint (#9148) * Add /api/v2/utils/decode-calldata endpoint * Process review comment * Update apps/block_scout_web/lib/block_scout_web/controllers/api/v2/utils_controller.ex Co-authored-by: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> --------- Co-authored-by: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> --- CHANGELOG.md | 1 + apps/block_scout_web/.sobelow-conf | 3 +- .../lib/block_scout_web/api_router.ex | 3 +- .../controllers/api/v2/utils_controller.ex | 35 +++++ .../block_scout_web/utils_api_v2_router.ex | 24 ++++ .../api/v2/utils_controller_test.exs | 130 ++++++++++++++++++ 6 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 apps/block_scout_web/lib/block_scout_web/controllers/api/v2/utils_controller.ex create mode 100644 apps/block_scout_web/lib/block_scout_web/utils_api_v2_router.ex create mode 100644 apps/block_scout_web/test/block_scout_web/controllers/api/v2/utils_controller_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ffc23469c40..75f9014beb44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - [#9155](https://github.com/blockscout/blockscout/pull/9155) - Allow bypassing avg block time in proxy implementation re-fetch ttl calculation +- [#9148](https://github.com/blockscout/blockscout/pull/9148) - Add `/api/v2/utils/decode-calldata` - [#9145](https://github.com/blockscout/blockscout/pull/9145) - Proxy for Account abstraction microservice - [#9132](https://github.com/blockscout/blockscout/pull/9132) - Fetch token image from CoinGecko - [#9131](https://github.com/blockscout/blockscout/pull/9131) - Merge addresses stage with address referencing diff --git a/apps/block_scout_web/.sobelow-conf b/apps/block_scout_web/.sobelow-conf index 82604d65bf53..1604d1f66daf 100644 --- a/apps/block_scout_web/.sobelow-conf +++ b/apps/block_scout_web/.sobelow-conf @@ -7,6 +7,7 @@ format: "compact", ignore: ["Config.Headers", "Config.CSWH", "XSS.SendResp", "XSS.Raw"], ignore_files: [ - "apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex" + "apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex", + "apps/block_scout_web/lib/block_scout_web/utils_api_v2_router.ex" ] ] diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 6899c96f3e72..f9da5834f4ab 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -13,11 +13,12 @@ defmodule BlockScoutWeb.ApiRouter do Router for API """ use BlockScoutWeb, :router - alias BlockScoutWeb.{AddressTransactionController, APIKeyV2Router, SmartContractsApiV2Router} + alias BlockScoutWeb.{AddressTransactionController, APIKeyV2Router, SmartContractsApiV2Router, UtilsApiV2Router} alias BlockScoutWeb.Plug.{CheckAccountAPI, CheckApiV2, RateLimit} forward("/v2/smart-contracts", SmartContractsApiV2Router) forward("/v2/key", APIKeyV2Router) + forward("/v2/utils", UtilsApiV2Router) pipeline :api do plug(BlockScoutWeb.Plug.Logger, application: :api) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/utils_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/utils_controller.ex new file mode 100644 index 000000000000..3fd6dd9500bf --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/utils_controller.ex @@ -0,0 +1,35 @@ +defmodule BlockScoutWeb.API.V2.UtilsController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.API.V2.TransactionView + alias Explorer.Chain + alias Explorer.Chain.{Data, SmartContract, Transaction} + + @api_true [api?: true] + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + @doc """ + Function to handle GET and POST requests to `/api/v2/utils/decode-calldata` + """ + @spec decode_calldata(Plug.Conn.t(), map()) :: {:format, :error} | Plug.Conn.t() + def decode_calldata(conn, params) do + with {:format, {:ok, data}} <- {:format, Data.cast(params["calldata"])}, + address_hash <- params["address_hash"] && Chain.string_to_address_hash(params["address_hash"]), + {:format, true} <- {:format, match?({:ok, _hash}, address_hash) || is_nil(address_hash)} do + smart_contract = + if address_hash, do: SmartContract.address_hash_to_smart_contract(elem(address_hash, 1), @api_true) + + {decoded_input, _abi_acc, _methods_acc} = + Transaction.decoded_input_data( + %Transaction{input: data, to_address: %{contract_code: "", smart_contract: smart_contract}}, + @api_true + ) + + decoded_input_data = decoded_input |> TransactionView.format_decoded_input() |> TransactionView.decoded_input() + + conn + |> json(%{result: decoded_input_data}) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/utils_api_v2_router.ex b/apps/block_scout_web/lib/block_scout_web/utils_api_v2_router.ex new file mode 100644 index 000000000000..572133156dd5 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/utils_api_v2_router.ex @@ -0,0 +1,24 @@ +# This file in ignore list of `sobelow`, be careful while adding new endpoints here +defmodule BlockScoutWeb.UtilsApiV2Router do + @moduledoc """ + Router for /api/v2/utils. This route has separate router in order to ignore sobelow's warning about missing CSRF protection + """ + use BlockScoutWeb, :router + alias BlockScoutWeb.Plug.{CheckApiV2, RateLimit} + + pipeline :api_v2_no_forgery_protect do + plug(BlockScoutWeb.Plug.Logger, application: :api_v2) + plug(:accepts, ["json"]) + plug(CheckApiV2) + plug(RateLimit) + plug(:fetch_session) + end + + scope "/", as: :api_v2 do + pipe_through(:api_v2_no_forgery_protect) + alias BlockScoutWeb.API.V2 + + get("/decode-calldata", V2.UtilsController, :decode_calldata) + post("/decode-calldata", V2.UtilsController, :decode_calldata) + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/utils_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/utils_controller_test.exs new file mode 100644 index 000000000000..8b80a9541cb5 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/utils_controller_test.exs @@ -0,0 +1,130 @@ +defmodule BlockScoutWeb.API.V2.UtilsControllerTest do + use BlockScoutWeb.ConnCase + + import Mox + + describe "/api/v2/utils/decode-calldata" do + test "success decodes calldata", %{conn: conn} do + transaction = + :transaction_to_verified_contract + |> insert() + + request_zero_implementations() + + assert conn + |> get("/api/v2/utils/decode-calldata", %{ + "calldata" => to_string(transaction.input), + "address_hash" => to_string(transaction.to_address) + }) + |> json_response(200) == + %{ + "result" => %{ + "method_call" => "set(uint256 x)", + "method_id" => "60fe47b1", + "parameters" => [%{"name" => "x", "type" => "uint256", "value" => "50"}] + } + } + + request_zero_implementations() + + assert conn + |> post("/api/v2/utils/decode-calldata", %{ + "calldata" => to_string(transaction.input), + "address_hash" => to_string(transaction.to_address) + }) + |> json_response(200) == + %{ + "result" => %{ + "method_call" => "set(uint256 x)", + "method_id" => "60fe47b1", + "parameters" => [%{"name" => "x", "type" => "uint256", "value" => "50"}] + } + } + end + + test "return nil in case of failed decoding", %{conn: conn} do + assert conn + |> post("/api/v2/utils/decode-calldata", %{ + "calldata" => "0x010101" + }) + |> json_response(200) == + %{ + "result" => nil + } + end + + test "decodes using ABI from smart_contracts_methods table", %{conn: conn} do + insert(:contract_method) + + input_data = + "set(uint)" + |> ABI.encode([50]) + |> Base.encode16(case: :lower) + + assert conn + |> post("/api/v2/utils/decode-calldata", %{ + "calldata" => "0x" <> input_data + }) + |> json_response(200) == + %{ + "result" => %{ + "method_call" => "set(uint256 x)", + "method_id" => "60fe47b1", + "parameters" => [%{"name" => "x", "type" => "uint256", "value" => "50"}] + } + } + end + end + + defp request_zero_implementations do + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + end +end From 6a13e367cb6680df65114339dc06c54da05993ad Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Thu, 25 Jan 2024 12:42:04 +0300 Subject: [PATCH 338/607] Fix 500 response on token id which is not in DB (#9187) * Fix Internal Server Error on request for nonexistent token instance * Add spec and doc --- CHANGELOG.md | 3 ++- .../controllers/api/v2/token_controller.ex | 4 +++- .../views/api/v2/address_view.ex | 15 +++++++++++--- .../api/v2/token_controller_test.exs | 20 +++++++++++++++++++ apps/explorer/lib/explorer/chain.ex | 4 ++++ .../lib/explorer/chain/token/instance.ex | 15 +++++++++----- 6 files changed, 51 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75f9014beb44..c5e184cfb7e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,9 +18,10 @@ - [#9241](https://github.com/blockscout/blockscout/pull/9241) - Fix log decoding bug - [#9229](https://github.com/blockscout/blockscout/pull/9229) - Add missing filter to txlist query +- [#9187](https://github.com/blockscout/blockscout/pull/9187) - Fix Internal Server Error on request for nonexistent token instance +- [#9178](https://github.com/blockscout/blockscout/pull/9178) - Change internal txs tracer type to opcode for Hardhat node - [#9143](https://github.com/blockscout/blockscout/pull/9143) - Handle nil token_ids in token transfers on render - [#9139](https://github.com/blockscout/blockscout/pull/9139) - TokenBalanceOnDemand fixes -- [#9178](https://github.com/blockscout/blockscout/pull/9178) - Change internal txs tracer type to opcode for Hardhat node - [#9125](https://github.com/blockscout/blockscout/pull/9125) - Fix Explorer.Chain.Cache.GasPriceOracle.merge_fees - [#9124](https://github.com/blockscout/blockscout/pull/9124) - EIP-1167 display multiple sources of implementation - [#9110](https://github.com/blockscout/blockscout/pull/9110) - Improve update_in in gas tracker diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex index 767af3dbbbc6..bc4034ad0f1d 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex @@ -170,7 +170,9 @@ defmodule BlockScoutWeb.API.V2.TokenController do |> Chain.put_owner_to_token_instance(token, @api_true) {:error, :not_found} -> - %{token_id: token_id, metadata: nil, owner: nil} + %Instance{token_id: token_id, metadata: nil, owner: nil} + |> Instance.put_is_unique(token, @api_true) + |> Chain.put_owner_to_token_instance(token, @api_true) end conn diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex index 1b29a23d4b73..3c8e52f7b1af 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex @@ -229,12 +229,21 @@ defmodule BlockScoutWeb.API.V2.AddressView do @api_true ) do # `%{hash: address_hash}` will match with `address_with_info(_, address_hash)` clause in `BlockScoutWeb.API.V2.Helper` - {:ok, token_instance} -> %Instance{token_instance | owner: %{hash: address_hash}} - {:error, :not_found} -> %Instance{token_id: token_id, metadata: nil, owner: %{hash: address_hash}} + {:ok, token_instance} -> + %Instance{token_instance | owner: %{hash: address_hash}, current_token_balance: token_balance} + + {:error, :not_found} -> + %Instance{ + token_id: token_id, + metadata: nil, + owner: %{hash: address_hash}, + current_token_balance: token_balance + } + |> Instance.put_is_unique(token, @api_true) end TokenView.render("token_instance.json", %{ - token_instance: %Instance{token_instance | current_token_balance: token_balance}, + token_instance: token_instance, token: token }) end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs index daefeaa8cb85..2dec0632fd3d 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs @@ -1010,6 +1010,26 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do assert compare_item(instance, data) assert Address.checksum(instance.owner_address_hash) == data["owner"]["hash"] end + + test "get token instance by token id which is not presented in DB", %{conn: conn} do + token = insert(:token, type: "ERC-721") + + request = get(conn, "/api/v2/tokens/#{token.contract_address.hash}/instances/0") + token_address = Address.checksum(token.contract_address.hash) + token_name = token.name + token_type = token.type + + assert %{ + "animation_url" => nil, + "external_app_url" => nil, + "id" => 0, + "image_url" => nil, + "is_unique" => true, + "metadata" => nil, + "owner" => nil, + "token" => %{"address" => ^token_address, "name" => ^token_name, "type" => ^token_type} + } = json_response(request, 200) + end end describe "/tokens/{address_hash}/instances/{token_id}/transfers" do diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 945e37b35ddf..014c7ff3bebf 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -4180,6 +4180,10 @@ defmodule Explorer.Chain do |> Enum.map(&put_owner_to_token_instance(&1, token, options)) end + @doc """ + Put owner address to unique token instance. If not unique, return original instance. + """ + @spec put_owner_to_token_instance(Instance.t(), Token.t(), [api?]) :: Instance.t() def put_owner_to_token_instance(token_instance, token, options \\ []) def put_owner_to_token_instance(%Instance{is_unique: nil} = token_instance, token, options) do diff --git a/apps/explorer/lib/explorer/chain/token/instance.ex b/apps/explorer/lib/explorer/chain/token/instance.ex index 04af69a30e99..8ea0e03483d2 100644 --- a/apps/explorer/lib/explorer/chain/token/instance.ex +++ b/apps/explorer/lib/explorer/chain/token/instance.ex @@ -20,12 +20,12 @@ defmodule Explorer.Chain.Token.Instance do @type t :: %Instance{ token_id: non_neg_integer(), - token_contract_address_hash: Hash.Address.t(), + token_contract_address_hash: Hash.Address.t() | nil, metadata: map() | nil, - error: String.t(), - owner_address_hash: Hash.Address.t(), - owner_updated_at_block: Block.block_number(), - owner_updated_at_log_index: non_neg_integer(), + error: String.t() | nil, + owner_address_hash: Hash.Address.t() | nil, + owner_updated_at_block: Block.block_number() | nil, + owner_updated_at_log_index: non_neg_integer() | nil, current_token_balance: any(), is_unique: bool() | nil } @@ -443,6 +443,11 @@ defmodule Explorer.Chain.Token.Instance do |> limit(^limit) end + @doc """ + Puts is_unique field in token instance. Returns updated token instance + is_unique is true for ERC-721 always and for ERC-1155 only if token_id is unique + """ + @spec put_is_unique(Instance.t(), Token.t(), Keyword.t()) :: Instance.t() def put_is_unique(instance, token, options) do %__MODULE__{instance | is_unique: unique?(instance, token, options)} end From e4e16534594cf75e8eeef5a2407d52755712ac58 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 25 Jan 2024 12:43:36 +0300 Subject: [PATCH 339/607] "cataloged" index on tokens table (#9233) --- CHANGELOG.md | 1 + .../20240123102336_add_tokens_cataloged_index.exs | 14 ++++++++++++++ docker-compose/envs/common-blockscout.env | 5 ++++- 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 apps/explorer/priv/repo/migrations/20240123102336_add_tokens_cataloged_index.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index c5e184cfb7e6..3b2d4247aa96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ ### Chore +- [#9233](https://github.com/blockscout/blockscout/pull/9233) - "cataloged" index on tokens table - [#9198](https://github.com/blockscout/blockscout/pull/9198) - Make Postgres@15 default option - [#9197](https://github.com/blockscout/blockscout/pull/9197) - Add `MARKET_HISTORY_FETCH_INTERVAL` env - [#9196](https://github.com/blockscout/blockscout/pull/9196) - Compatibility with docker-compose 2.24 diff --git a/apps/explorer/priv/repo/migrations/20240123102336_add_tokens_cataloged_index.exs b/apps/explorer/priv/repo/migrations/20240123102336_add_tokens_cataloged_index.exs new file mode 100644 index 000000000000..e3d4e02be5f3 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240123102336_add_tokens_cataloged_index.exs @@ -0,0 +1,14 @@ +defmodule Explorer.Repo.Migrations.AddTokensCatalogedIndex do + use Ecto.Migration + + def change do + create( + index( + :tokens, + ~w(cataloged)a, + name: :uncataloged_tokens, + where: ~s|"cataloged" = false| + ) + ) + end +end diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index f55cf1f1099c..18ae2f7a5faf 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -121,6 +121,10 @@ INDEXER_DISABLE_TOKEN_INSTANCE_SANITIZE_FETCHER=false INDEXER_DISABLE_TOKEN_INSTANCE_LEGACY_SANITIZE_FETCHER=false INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER=false INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false +# INDEXER_DISABLE_CATALOGED_TOKEN_UPDATER_FETCHER= +# INDEXER_DISABLE_BLOCK_REWARD_FETCHER= +# INDEXER_DISABLE_EMPTY_BLOCKS_SANITIZER= +# INDEXER_DISABLE_WITHDRAWALS_FETCHER= # INDEXER_CATCHUP_BLOCKS_BATCH_SIZE= # INDEXER_CATCHUP_BLOCKS_CONCURRENCY= # INDEXER_CATCHUP_BLOCK_INTERVAL= @@ -170,7 +174,6 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false # INDEXER_FETCHER_INIT_QUERY_LIMIT= # INDEXER_TOKEN_BALANCES_FETCHER_INIT_QUERY_LIMIT= # INDEXER_COIN_BALANCES_FETCHER_INIT_QUERY_LIMIT= -# INDEXER_DISABLE_WITHDRAWALS_FETCHER= # WITHDRAWALS_FIRST_BLOCK= # ROOTSTOCK_REMASC_ADDRESS= # ROOTSTOCK_BRIDGE_ADDRESS= From 4377c42b90d5ad1ba0e6d7616cee4eedd50f15e1 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Thu, 25 Jan 2024 14:05:45 +0400 Subject: [PATCH 340/607] chore: update default values --- .../explorer/chain/import/stage/block_referencing.ex | 10 +++++----- config/runtime.exs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex index b735b5ccb3c2..845231277594 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex @@ -35,6 +35,10 @@ defmodule Explorer.Chain.Import.Stage.BlockReferencing do Runner.Shibarium.BridgeOperations ] + @ethereum_runners [ + Runner.Beacon.BlobTransactions + ] + @impl Stage def runners do case System.get_env("CHAIN_TYPE") do @@ -48,11 +52,7 @@ defmodule Explorer.Chain.Import.Stage.BlockReferencing do @default_runners ++ @shibarium_runners "ethereum" -> - # credo:disable-for-next-line - @default_runners ++ - [ - Runner.Beacon.BlobTransactions - ] + @default_runners ++ @ethereum_runners _ -> @default_runners diff --git a/config/runtime.exs b/config/runtime.exs index 79599b5539d0..56d5694777af 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -691,10 +691,10 @@ config :indexer, Indexer.Fetcher.Beacon.Blob.Supervisor, config :indexer, Indexer.Fetcher.Beacon.Blob, slot_duration: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_FETCHER_SLOT_DURATION", 12), - reference_slot: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_FETCHER_REFERENCE_SLOT", 8_206_822), + reference_slot: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_FETCHER_REFERENCE_SLOT", 8_000_000), reference_timestamp: - ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_FETCHER_REFERENCE_TIMESTAMP", 1_705_305_887), - start_block: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_FETCHER_START_BLOCK", 8_206_822), + ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_FETCHER_REFERENCE_TIMESTAMP", 1_702_824_023), + start_block: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_FETCHER_START_BLOCK", 19_200_000), end_block: ConfigHelper.parse_integer_env_var("INDEXER_BEACON_BLOB_FETCHER_END_BLOCK", 0) config :indexer, Indexer.Fetcher.Shibarium.L1, From 250bc5d3c7edcae824193ef7ecd9278ed6b964d5 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 25 Jan 2024 13:51:26 +0300 Subject: [PATCH 341/607] Exclude genesis block from average block time calculation (#9173) --- CHANGELOG.md | 3 +- .../api/rpc/address_controller_test.exs | 22 ++++---- .../explorer/counters/average_block_time.ex | 14 ++++-- .../fetcher/coin_balance_on_demand_test.exs | 50 +++++++++---------- 4 files changed, 48 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b2d4247aa96..e8bbdc21001b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#9158](https://github.com/blockscout/blockscout/pull/9158) - Increase shared memory for PostgreSQL containers - [#9155](https://github.com/blockscout/blockscout/pull/9155) - Allow bypassing avg block time in proxy implementation re-fetch ttl calculation - [#9148](https://github.com/blockscout/blockscout/pull/9148) - Add `/api/v2/utils/decode-calldata` - [#9145](https://github.com/blockscout/blockscout/pull/9145) - Proxy for Account abstraction microservice @@ -12,7 +13,6 @@ - [#9072](https://github.com/blockscout/blockscout/pull/9072) - Add tracing by block logic for geth - [#9068](https://github.com/blockscout/blockscout/pull/9068) - New RPC API v1 endpoints - [#9056](https://github.com/blockscout/blockscout/pull/9056) - Noves.fi API proxy -- [#9158](https://github.com/blockscout/blockscout/pull/9158) - Increase shared memory for PostgreSQL containers ### Fixes @@ -20,6 +20,7 @@ - [#9229](https://github.com/blockscout/blockscout/pull/9229) - Add missing filter to txlist query - [#9187](https://github.com/blockscout/blockscout/pull/9187) - Fix Internal Server Error on request for nonexistent token instance - [#9178](https://github.com/blockscout/blockscout/pull/9178) - Change internal txs tracer type to opcode for Hardhat node +- [#9173](https://github.com/blockscout/blockscout/pull/9173) - Exclude genesis block from average block time calculation - [#9143](https://github.com/blockscout/blockscout/pull/9143) - Handle nil token_ids in token transfers on render - [#9139](https://github.com/blockscout/blockscout/pull/9139) - TokenBalanceOnDemand fixes - [#9125](https://github.com/blockscout/blockscout/pull/9125) - Fix Explorer.Chain.Cache.GasPriceOracle.merge_fees diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs index 7eb0463a04fe..4d14edab7578 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs @@ -131,24 +131,24 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do mining_address = insert(:address, fetched_coin_balance: 0, - fetched_coin_balance_block_number: 102, + fetched_coin_balance_block_number: 103, inserted_at: Timex.shift(now, minutes: -10) ) mining_address_hash = to_string(mining_address.hash) # we space these very far apart so that we know it will consider the 0th block stale (it calculates how far # back we'd need to go to get 24 hours in the past) - Enum.each(0..100, fn i -> - insert(:block, number: i, timestamp: Timex.shift(now, hours: -(102 - i) * 25), miner: mining_address) + Enum.each(0..101, fn i -> + insert(:block, number: i, timestamp: Timex.shift(now, hours: -(103 - i) * 25), miner: mining_address) end) - insert(:block, number: 101, timestamp: Timex.shift(now, hours: -25), miner: mining_address) + insert(:block, number: 102, timestamp: Timex.shift(now, hours: -25), miner: mining_address) AverageBlockTime.refresh() address = insert(:address, fetched_coin_balance: 100, - fetched_coin_balance_block_number: 100, + fetched_coin_balance_block_number: 101, inserted_at: Timex.shift(now, minutes: -5) ) @@ -158,20 +158,20 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{ id: id, method: "eth_getBalance", - params: [^mining_address_hash, "0x65"] + params: [^mining_address_hash, "0x66"] } ], _options -> {:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]} end) - res = eth_block_number_fake_response("0x65") + res = eth_block_number_fake_response("0x66") expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn [ %{ id: 0, method: "eth_getBlockByNumber", - params: ["0x65", true] + params: ["0x66", true] } ], _ -> @@ -182,7 +182,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{ id: id, method: "eth_getBalance", - params: [^address_hash, "0x65"] + params: [^address_hash, "0x66"] } ], _options -> @@ -193,7 +193,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{ id: 0, method: "eth_getBlockByNumber", - params: ["0x65", true] + params: ["0x66", true] } ], _ -> @@ -229,7 +229,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert received_address.hash == address.hash assert received_address.fetched_coin_balance == expected_wei - assert received_address.fetched_coin_balance_block_number == 101 + assert received_address.fetched_coin_balance_block_number == 102 end end diff --git a/apps/explorer/lib/explorer/counters/average_block_time.ex b/apps/explorer/lib/explorer/counters/average_block_time.ex index 9c7338dc9e97..2ca84bc87612 100644 --- a/apps/explorer/lib/explorer/counters/average_block_time.ex +++ b/apps/explorer/lib/explorer/counters/average_block_time.ex @@ -11,6 +11,9 @@ defmodule Explorer.Counters.AverageBlockTime do alias Explorer.Repo alias Timex.Duration + @num_of_blocks 100 + @offset 100 + @doc """ Starts a process to periodically update the counter of the token holders. """ @@ -62,10 +65,13 @@ defmodule Explorer.Counters.AverageBlockTime do end defp refresh_timestamps do + first_block_from_config = Application.get_env(:indexer, :first_block) + base_query = from(block in Block, - limit: 100, - offset: 100, + where: block.number > ^first_block_from_config, + limit: ^@num_of_blocks, + offset: ^@offset, order_by: [desc: block.number], select: {block.number, block.timestamp} ) @@ -78,12 +84,12 @@ defmodule Explorer.Counters.AverageBlockTime do |> where(consensus: true) end - timestamps_row = + raw_timestamps = timestamps_query |> Repo.all() timestamps = - timestamps_row + raw_timestamps |> Enum.sort_by(fn {_, timestamp} -> timestamp end, &Timex.after?/2) |> Enum.map(fn {number, timestamp} -> {number, DateTime.to_unix(timestamp, :millisecond)} diff --git a/apps/indexer/test/indexer/fetcher/coin_balance_on_demand_test.exs b/apps/indexer/test/indexer/fetcher/coin_balance_on_demand_test.exs index d345ecee0827..17697b2f7b20 100644 --- a/apps/indexer/test/indexer/fetcher/coin_balance_on_demand_test.exs +++ b/apps/indexer/test/indexer/fetcher/coin_balance_on_demand_test.exs @@ -43,18 +43,18 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do # we space these very far apart so that we know it will consider the 0th block stale (it calculates how far # back we'd need to go to get 24 hours in the past) - Enum.each(0..100, fn i -> - insert(:block, number: i, timestamp: Timex.shift(now, hours: -(101 - i) * 50)) + Enum.each(0..101, fn i -> + insert(:block, number: i, timestamp: Timex.shift(now, hours: -(102 - i) * 50)) end) - insert(:block, number: 101, timestamp: now) + insert(:block, number: 102, timestamp: now) AverageBlockTime.refresh() - stale_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 100) - current_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 101) + stale_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 101) + current_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 102) - pending_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 101) - insert(:unfetched_balance, address_hash: pending_address.hash, block_number: 102) + pending_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 102) + insert(:unfetched_balance, address_hash: pending_address.hash, block_number: 103) %{stale_address: stale_address, current_address: current_address, pending_address: pending_address} end @@ -68,7 +68,7 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do test "if the address has not been fetched within the last 24 hours of blocks it is considered stale", %{ stale_address: address } do - assert CoinBalanceOnDemand.trigger_fetch(address) == {:stale, 101} + assert CoinBalanceOnDemand.trigger_fetch(address) == {:stale, 102} end test "if the address has been fetched within the last 24 hours of blocks it is considered current", %{ @@ -80,7 +80,7 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do test "if there is an unfetched balance within the window for an address, it is considered pending", %{ pending_address: pending_address } do - assert CoinBalanceOnDemand.trigger_fetch(pending_address) == {:pending, 102} + assert CoinBalanceOnDemand.trigger_fetch(pending_address) == {:pending, 103} end end @@ -139,18 +139,18 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do # we space these very far apart so that we know it will consider the 0th block stale (it calculates how far # back we'd need to go to get 24 hours in the past) - Enum.each(0..100, fn i -> - insert(:block, number: i, timestamp: Timex.shift(now, hours: -(101 - i) * 50)) + Enum.each(0..101, fn i -> + insert(:block, number: i, timestamp: Timex.shift(now, hours: -(102 - i) * 50)) end) - insert(:block, number: 101, timestamp: now) + insert(:block, number: 102, timestamp: now) AverageBlockTime.refresh() :ok end test "a stale address broadcasts the new address" do - address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 100) + address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 101) address_hash = address.hash string_address_hash = to_string(address.hash) @@ -158,14 +158,14 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do %{ id: id, method: "eth_getBalance", - params: [^string_address_hash, "0x65"] + params: [^string_address_hash, "0x66"] } ], _options -> {:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]} end) - res = eth_block_number_fake_response("0x65") + res = eth_block_number_fake_response("0x66") EthereumJSONRPC.Mox |> expect(:json_rpc, fn [ @@ -173,26 +173,26 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do id: 0, jsonrpc: "2.0", method: "eth_getBlockByNumber", - params: ["0x65", true] + params: ["0x66", true] } ], _ -> {:ok, [res]} end) - assert CoinBalanceOnDemand.trigger_fetch(address) == {:stale, 101} + assert CoinBalanceOnDemand.trigger_fetch(address) == {:stale, 102} {:ok, expected_wei} = Wei.cast(2) assert_receive( {:chain_event, :addresses, :on_demand, - [%{hash: ^address_hash, fetched_coin_balance: ^expected_wei, fetched_coin_balance_block_number: 101}]} + [%{hash: ^address_hash, fetched_coin_balance: ^expected_wei, fetched_coin_balance_block_number: 102}]} ) end test "a pending address broadcasts the new address and the new coin balance" do - address = insert(:address, fetched_coin_balance: 0, fetched_coin_balance_block_number: 101) - insert(:unfetched_balance, address_hash: address.hash, block_number: 102) + address = insert(:address, fetched_coin_balance: 0, fetched_coin_balance_block_number: 102) + insert(:unfetched_balance, address_hash: address.hash, block_number: 103) address_hash = address.hash string_address_hash = to_string(address.hash) @@ -200,7 +200,7 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do %{ id: id, method: "eth_getBalance", - params: [^string_address_hash, "0x66"] + params: [^string_address_hash, "0x67"] } ], _options -> @@ -213,21 +213,21 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do id: 0, jsonrpc: "2.0", method: "eth_getBlockByNumber", - params: ["0x66", true] + params: ["0x67", true] } ], _ -> - res = eth_block_number_fake_response("0x66") + res = eth_block_number_fake_response("0x67") {:ok, [res]} end) - assert CoinBalanceOnDemand.trigger_fetch(address) == {:pending, 102} + assert CoinBalanceOnDemand.trigger_fetch(address) == {:pending, 103} {:ok, expected_wei} = Wei.cast(2) assert_receive( {:chain_event, :addresses, :on_demand, - [%{hash: ^address_hash, fetched_coin_balance: ^expected_wei, fetched_coin_balance_block_number: 102}]} + [%{hash: ^address_hash, fetched_coin_balance: ^expected_wei, fetched_coin_balance_block_number: 103}]} ) end end From a7165562a5f7fb04aac4bef1013d8b1c7a54459d Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 26 Jan 2024 11:46:57 +0300 Subject: [PATCH 342/607] Change PR number of tx denormalization --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8bbdc21001b..fcc1b7ffca4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,7 +71,7 @@ - [#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 +- [#8776](https://github.com/blockscout/blockscout/pull/8776) - DB denormalization: block consensus and timestamp in transaction table
Dependencies version bumps From 47cef53dbfb66f1dbed80f86cb5bcada568dd743 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Thu, 18 Jan 2024 10:05:47 +0300 Subject: [PATCH 343/607] Fix getblockreward; Add getblockcountdown actions --- CHANGELOG.md | 2 +- .../controllers/api/rpc/block_controller.ex | 56 ++++- .../controllers/api/rpc/rpc_translator.ex | 9 +- .../controllers/api/rpc/stats_controller.ex | 2 + .../views/api/rpc/block_view.ex | 64 +++++- .../views/api/rpc/stats_view.ex | 6 +- .../api/rpc/block_controller_test.exs | 202 ++++++++++++++++-- .../api/rpc/rpc_translator_test.exs | 2 +- apps/explorer/lib/explorer/chain.ex | 52 ----- apps/explorer/lib/explorer/chain/block.ex | 2 + .../explorer/counters/average_block_time.ex | 5 +- apps/explorer/test/explorer/chain_test.exs | 33 --- cspell.json | 3 + 13 files changed, 317 insertions(+), 121 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcc1b7ffca4a..758b6446de72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ - [#9132](https://github.com/blockscout/blockscout/pull/9132) - Fetch token image from CoinGecko - [#9131](https://github.com/blockscout/blockscout/pull/9131) - Merge addresses stage with address referencing - [#9072](https://github.com/blockscout/blockscout/pull/9072) - Add tracing by block logic for geth -- [#9068](https://github.com/blockscout/blockscout/pull/9068) - New RPC API v1 endpoints +- [#9185](https://github.com/blockscout/blockscout/pull/9185), [#9068](https://github.com/blockscout/blockscout/pull/9068) - New RPC API v1 endpoints - [#9056](https://github.com/blockscout/blockscout/pull/9056) - Noves.fi API proxy ### Fixes diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex index 68256be97564..5c505e4f2239 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex @@ -4,14 +4,27 @@ defmodule BlockScoutWeb.API.RPC.BlockController do alias BlockScoutWeb.Chain, as: ChainWeb alias Explorer.Chain alias Explorer.Chain.Cache.BlockNumber + alias Explorer.Counters.AverageBlockTime + alias Timex.Duration + @doc """ + Reward for mining a block. + + The block reward is the sum of the following: + + * Sum of the transaction fees (gas_used * gas_price) for the block + * A static reward for miner (this value may change during the life of the chain) + * The reward for uncle blocks (1/32 * static_reward * number_of_uncles) + """ def getblockreward(conn, params) do with {:block_param, {:ok, unsafe_block_number}} <- {:block_param, Map.fetch(params, "blockno")}, {:ok, block_number} <- ChainWeb.param_to_block_number(unsafe_block_number), - {:ok, block} <- Chain.number_to_block(block_number) do - reward = Chain.block_reward(block_number) - - render(conn, :block_reward, block: block, reward: reward) + {:ok, block} <- + Chain.number_to_block(block_number, + necessity_by_association: %{rewards: :optional}, + api?: true + ) do + render(conn, :block_reward, block: block) else {:block_param, :error} -> render(conn, :error, error: "Query parameter 'blockno' is required") @@ -24,6 +37,41 @@ defmodule BlockScoutWeb.API.RPC.BlockController do end end + def getblockcountdown(conn, params) do + with {:block_param, {:ok, unsafe_target_block_number}} <- {:block_param, Map.fetch(params, "blockno")}, + {:ok, target_block_number} <- ChainWeb.param_to_block_number(unsafe_target_block_number), + {:max_block, current_block_number} when not is_nil(current_block_number) <- + {:max_block, BlockNumber.get_max()}, + {:average_block_time, average_block_time} when is_struct(average_block_time) <- + {:average_block_time, AverageBlockTime.average_block_time()}, + {:remaining_blocks, remaining_blocks} when remaining_blocks > 0 <- + {:remaining_blocks, target_block_number - current_block_number} do + estimated_time_in_sec = Float.round(remaining_blocks * Duration.to_seconds(average_block_time), 1) + + render(conn, :block_countdown, + current_block: current_block_number, + countdown_block: target_block_number, + remaining_blocks: remaining_blocks, + estimated_time_in_sec: estimated_time_in_sec + ) + else + {:block_param, :error} -> + render(conn, :error, error: "Query parameter 'blockno' is required") + + {:error, :invalid} -> + render(conn, :error, error: "Invalid block number") + + {:average_block_time, {:error, :disabled}} -> + render(conn, :error, error: "Average block time calculating is disabled, so getblockcountdown is not available") + + {stage, _} when stage in ~w(max_block average_block_time)a -> + render(conn, :error, error: "Chain is indexing now, try again later") + + {:remaining_blocks, _} -> + render(conn, :error, error: "Error! Block number already pass") + end + end + def getblocknobytime(conn, params) do from_api = true diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex index b27554eed39b..7abf738f8915 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex @@ -44,6 +44,13 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do |> Controller.render(:error, error: "Unknown action") |> halt() + {:error, :no_module} -> + conn + |> put_status(400) + |> put_view(RPCView) + |> Controller.render(:error, error: "Unknown module") + |> halt() + {:error, error} -> APILogger.error(fn -> ["Error while calling RPC action", inspect(error, limit: :infinity, printable_limit: :infinity)] @@ -89,7 +96,7 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do case Map.fetch(translations, module_lowercase) do {:ok, module} -> {:ok, module} - _ -> {:error, :no_action} + _ -> {:error, :no_module} end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex index 19fcf8768c64..fec113b4ee55 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex @@ -59,6 +59,8 @@ defmodule BlockScoutWeb.API.RPC.StatsController do render(conn, "coinsupply.json", total_supply: cached_coin_total_supply) end + def ethprice(conn, params), do: coinprice(conn, params) + def coinprice(conn, _params) do rates = Market.get_coin_exchange_rate() diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex index 70d5f0bd0441..98f976d75b3d 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex @@ -3,22 +3,76 @@ defmodule BlockScoutWeb.API.RPC.BlockView do alias BlockScoutWeb.API.EthRPC.View, as: EthRPCView alias BlockScoutWeb.API.RPC.RPCView - alias Explorer.Chain.{Hash, Wei} + alias Explorer.Chain.{Block, Hash, Wei} alias Explorer.EthRPC, as: EthRPC - def render("block_reward.json", %{block: block, reward: reward}) do + def render("block_reward.json", %{block: %Block{rewards: [_ | _]} = block}) do reward_as_string = - reward + block.rewards + |> Enum.find(%{reward: %Wei{value: Decimal.new(0)}}, &(&1.address_type == :validator)) + |> Map.get(:reward) |> Wei.to(:wei) |> Decimal.to_string(:normal) + static_reward = + block.rewards + |> Enum.find(%{reward: %Wei{value: Decimal.new(0)}}, &(&1.address_type == :emission_funds)) + |> Map.get(:reward) + |> Wei.to(:wei) + + uncles = + block.rewards + |> Stream.filter(&(&1.address_type == :uncle)) + |> Stream.with_index() + |> Enum.map(fn {reward, index} -> + %{ + "unclePosition" => to_string(index), + "miner" => Hash.to_string(reward.address_hash), + "blockreward" => reward.reward |> Wei.to(:wei) |> Decimal.to_string(:normal) + } + end) + data = %{ "blockNumber" => to_string(block.number), "timeStamp" => DateTime.to_unix(block.timestamp), "blockMiner" => Hash.to_string(block.miner_hash), "blockReward" => reward_as_string, - "uncles" => nil, - "uncleInclusionReward" => nil + "uncles" => uncles, + "uncleInclusionReward" => + static_reward + |> Decimal.mult(Enum.count(uncles)) + |> Decimal.mult(Decimal.from_float(Block.uncle_reward_coef())) + |> Decimal.normalize() + |> Decimal.to_string(:normal) + } + + RPCView.render("show.json", data: data) + end + + def render("block_reward.json", %{block: block}) do + data = %{ + "blockNumber" => to_string(block.number), + "timeStamp" => DateTime.to_unix(block.timestamp), + "blockMiner" => Hash.to_string(block.miner_hash), + "blockReward" => "0", + "uncles" => [], + "uncleInclusionReward" => "0" + } + + RPCView.render("show.json", data: data) + end + + def render("block_countdown.json", %{ + current_block: current_block, + countdown_block: countdown_block, + remaining_blocks: remaining_blocks, + estimated_time_in_sec: estimated_time_in_sec + }) do + data = %{ + "CurrentBlock" => to_string(current_block), + "CountdownBlock" => to_string(countdown_block), + "RemainingBlock" => to_string(remaining_blocks), + "EstimateTimeInSec" => to_string(estimated_time_in_sec) } RPCView.render("show.json", data: data) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/stats_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/stats_view.ex index 91f6967070da..a239c2eb9aac 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/stats_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/stats_view.ex @@ -33,12 +33,12 @@ defmodule BlockScoutWeb.API.RPC.StatsView do defp prepare_rates(rates) do if rates do - timestamp = rates.last_updated |> DateTime.to_unix() |> to_string() + timestamp = rates.last_updated && rates.last_updated |> DateTime.to_unix() |> to_string() %{ - "coin_btc" => to_string(rates.btc_value), + "coin_btc" => rates.btc_value && to_string(rates.btc_value), "coin_btc_timestamp" => timestamp, - "coin_usd" => to_string(rates.usd_value), + "coin_usd" => rates.usd_value && to_string(rates.usd_value), "coin_usd_timestamp" => timestamp } else diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/block_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/block_controller_test.exs index 70e9726fe7cc..ef8ce3513d47 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/block_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/block_controller_test.exs @@ -1,8 +1,11 @@ defmodule BlockScoutWeb.API.RPC.BlockControllerTest do use BlockScoutWeb.ConnCase - alias Explorer.Chain.{Hash, Wei} + import EthereumJSONRPC, only: [integer_to_quantity: 1] + alias BlockScoutWeb.Chain + alias Explorer.Chain.{Hash, Wei} + alias Explorer.Counters.AverageBlockTime describe "getblockreward" do test "with missing block number", %{conn: conn} do @@ -15,7 +18,7 @@ defmodule BlockScoutWeb.API.RPC.BlockControllerTest do assert response["status"] == "0" assert Map.has_key?(response, "result") refute response["result"] - schema = resolve_schema() + schema = resolve_getblockreward_schema() assert :ok = ExJsonSchema.Validator.validate(schema, response) end @@ -29,7 +32,7 @@ defmodule BlockScoutWeb.API.RPC.BlockControllerTest do assert response["status"] == "0" assert Map.has_key?(response, "result") refute response["result"] - schema = resolve_schema() + schema = resolve_getblockreward_schema() assert :ok = ExJsonSchema.Validator.validate(schema, response) end @@ -43,7 +46,7 @@ defmodule BlockScoutWeb.API.RPC.BlockControllerTest do assert response["status"] == "0" assert Map.has_key?(response, "result") refute response["result"] - schema = resolve_schema() + schema = resolve_getblockreward_schema() assert :ok = ExJsonSchema.Validator.validate(schema, response) end @@ -55,19 +58,103 @@ defmodule BlockScoutWeb.API.RPC.BlockControllerTest do |> insert(gas_price: 1) |> with_block(block, gas_used: 1) + block_quantity = integer_to_quantity(block.number) + expected_reward = emission_reward.reward |> Wei.to(:wei) |> Decimal.add(Decimal.new(1)) - |> Decimal.to_string(:normal) + + insert(:reward, address_hash: block.miner_hash, block_hash: block.hash, reward: expected_reward) + + expected_result = %{ + "blockNumber" => "#{block.number}", + "timeStamp" => DateTime.to_unix(block.timestamp), + "blockMiner" => Hash.to_string(block.miner_hash), + "blockReward" => expected_reward |> Decimal.to_string(:normal), + "uncles" => [], + "uncleInclusionReward" => "0" + } + + assert response = + conn + |> get("/api", %{"module" => "block", "action" => "getblockreward", "blockno" => "#{block.number}"}) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + schema = resolve_getblockreward_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + end + + test "with a valid block and uncles", %{conn: conn} do + %{block_range: range} = emission_reward = insert(:emission_reward) + block = insert(:block, number: Enum.random(Range.new(range.from + 2, range.to))) + uncle1 = insert(:block, number: block.number - 1) + uncle2 = insert(:block, number: block.number - 2) + + insert(:block_second_degree_relation, nephew: block, uncle_hash: uncle1.hash, index: 0) + insert(:block_second_degree_relation, nephew: block, uncle_hash: uncle2.hash, index: 1) + + :transaction + |> insert(gas_price: 1) + |> with_block(block, gas_used: 1) + + block_quantity = integer_to_quantity(block.number) + + decimal_emission_reward = Wei.to(emission_reward.reward, :wei) + + uncle1_reward = + decimal_emission_reward |> Decimal.div(8) |> Decimal.mult(Decimal.new(uncle1.number + 8 - block.number)) + + uncle2_reward = + decimal_emission_reward |> Decimal.div(8) |> Decimal.mult(Decimal.new(uncle2.number + 8 - block.number)) + + uncle_inclusion_reward = + decimal_emission_reward + |> Decimal.div(Decimal.new(32)) + |> Decimal.mult(Decimal.new(2)) + + block_reward = + decimal_emission_reward + |> Decimal.add(Decimal.new(1)) + |> Decimal.add(uncle_inclusion_reward) + + insert(:reward, address_hash: block.miner_hash, block_hash: block.hash, reward: block_reward) + + insert(:reward, + address_hash: uncle1.miner_hash, + block_hash: block.hash, + reward: uncle1_reward, + address_type: :uncle + ) + + insert(:reward, + address_hash: uncle2.miner_hash, + block_hash: block.hash, + reward: uncle2_reward, + address_type: :uncle + ) expected_result = %{ "blockNumber" => "#{block.number}", "timeStamp" => DateTime.to_unix(block.timestamp), "blockMiner" => Hash.to_string(block.miner_hash), - "blockReward" => expected_reward, - "uncles" => nil, - "uncleInclusionReward" => nil + "blockReward" => block_reward |> Decimal.to_string(:normal), + "uncles" => [ + %{ + "blockreward" => uncle1_reward |> Decimal.to_string(:normal), + "miner" => uncle1.miner_hash |> Hash.to_string(), + "unclePosition" => "0" + }, + %{ + "blockreward" => uncle2_reward |> Decimal.to_string(:normal), + "miner" => uncle2.miner_hash |> Hash.to_string(), + "unclePosition" => "1" + } + ], + "uncleInclusionReward" => "0" } assert response = @@ -78,7 +165,57 @@ defmodule BlockScoutWeb.API.RPC.BlockControllerTest do assert response["result"] == expected_result assert response["status"] == "1" assert response["message"] == "OK" - schema = resolve_schema() + schema = resolve_getblockreward_schema() + assert :ok = ExJsonSchema.Validator.validate(schema, response) + end + end + + describe "getblockcountdown" do + setup do + start_supervised!(AverageBlockTime) + Application.put_env(:explorer, AverageBlockTime, enabled: true, cache_period: 1_800_000) + + on_exit(fn -> + Application.put_env(:explorer, AverageBlockTime, enabled: false, cache_period: 1_800_000) + end) + end + + test "returns countdown information when valid block number is provided", %{conn: conn} do + unsafe_target_block_number = "120" + current_block_number = 110 + average_block_time = 15 + remaining_blocks = 10 + + first_timestamp = Timex.now() + + for i <- 1..current_block_number do + insert(:block, number: i, timestamp: Timex.shift(first_timestamp, seconds: i * average_block_time)) + end + + AverageBlockTime.refresh() + + estimated_time_in_sec = Float.round(remaining_blocks * average_block_time * 1.0, 1) + + expected_result = %{ + "CurrentBlock" => "#{current_block_number}", + "CountdownBlock" => unsafe_target_block_number, + "RemainingBlock" => "#{remaining_blocks}", + "EstimateTimeInSec" => "#{estimated_time_in_sec}" + } + + response = + conn + |> get("/api", %{ + "module" => "block", + "action" => "getblockcountdown", + "blockno" => unsafe_target_block_number + }) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + schema = resolve_getblockcountdown_schema() assert :ok = ExJsonSchema.Validator.validate(schema, response) end end @@ -94,7 +231,7 @@ defmodule BlockScoutWeb.API.RPC.BlockControllerTest do assert response["status"] == "0" assert Map.has_key?(response, "result") refute response["result"] - schema = resolve_schema() + schema = resolve_getblockreward_schema() assert :ok = ExJsonSchema.Validator.validate(schema, response) end @@ -108,7 +245,7 @@ defmodule BlockScoutWeb.API.RPC.BlockControllerTest do assert response["status"] == "0" assert Map.has_key?(response, "result") refute response["result"] - schema = resolve_schema() + schema = resolve_getblockreward_schema() assert :ok = ExJsonSchema.Validator.validate(schema, response) end @@ -127,7 +264,7 @@ defmodule BlockScoutWeb.API.RPC.BlockControllerTest do assert response["status"] == "0" assert Map.has_key?(response, "result") refute response["result"] - schema = resolve_schema() + schema = resolve_getblockreward_schema() assert :ok = ExJsonSchema.Validator.validate(schema, response) end @@ -146,7 +283,7 @@ defmodule BlockScoutWeb.API.RPC.BlockControllerTest do assert response["status"] == "0" assert Map.has_key?(response, "result") refute response["result"] - schema = resolve_schema() + schema = resolve_getblockreward_schema() assert :ok = ExJsonSchema.Validator.validate(schema, response) end @@ -178,7 +315,7 @@ defmodule BlockScoutWeb.API.RPC.BlockControllerTest do assert response["result"] == expected_result assert response["status"] == "1" assert response["message"] == "OK" - schema = resolve_schema() + schema = resolve_getblockreward_schema() assert :ok = ExJsonSchema.Validator.validate(schema, response) end @@ -210,12 +347,12 @@ defmodule BlockScoutWeb.API.RPC.BlockControllerTest do assert response["result"] == expected_result assert response["status"] == "1" assert response["message"] == "OK" - schema = resolve_schema() + schema = resolve_getblockreward_schema() assert :ok = ExJsonSchema.Validator.validate(schema, response) end end - defp resolve_schema() do + defp resolve_getblockreward_schema() do ExJsonSchema.Schema.resolve(%{ "type" => "object", "properties" => %{ @@ -228,8 +365,37 @@ defmodule BlockScoutWeb.API.RPC.BlockControllerTest do "timeStamp" => %{"type" => "number"}, "blockMiner" => %{"type" => "string"}, "blockReward" => %{"type" => "string"}, - "uncles" => %{"type" => "null"}, - "uncleInclusionReward" => %{"type" => "null"} + "uncles" => %{ + "type" => "array", + "items" => %{ + "type" => "object", + "properties" => %{ + "miner" => %{"type" => "string"}, + "unclePosition" => %{"type" => "string"}, + "blockreward" => %{"type" => "string"} + } + } + }, + "uncleInclusionReward" => %{"type" => "string"} + } + } + } + }) + end + + defp resolve_getblockcountdown_schema() do + ExJsonSchema.Schema.resolve(%{ + "type" => "object", + "properties" => %{ + "message" => %{"type" => "string"}, + "status" => %{"type" => "string"}, + "result" => %{ + "type" => "object", + "properties" => %{ + "CurrentBlock" => %{"type" => "string"}, + "CountdownBlock" => %{"type" => "string"}, + "RemainingBlock" => %{"type" => "string"}, + "EstimateTimeInSec" => %{"type" => "string"} } } } diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/rpc_translator_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/rpc_translator_test.exs index a5b23b25323b..fa7222926ed7 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/rpc_translator_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/rpc_translator_test.exs @@ -28,7 +28,7 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslatorTest do result = RPCTranslator.call(conn, %{}) assert result.halted assert response = json_response(result, 400) - assert response["message"] =~ "Unknown action" + assert response["message"] =~ "Unknown module" assert response["status"] == "0" assert Map.has_key?(response, "result") refute response["result"] diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 014c7ff3bebf..a985998e5485 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -66,8 +66,6 @@ defmodule Explorer.Chain do Withdrawal } - alias Explorer.Chain.Block.{EmissionReward, Reward} - alias Explorer.Chain.Cache.{ BlockNumber, Blocks, @@ -475,56 +473,6 @@ defmodule Explorer.Chain do Repo.aggregate(Block, :count, :hash) end - @doc """ - Reward for mining a block. - - The block reward is the sum of the following: - - * Sum of the transaction fees (gas_used * gas_price) for the block - * A static reward for miner (this value may change during the life of the chain) - * The reward for uncle blocks (1/32 * static_reward * number_of_uncles) - - *NOTE* - - Uncles are not currently accounted for. - """ - @spec block_reward(Block.block_number()) :: Wei.t() - def block_reward(block_number) do - block_hash = - Block - |> where([block], block.number == ^block_number and block.consensus == true) - |> select([block], block.hash) - |> Repo.one!() - - case Repo.one!( - from(reward in Reward, - where: reward.block_hash == ^block_hash, - select: %Wei{ - value: coalesce(sum(reward.reward), 0) - } - ) - ) do - %Wei{ - value: %Decimal{coef: 0} - } -> - Repo.one!( - from(block in Block, - left_join: transaction in assoc(block, :transactions), - inner_join: emission_reward in EmissionReward, - on: fragment("? <@ ?", block.number, emission_reward.block_range), - where: block.number == ^block_number and block.consensus == true, - group_by: [emission_reward.reward, block.hash], - select: %Wei{ - value: coalesce(sum(transaction.gas_used * transaction.gas_price), 0) + emission_reward.reward - } - ) - ) - - other_value -> - other_value - end - end - @doc """ The `t:Explorer.Chain.Wei.t/0` paid to the miners of the `t:Explorer.Chain.Block.t/0`s with `hash` `Explorer.Chain.Hash.Full.t/0` by the signers of the transactions in those blocks to cover the gas fee diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index fae356f3bcfa..cdf6381b9772 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -305,4 +305,6 @@ defmodule Explorer.Chain.Block do uncle_reward: uncle_reward || %Wei{value: Decimal.new(0)} } end + + def uncle_reward_coef, do: @uncle_reward_coef end diff --git a/apps/explorer/lib/explorer/counters/average_block_time.ex b/apps/explorer/lib/explorer/counters/average_block_time.ex index 2ca84bc87612..02e8e3464705 100644 --- a/apps/explorer/lib/explorer/counters/average_block_time.ex +++ b/apps/explorer/lib/explorer/counters/average_block_time.ex @@ -1,9 +1,8 @@ defmodule Explorer.Counters.AverageBlockTime do - use GenServer - @moduledoc """ - Caches the number of token holders of a token. + Caches the average block time in milliseconds. """ + use GenServer import Ecto.Query, only: [from: 2, where: 2] diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 9c6236301dd2..be01544eeecc 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -3114,39 +3114,6 @@ defmodule Explorer.ChainTest do end end - describe "block_reward/1" do - setup do - %{block_range: range} = emission_reward = insert(:emission_reward) - - block = insert(:block, number: Enum.random(Range.new(range.from, range.to))) - insert(:transaction) - - {:ok, block: block, emission_reward: emission_reward} - end - - test "with block containing transactions", %{block: block, emission_reward: emission_reward} do - :transaction - |> insert(gas_price: 1) - |> with_block(block, gas_used: 1) - - :transaction - |> insert(gas_price: 1) - |> with_block(block, gas_used: 2) - - expected = - emission_reward.reward - |> Wei.to(:wei) - |> Decimal.add(Decimal.new(3)) - |> Wei.from(:wei) - - assert expected == Chain.block_reward(block.number) - end - - test "with block without transactions", %{block: block, emission_reward: emission_reward} do - assert emission_reward.reward == Chain.block_reward(block.number) - end - end - describe "gas_payment_by_block_hash/1" do setup do number = 1 diff --git a/cspell.json b/cspell.json index 1712455438d7..e2289d76f9b2 100644 --- a/cspell.json +++ b/cspell.json @@ -50,6 +50,7 @@ "blockheight", "blockless", "blockno", + "blockreward", "blockscout", "blockscoutuser", "bools", @@ -154,6 +155,7 @@ "erts", "Ethash", "etherchain", + "ethprice", "ethsupply", "ethsupplyexchange", "etimedout", @@ -181,6 +183,7 @@ "fwupv", "getabi", "getblockbyhash", + "getblockcountdown", "getblocknobytime", "getblockreward", "getlogs", From b8df64b9eac79f4a9c35fa665356fdf78d3f93be Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop <105209995+Qwerty5Uiop@users.noreply.github.com> Date: Fri, 26 Jan 2024 14:14:23 +0400 Subject: [PATCH 344/607] Fix pending transactions sanitizer (#9261) * Fix pending transactions sanitizer * Add block consensus filters to list_transactions and list_token_transfers --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/etherscan.ex | 19 ++++++++++++------ .../indexer/pending_transactions_sanitizer.ex | 20 +++++++++---------- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcc1b7ffca4a..5a2b8a233134 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ ### Fixes +- [#9261](https://github.com/blockscout/blockscout/pull/9261) - Fix pending transactions sanitizer - [#9241](https://github.com/blockscout/blockscout/pull/9241) - Fix log decoding bug - [#9229](https://github.com/blockscout/blockscout/pull/9229) - Add missing filter to txlist query - [#9187](https://github.com/blockscout/blockscout/pull/9187) - Fix Internal Server Error on request for nonexistent token instance diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index 070e8e2cd276..0228ad7eb556 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -4,7 +4,7 @@ defmodule Explorer.Etherscan do """ import Ecto.Query, - only: [from: 2, where: 3, or_where: 3, union: 2, subquery: 1, order_by: 3, limit: 2, offset: 2, preload: 3] + only: [from: 2, where: 3, union: 2, subquery: 1, order_by: 3, limit: 2, offset: 2, preload: 3] import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] @@ -474,6 +474,7 @@ defmodule Explorer.Etherscan do from( t in Transaction, where: not is_nil(t.block_hash), + where: t.block_consensus == true, order_by: [{^options.order_by_direction, t.block_number}], limit: ^options.page_size, offset: ^offset(options), @@ -486,6 +487,7 @@ defmodule Explorer.Etherscan do from( t in Transaction, inner_join: b in assoc(t, :block), + where: b.consensus == true, order_by: [{^options.order_by_direction, t.block_number}], limit: ^options.page_size, offset: ^offset(options), @@ -515,10 +517,12 @@ defmodule Explorer.Etherscan do end defp where_address_match(query, address_hash, _) do - query - |> where([t], t.to_address_hash == ^address_hash) - |> or_where([t], t.from_address_hash == ^address_hash) - |> or_where([t], t.created_contract_address_hash == ^address_hash) + where( + query, + [t], + t.to_address_hash == ^address_hash or t.from_address_hash == ^address_hash or + t.created_contract_address_hash == ^address_hash + ) end @token_transfer_fields ~w( @@ -564,7 +568,9 @@ defmodule Explorer.Etherscan 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, + on: + tt.transaction_hash == t.hash and tt.block_number == t.block_number and tt.block_hash == t.block_hash and + t.block_consensus == true, 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, @@ -598,6 +604,7 @@ defmodule Explorer.Etherscan do 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), + where: b.consensus == true, 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, diff --git a/apps/indexer/lib/indexer/pending_transactions_sanitizer.ex b/apps/indexer/lib/indexer/pending_transactions_sanitizer.ex index 6e6053e7b35a..17de593bc01a 100644 --- a/apps/indexer/lib/indexer/pending_transactions_sanitizer.ex +++ b/apps/indexer/lib/indexer/pending_transactions_sanitizer.ex @@ -13,7 +13,6 @@ defmodule Indexer.PendingTransactionsSanitizer do alias Ecto.Changeset alias Explorer.{Chain, Repo} - alias Explorer.Chain.Hash.Full, as: Hash alias Explorer.Chain.Import.Runner.Blocks alias Explorer.Chain.Transaction @@ -138,13 +137,13 @@ defmodule Indexer.PendingTransactionsSanitizer do defp fetch_block_and_invalidate(block_hash, pending_tx, tx) do case Chain.fetch_block_by_hash(block_hash) do - %{number: number, consensus: consensus} -> + %{number: number, consensus: consensus} = block -> Logger.debug( "Corresponding number of the block with hash #{block_hash} to invalidate is #{number} and consensus #{consensus}", fetcher: :pending_transactions_to_refetch ) - invalidate_block(number, block_hash, consensus, pending_tx, tx) + invalidate_block(block, pending_tx, tx) _ -> Logger.debug( @@ -154,11 +153,10 @@ defmodule Indexer.PendingTransactionsSanitizer do end end - defp invalidate_block(block_number, block_hash, consensus, pending_tx, tx) do - if consensus do - Blocks.invalidate_consensus_blocks([block_number]) + defp invalidate_block(block, pending_tx, tx) do + if block.consensus do + Blocks.invalidate_consensus_blocks([block.number]) else - {:ok, hash} = Hash.cast(block_hash) tx_info = to_elixir(tx) changeset = @@ -167,13 +165,15 @@ defmodule Indexer.PendingTransactionsSanitizer do |> Changeset.put_change(:cumulative_gas_used, tx_info["cumulativeGasUsed"]) |> Changeset.put_change(:gas_used, tx_info["gasUsed"]) |> Changeset.put_change(:index, tx_info["transactionIndex"]) - |> Changeset.put_change(:block_number, block_number) - |> Changeset.put_change(:block_hash, hash) + |> Changeset.put_change(:block_number, block.number) + |> Changeset.put_change(:block_hash, block.hash) + |> Changeset.put_change(:block_timestamp, block.timestamp) + |> Changeset.put_change(:block_consensus, false) Repo.update(changeset) Logger.debug( - "Pending tx with hash #{"0x" <> Base.encode16(pending_tx.hash.bytes, case: :lower)} assigned to block ##{block_number} with hash #{block_hash}" + "Pending tx with hash #{"0x" <> Base.encode16(pending_tx.hash.bytes, case: :lower)} assigned to block ##{block.number} with hash #{block.hash}" ) end end From 6df35215643d16ea95b818136ad1ea727c1ca479 Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Fri, 26 Jan 2024 13:20:09 +0300 Subject: [PATCH 345/607] Add GET and POST /api/v2/smart-contracts/:address_hash/audit-reports (#9120) * Add GET and POST /api/v2/smart-contracts/:address_hash/audit-reports * Add envs to .env file * Rename envs * Process reviewer's comments * Handle null envs --- .github/workflows/config.yml | 26 +-- CHANGELOG.md | 1 + .../controllers/api/v2/fallback_controller.ex | 17 ++ .../api/v2/smart_contract_controller.ex | 72 ++++++++- .../smart_contracts_api_v2_router.ex | 2 + .../views/api/v2/smart_contract_view.ex | 12 ++ .../explorer/account/public_tags_request.ex | 16 ++ .../chain/smart_contract/audit_report.ex | 149 ++++++++++++++++++ .../lib/explorer/chain_spec/genesis_data.ex | 9 +- apps/explorer/lib/explorer/helper.ex | 10 ++ .../third_party_integrations/airtable.ex | 81 +++++++--- ...add_smart_contract_audit_reports_table.exs | 39 +++++ config/runtime.exs | 12 +- docker-compose/envs/common-blockscout.env | 2 + 14 files changed, 397 insertions(+), 51 deletions(-) create mode 100644 apps/explorer/lib/explorer/chain/smart_contract/audit_report.ex create mode 100644 apps/explorer/priv/repo/migrations/20231229120232_add_smart_contract_audit_reports_table.exs diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 4c59f5a582d6..e9566d63ac52 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -75,7 +75,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_36-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_37-${{ 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_36-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_37-${{ 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_36-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_37-${{ 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_36-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_37-${{ 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_36-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_37-${{ 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_36-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_37-${{ 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_36-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_37-${{ 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_36-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_37-${{ 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_36-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_37-${{ 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_36-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_37-${{ 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_36-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_37-${{ 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_36-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_37-${{ 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_36-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_37-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a2b8a233134..6060acecc726 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - [#9145](https://github.com/blockscout/blockscout/pull/9145) - Proxy for Account abstraction microservice - [#9132](https://github.com/blockscout/blockscout/pull/9132) - Fetch token image from CoinGecko - [#9131](https://github.com/blockscout/blockscout/pull/9131) - Merge addresses stage with address referencing +- [#9120](https://github.com/blockscout/blockscout/pull/9120) - Add GET and POST `/api/v2/smart-contracts/:address_hash/audit-reports` - [#9072](https://github.com/blockscout/blockscout/pull/9072) - Add tracing by block logic for geth - [#9068](https://github.com/blockscout/blockscout/pull/9068) - New RPC API v1 endpoints - [#9056](https://github.com/blockscout/blockscout/pull/9056) - Noves.fi API proxy diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex index c7f1fc3692ee..d08a4705fb92 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex @@ -3,7 +3,9 @@ defmodule BlockScoutWeb.API.V2.FallbackController do require Logger + alias BlockScoutWeb.Account.Api.V1.UserView alias BlockScoutWeb.API.V2.ApiView + alias Ecto.Changeset @verification_failed "API v2 smart-contract verification failed" @invalid_parameters "Invalid parameter(s)" @@ -27,6 +29,7 @@ defmodule BlockScoutWeb.API.V2.FallbackController do @address_is_not_smart_contract "Address is not smart-contract" @empty_response "Empty response" @tx_interpreter_service_disabled "Transaction Interpretation Service is not enabled" + @disabled "API endpoint is disabled" def call(conn, {:format, _params}) do Logger.error(fn -> @@ -123,6 +126,13 @@ defmodule BlockScoutWeb.API.V2.FallbackController do |> call({:not_found, nil}) end + def call(conn, {:error, %Changeset{} = changeset}) do + conn + |> put_status(:unprocessable_entity) + |> put_view(UserView) + |> render(:changeset_errors, changeset: changeset) + end + def call(conn, {:restricted_access, true}) do Logger.error(fn -> ["#{@verification_failed}: #{@restricted_access}"] @@ -264,4 +274,11 @@ defmodule BlockScoutWeb.API.V2.FallbackController do |> put_view(ApiView) |> render(:message, %{message: @tx_interpreter_service_disabled}) end + + def call(conn, {:disabled, _}) do + conn + |> put_status(:forbidden) + |> put_view(ApiView) + |> render(:message, %{message: @disabled}) + end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex index dfc4836bd9ca..294cfaab5f1f 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex @@ -13,6 +13,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do alias Ecto.Association.NotLoaded alias Explorer.Chain alias Explorer.Chain.{Address, SmartContract} + alias Explorer.Chain.SmartContract.AuditReport alias Explorer.SmartContract.{Reader, Writer} alias Explorer.SmartContract.Solidity.PublishHelper alias Explorer.ThirdPartyIntegrations.SolidityScan @@ -59,10 +60,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do end def methods_read(conn, %{"address_hash" => address_hash_string} = params) do - with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, - {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - smart_contract <- SmartContract.address_hash_to_smart_contract(address_hash, @api_true), - {:not_found, false} <- {:not_found, is_nil(smart_contract)} do + with {:ok, address_hash, smart_contract} <- validate_smart_contract(params, address_hash_string) do read_only_functions_from_abi = Reader.read_only_functions(smart_contract, address_hash, params["from"]) read_functions_required_wallet_from_abi = Reader.read_functions_required_wallet(smart_contract) @@ -89,10 +87,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do def methods_write(conn, %{"address_hash" => address_hash_string} = params) do with {:contract_interaction_disabled, false} <- {:contract_interaction_disabled, AddressView.contract_interaction_disabled?()}, - {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, - {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - smart_contract <- SmartContract.address_hash_to_smart_contract(address_hash, @api_true), - {:not_found, false} <- {:not_found, is_nil(smart_contract)} do + {:ok, _address_hash, smart_contract} <- validate_smart_contract(params, address_hash_string) do conn |> put_status(200) |> json(smart_contract |> Writer.write_functions() |> Reader.get_abi_with_method_id()) @@ -235,6 +230,58 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do |> render(:smart_contracts, %{smart_contracts: smart_contracts, next_page_params: next_page_params}) end + @doc """ + POST /api/v2/smart-contracts/{address_hash}/audit-reports + """ + @spec audit_report_submission(Plug.Conn.t(), map()) :: + {:error, Ecto.Changeset.t()} + | {:format, :error} + | {:not_found, nil | Explorer.Chain.SmartContract.t()} + | {:recaptcha, any()} + | {:restricted_access, true} + | Plug.Conn.t() + def audit_report_submission(conn, %{"address_hash" => address_hash_string} = params) do + captcha_helper = Application.get_env(:block_scout_web, :captcha_helper) + + with {:disabled, true} <- {:disabled, Application.get_env(:explorer, :air_table_audit_reports)[:enabled]}, + {:ok, address_hash, _smart_contract} <- validate_smart_contract(params, address_hash_string), + {:recaptcha, _} <- {:recaptcha, captcha_helper.recaptcha_passed?(params["recaptcha_response"])}, + audit_report_params <- %{ + address_hash: address_hash, + submitter_name: params["submitter_name"], + submitter_email: params["submitter_email"], + is_project_owner: params["is_project_owner"], + project_name: params["project_name"], + project_url: params["project_url"], + audit_company_name: params["audit_company_name"], + audit_report_url: params["audit_report_url"], + audit_publish_date: params["audit_publish_date"], + comment: params["comment"] + }, + {:ok, _} <- AuditReport.create(audit_report_params) do + conn + |> put_status(200) + |> json(%{message: "OK"}) + end + end + + @doc """ + GET /api/v2/smart-contracts/{address_hash}/audit-reports + """ + @spec audit_reports_list(Plug.Conn.t(), map()) :: + {:format, :error} + | {:not_found, nil | Explorer.Chain.SmartContract.t()} + | {:restricted_access, true} + | Plug.Conn.t() + def audit_reports_list(conn, %{"address_hash" => address_hash_string} = params) do + with {:ok, address_hash, _smart_contract} <- validate_smart_contract(params, address_hash_string) do + reports = AuditReport.get_audit_reports_by_smart_contract_address_hash(address_hash, @api_true) + + conn + |> render(:audit_reports, %{reports: reports}) + end + end + def smart_contracts_counters(conn, _params) do conn |> json(%{ @@ -247,4 +294,13 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do def prepare_args(list) when is_list(list), do: list def prepare_args(other), do: [other] + + defp validate_smart_contract(params, address_hash_string) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), + {:not_found, smart_contract} when not is_nil(smart_contract) <- + {:not_found, SmartContract.address_hash_to_smart_contract(address_hash, @api_true)} do + {:ok, address_hash, smart_contract} + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex b/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex index ad8bf8ad9609..aad961f117f5 100644 --- a/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex @@ -28,6 +28,8 @@ defmodule BlockScoutWeb.SmartContractsApiV2Router do get("/:address_hash/methods-write-proxy", V2.SmartContractController, :methods_write_proxy) post("/:address_hash/query-read-method", V2.SmartContractController, :query_read_method) get("/:address_hash/solidityscan-report", V2.SmartContractController, :solidityscan_report) + post("/:address_hash/audit-reports", V2.SmartContractController, :audit_report_submission) + get("/:address_hash/audit-reports", V2.SmartContractController, :audit_reports_list) get("/verification/config", V2.VerificationController, :config) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex index 549ca3249708..607fc67e91a7 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex @@ -42,6 +42,18 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do end) end + def render("audit_reports.json", %{reports: reports}) do + %{"items" => Enum.map(reports, &prepare_audit_report/1), "next_page_params" => nil} + end + + defp prepare_audit_report(report) do + %{ + "audit_company_name" => report.audit_company_name, + "audit_report_url" => report.audit_report_url, + "audit_publish_date" => report.audit_publish_date + } + end + def prepare_function_response(outputs, names, contract_address_hash) do case outputs do {:error, %{code: code, message: message, data: data}} -> diff --git a/apps/explorer/lib/explorer/account/public_tags_request.ex b/apps/explorer/lib/explorer/account/public_tags_request.ex index eb989e5e7611..d892d15340b4 100644 --- a/apps/explorer/lib/explorer/account/public_tags_request.ex +++ b/apps/explorer/lib/explorer/account/public_tags_request.ex @@ -19,6 +19,22 @@ defmodule Explorer.Account.PublicTagsRequest do @max_tags_per_request 2 @max_tag_length 35 + @type t :: %__MODULE__{ + company: String.t(), + website: String.t(), + tags: String.t(), + addresses: [Hash.Address.t()], + description: String.t(), + additional_comment: String.t(), + request_type: String.t(), + is_owner: boolean(), + remove_reason: String.t(), + request_id: String.t(), + full_name: String.t(), + email: String.t(), + identity_id: integer() + } + schema("account_public_tags_requests") do field(:company, :string) field(:website, :string) diff --git a/apps/explorer/lib/explorer/chain/smart_contract/audit_report.ex b/apps/explorer/lib/explorer/chain/smart_contract/audit_report.ex new file mode 100644 index 000000000000..fafb74f25bbc --- /dev/null +++ b/apps/explorer/lib/explorer/chain/smart_contract/audit_report.ex @@ -0,0 +1,149 @@ +defmodule Explorer.Chain.SmartContract.AuditReport do + @moduledoc """ + The representation of an audit report for a smart contract. + """ + + use Explorer.Schema + + alias Explorer.{Chain, Helper, Repo} + alias Explorer.Chain.Hash + alias Explorer.ThirdPartyIntegrations.AirTable + + @max_reports_per_day_for_contract 5 + + @type t :: %__MODULE__{ + address_hash: Hash.Address.t(), + is_approved: boolean(), + submitter_name: String.t(), + submitter_email: String.t(), + is_project_owner: boolean(), + project_name: String.t(), + project_url: String.t(), + audit_company_name: String.t(), + audit_report_url: String.t(), + audit_publish_date: Date.t(), + request_id: String.t(), + comment: String.t() + } + + schema "smart_contract_audit_reports" do + field(:address_hash, Hash.Address) + field(:is_approved, :boolean) + field(:submitter_name, :string) + field(:submitter_email, :string) + field(:is_project_owner, :boolean) + field(:project_name, :string) + field(:project_url, :string) + field(:audit_company_name, :string) + field(:audit_report_url, :string) + field(:audit_publish_date, :date) + field(:request_id, :string) + field(:comment, :string) + + timestamps() + end + + @local_fields [:__meta__, :inserted_at, :updated_at, :id, :request_id] + + @doc """ + Returns a map representation of the request. Appends :chain to a resulting map + """ + @spec to_map(__MODULE__.t()) :: map() + def to_map(%__MODULE__{} = request) do + association_fields = request.__struct__.__schema__(:associations) + waste_fields = association_fields ++ @local_fields + + chain = + Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:host] <> + Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:path] + + request |> Map.from_struct() |> Map.drop(waste_fields) |> Map.put(:chain, chain) + end + + @required_fields ~w(address_hash submitter_name submitter_email is_project_owner project_name project_url audit_company_name audit_report_url audit_publish_date)a + @optional_fields ~w(comment is_approved request_id)a + + @max_string_length 255 + @doc """ + Returns a changeset for audit_report. + """ + @spec changeset(struct(), map()) :: Ecto.Changeset.t() + def changeset(%__MODULE__{} = audit_report, attrs \\ %{}) do + audit_report + |> cast(attrs, @optional_fields ++ @required_fields) + |> validate_required(@required_fields, message: "Required") + |> validate_length(:submitter_email, max: @max_string_length) + |> validate_format(:submitter_email, ~r/^[A-Z0-9._%+-]+@[A-Z0-9-]+.+.[A-Z]{2,4}$/i, message: "invalid email address") + |> validate_format(:submitter_name, ~r/[a-zA-Z ]+/i, message: "only letters are allowed") + |> validate_length(:submitter_name, max: @max_string_length) + |> validate_length(:project_name, max: @max_string_length) + |> validate_length(:project_url, max: @max_string_length) + |> validate_length(:audit_company_name, max: @max_string_length) + |> validate_length(:audit_report_url, max: @max_string_length) + |> validate_change(:audit_publish_date, &past_date?/2) + |> validate_change(:audit_report_url, &valid_url?/2) + |> validate_change(:project_url, &valid_url?/2) + |> unique_constraint([:address_hash, :audit_report_url, :audit_publish_date, :audit_company_name], + message: "the report was submitted before", + name: :audit_report_unique_index + ) + |> validate_change(:address_hash, &limit_not_exceeded?/2) + end + + defp past_date?(field, date) do + if Date.compare(Date.utc_today(), date) == :lt do + [{field, "cannot be the future date"}] + else + [] + end + end + + defp valid_url?(field, url) do + if Helper.valid_url?(url) do + [] + else + [{field, "invalid url"}] + end + end + + defp limit_not_exceeded?(field, address_hash) do + if get_reports_count_by_day_for_address_hash_by_day(address_hash) >= @max_reports_per_day_for_contract do + [{field, "max #{@max_reports_per_day_for_contract} reports for address per day"}] + else + [] + end + end + + @doc """ + Insert a new audit report to DB. + """ + @spec create(map()) :: {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()} + def create(attrs) do + %__MODULE__{} + |> changeset(attrs) + |> AirTable.submit() + |> Repo.insert() + end + + defp get_reports_count_by_day_for_address_hash_by_day(address_hash) do + __MODULE__ + |> where( + [ar], + ar.address_hash == ^address_hash and + fragment("NOW() - ? at time zone 'UTC' <= interval '24 hours'", ar.inserted_at) + ) + |> limit(@max_reports_per_day_for_contract) + |> Repo.aggregate(:count) + end + + @doc """ + Returns a list of audit reports by smart contract address hash. + """ + @spec get_audit_reports_by_smart_contract_address_hash(Hash.Address.t(), keyword()) :: [__MODULE__.t()] + def get_audit_reports_by_smart_contract_address_hash(address_hash, options \\ []) do + __MODULE__ + |> where([ar], ar.address_hash == ^address_hash) + |> where([ar], ar.is_approved == true) + |> Chain.select_repo(options).all() + end +end diff --git a/apps/explorer/lib/explorer/chain_spec/genesis_data.ex b/apps/explorer/lib/explorer/chain_spec/genesis_data.ex index cb8a2279b1de..3367faf06cdd 100644 --- a/apps/explorer/lib/explorer/chain_spec/genesis_data.ex +++ b/apps/explorer/lib/explorer/chain_spec/genesis_data.ex @@ -9,6 +9,7 @@ defmodule Explorer.ChainSpec.GenesisData do alias Explorer.ChainSpec.Geth.Importer, as: GethImporter alias Explorer.ChainSpec.Parity.Importer + alias Explorer.Helper alias HTTPoison.Response @interval :timer.minutes(2) @@ -85,7 +86,7 @@ defmodule Explorer.ChainSpec.GenesisData do end defp fetch_spec(path) do - if valid_url?(path) do + if Helper.valid_url?(path) do fetch_from_url(path) else fetch_from_file(path) @@ -108,10 +109,4 @@ defmodule Explorer.ChainSpec.GenesisData do {:error, reason} end end - - defp valid_url?(string) do - uri = URI.parse(string) - - uri.scheme != nil && uri.host =~ "." - end end diff --git a/apps/explorer/lib/explorer/helper.ex b/apps/explorer/lib/explorer/helper.ex index 63687694ba0c..ab7214db8f6f 100644 --- a/apps/explorer/lib/explorer/helper.ex +++ b/apps/explorer/lib/explorer/helper.ex @@ -105,4 +105,14 @@ defmodule Explorer.Helper do end def validate_url(_), do: :error + + @doc """ + Validate url + """ + @spec valid_url?(String.t()) :: boolean + def valid_url?(string) do + uri = URI.parse(string) + + uri.scheme != nil && uri.host =~ "." + end end diff --git a/apps/explorer/lib/explorer/third_party_integrations/airtable.ex b/apps/explorer/lib/explorer/third_party_integrations/airtable.ex index a2aba9c6640c..f7e178002e86 100644 --- a/apps/explorer/lib/explorer/third_party_integrations/airtable.ex +++ b/apps/explorer/lib/explorer/third_party_integrations/airtable.ex @@ -1,14 +1,20 @@ defmodule Explorer.ThirdPartyIntegrations.AirTable do @moduledoc """ - Module is responsible for submitting requests for public tags to AirTable + Module is responsible for submitting requests for public tags and audit reports to AirTable """ require Logger alias Ecto.Changeset alias Explorer.Account.PublicTagsRequest + alias Explorer.Chain.SmartContract.AuditReport alias Explorer.Repo alias HTTPoison.Response + @doc """ + Submits a public tags request or audit report to AirTable + """ + @spec submit({:ok, PublicTagsRequest.t()} | {:error, Changeset.t()} | Changeset.t()) :: + {:ok, PublicTagsRequest.t()} | {:error, Changeset.t()} | Changeset.t() def submit({:ok, %PublicTagsRequest{} = new_request} = input) do if Mix.env() == :test do new_request @@ -17,30 +23,17 @@ defmodule Explorer.ThirdPartyIntegrations.AirTable do input else - api_key = Application.get_env(:explorer, __MODULE__)[:api_key] - headers = [{"Authorization", "Bearer #{api_key}"}, {"Content-Type", "application/json"}] - url = Application.get_env(:explorer, __MODULE__)[:table_url] - - body = %{ - "typecast" => true, - "records" => [%{"fields" => PublicTagsRequest.to_map(new_request)}] - } - - request = HTTPoison.post(url, Jason.encode!(body), headers, []) - - case request do - {:ok, %Response{body: body, status_code: 200}} -> - request_id = Enum.at(Jason.decode!(body)["records"], 0)["fields"]["request_id"] - + submit_entry( + PublicTagsRequest.to_map(new_request), + :air_table_public_tags, + fn request_id -> new_request |> PublicTagsRequest.changeset(%{request_id: request_id}) |> Repo.account_repo().update() input - - error -> - Logger.error(fn -> ["Error while submitting AirTable entry", inspect(error)] end) - + end, + fn -> {:error, %{ (%PublicTagsRequest{} @@ -48,9 +41,53 @@ defmodule Explorer.ThirdPartyIntegrations.AirTable do |> Changeset.add_error(:full_name, "AirTable error. Please try again later")) | action: :insert }} - end + end + ) end end - def submit(error), do: error + def submit(%Changeset{} = changeset), do: submit(Changeset.apply_action(changeset, :insert), changeset) + + def submit({:ok, %AuditReport{} = audit_report}, changeset) do + submit_entry( + AuditReport.to_map(audit_report), + :air_table_audit_reports, + fn request_id -> + changeset + |> Changeset.put_change(:request_id, request_id) + end, + fn -> + changeset + |> Changeset.add_error(:smart_contract_address_hash, "AirTable error. Please try again later") + end + ) + end + + def submit(_error, changeset), do: changeset + + defp submit_entry(map, envs_key, success_callback, failure_callback) do + envs = Application.get_env(:explorer, envs_key) + api_key = envs[:api_key] + headers = [{"Authorization", "Bearer #{api_key}"}, {"Content-Type", "application/json"}] + url = envs[:table_url] + + body = %{ + "typecast" => true, + "records" => [%{"fields" => map}] + } + + request = HTTPoison.post(url, Jason.encode!(body), headers, []) + + case request do + {:ok, %Response{body: body, status_code: 200}} -> + request_id = Enum.at(Jason.decode!(body)["records"], 0)["fields"]["request_id"] + + success_callback.(request_id) + + error -> + Logger.error(fn -> ["Error while submitting AirTable entry", inspect(error)] end) + + failure_callback.() + end + end end diff --git a/apps/explorer/priv/repo/migrations/20231229120232_add_smart_contract_audit_reports_table.exs b/apps/explorer/priv/repo/migrations/20231229120232_add_smart_contract_audit_reports_table.exs new file mode 100644 index 000000000000..3b5880acfb8a --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231229120232_add_smart_contract_audit_reports_table.exs @@ -0,0 +1,39 @@ +defmodule Explorer.Repo.Migrations.AddSmartContractAuditReportsTable do + use Ecto.Migration + + def change do + create table(:smart_contract_audit_reports) do + add(:address_hash, references(:smart_contracts, column: :address_hash, on_delete: :delete_all, type: :bytea), + null: false + ) + + add(:is_approved, :boolean, default: false) + add(:submitter_name, :string, null: false) + add(:submitter_email, :string, null: false) + add(:is_project_owner, :boolean, default: false) + + add(:project_name, :string, null: false) + add(:project_url, :string, null: false) + + add(:audit_company_name, :string, null: false) + add(:audit_report_url, :string, null: false) + add(:audit_publish_date, :date, null: false) + + add(:request_id, :string, null: true) + + add(:comment, :text, null: true) + + timestamps() + end + + create(index(:smart_contract_audit_reports, [:address_hash])) + + create( + unique_index( + :smart_contract_audit_reports, + [:address_hash, :audit_report_url, :audit_publish_date, :audit_company_name], + name: :audit_report_unique_index + ) + ) + end +end diff --git a/config/runtime.exs b/config/runtime.exs index 960c6bf4dc7e..3d3d6f103d68 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -415,10 +415,20 @@ config :explorer, Explorer.MicroserviceInterfaces.AccountAbstraction, service_url: System.get_env("MICROSERVICE_ACCOUNT_ABSTRACTION_URL"), enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_ACCOUNT_ABSTRACTION_ENABLED") -config :explorer, Explorer.ThirdPartyIntegrations.AirTable, +config :explorer, :air_table_public_tags, table_url: System.get_env("ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL"), api_key: System.get_env("ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY") +audit_reports_table_url = System.get_env("CONTRACT_AUDIT_REPORTS_AIRTABLE_URL") + +audit_reports_api_key = + System.get_env("CONTRACT_AUDIT_REPORTS_AIRTABLE_API_KEY") || System.get_env("ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY") + +config :explorer, :air_table_audit_reports, + table_url: audit_reports_table_url, + api_key: audit_reports_api_key, + enabled: (audit_reports_table_url && audit_reports_api_key && true) || false + config :explorer, Explorer.Mailer, adapter: Bamboo.SendGridAdapter, api_key: System.get_env("ACCOUNT_SENDGRID_API_KEY") diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 18ae2f7a5faf..69816e29db00 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -96,6 +96,8 @@ CONTRACT_VERIFICATION_ALLOWED_VYPER_EVM_VERSIONS=byzantium,constantinople,peters # CONTRACT_VERIFICATION_MAX_LIBRARIES=10 CONTRACT_MAX_STRING_LENGTH_WITHOUT_TRIMMING=2040 # CONTRACT_DISABLE_INTERACTION= +# CONTRACT_AUDIT_REPORTS_AIRTABLE_URL= +# CONTRACT_AUDIT_REPORTS_AIRTABLE_API_KEY= UNCLES_IN_AVERAGE_BLOCK_TIME=false DISABLE_WEBAPP=false API_V2_ENABLED=true From bef38a0aec65d90af351b9128465b16a04790458 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Thu, 25 Jan 2024 13:28:31 +0400 Subject: [PATCH 346/607] Don't fetch first trace for pending transactions --- CHANGELOG.md | 1 + apps/indexer/lib/indexer/fetcher/first_trace_on_demand.ex | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6060acecc726..ff9d3ba98edd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ ### Fixes - [#9261](https://github.com/blockscout/blockscout/pull/9261) - Fix pending transactions sanitizer +- [#9253](https://github.com/blockscout/blockscout/pull/9253) - Don't fetch first trace for pending transactions - [#9241](https://github.com/blockscout/blockscout/pull/9241) - Fix log decoding bug - [#9229](https://github.com/blockscout/blockscout/pull/9229) - Add missing filter to txlist query - [#9187](https://github.com/blockscout/blockscout/pull/9187) - Fix Internal Server Error on request for nonexistent token instance diff --git a/apps/indexer/lib/indexer/fetcher/first_trace_on_demand.ex b/apps/indexer/lib/indexer/fetcher/first_trace_on_demand.ex index 10bbc181ddc8..ed8f87a2881a 100644 --- a/apps/indexer/lib/indexer/fetcher/first_trace_on_demand.ex +++ b/apps/indexer/lib/indexer/fetcher/first_trace_on_demand.ex @@ -60,6 +60,9 @@ defmodule Indexer.Fetcher.FirstTraceOnDemand do end @impl true + # Don't fetch first trace for pending transactions + def handle_cast({:fetch, %{block_hash: nil}}, state), do: {:noreply, state} + def handle_cast({:fetch, transaction}, state) do fetch_first_trace(transaction, state) From 7f5903b3cbcb8729a0dfaee9a4f8829b7bf098e6 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Tue, 23 Jan 2024 15:12:44 +0400 Subject: [PATCH 347/607] Add missing filters by non-pending transactions --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/etherscan.ex | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff9d3ba98edd..36f8be20118d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - [#9261](https://github.com/blockscout/blockscout/pull/9261) - Fix pending transactions sanitizer - [#9253](https://github.com/blockscout/blockscout/pull/9253) - Don't fetch first trace for pending transactions - [#9241](https://github.com/blockscout/blockscout/pull/9241) - Fix log decoding bug +- [#9234](https://github.com/blockscout/blockscout/pull/9234) - Add missing filters by non-pending transactions - [#9229](https://github.com/blockscout/blockscout/pull/9229) - Add missing filter to txlist query - [#9187](https://github.com/blockscout/blockscout/pull/9187) - Fix Internal Server Error on request for nonexistent token instance - [#9178](https://github.com/blockscout/blockscout/pull/9178) - Change internal txs tracer type to opcode for Hardhat node diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index 0228ad7eb556..da00577498b8 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -107,6 +107,7 @@ defmodule Explorer.Etherscan do from( it in InternalTransaction, inner_join: transaction in assoc(it, :transaction), + where: not is_nil(transaction.block_hash), where: it.transaction_hash == ^transaction_hash, limit: 10_000, select: @@ -232,6 +233,7 @@ defmodule Explorer.Etherscan do from( it in InternalTransaction, inner_join: transaction in assoc(it, :transaction), + where: not is_nil(transaction.block_hash), order_by: [{^options.order_by_direction, transaction.block_number}], limit: ^options.page_size, offset: ^offset(options), From 3eaddd16ab14739a5adc253f2d0c60cec6bbf13f Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Fri, 26 Jan 2024 14:00:09 +0300 Subject: [PATCH 348/607] Process reviewers comments --- .../controllers/api/rpc/block_controller.ex | 85 +++++++++++++++++-- .../controllers/api/rpc/stats_controller.ex | 6 +- .../views/api/rpc/stats_view.ex | 24 +++--- 3 files changed, 97 insertions(+), 18 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex index 5c505e4f2239..f5b87aea0b68 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex @@ -8,14 +8,27 @@ defmodule BlockScoutWeb.API.RPC.BlockController do alias Timex.Duration @doc """ - Reward for mining a block. - - The block reward is the sum of the following: - - * Sum of the transaction fees (gas_used * gas_price) for the block - * A static reward for miner (this value may change during the life of the chain) - * The reward for uncle blocks (1/32 * static_reward * number_of_uncles) + Calculates the total reward for mining a specific block. + + ## Parameters + - conn: Plug.Conn struct. + - params: A map containing the query parameters which should include: + - `blockno`: The number of the block for which to calculate the reward. + + ## Description + This function computes the block reward, which consists of: + - The sum of the transaction fees (gas_used * gas_price) for the block. + - A static reward for the miner, which may vary over the blockchain's lifespan. + - The reward for uncle blocks calculated as (1/32 * static_reward * number_of_uncles). + + ## Responses + - On success: Renders a JSON response with the reward details for the block. + - On failure: Renders an error response with an appropriate message due to: + - Absence of the `blockno` parameter. + - Invalid `blockno` parameter. + - Non-existence of the specified block. """ + @spec getblockreward(Plug.Conn.t(), map()) :: Plug.Conn.t() def getblockreward(conn, params) do with {:block_param, {:ok, unsafe_block_number}} <- {:block_param, Map.fetch(params, "blockno")}, {:ok, block_number} <- ChainWeb.param_to_block_number(unsafe_block_number), @@ -37,6 +50,27 @@ defmodule BlockScoutWeb.API.RPC.BlockController do end end + @doc """ + Calculates and renders the estimated time until a target block number is reached. + + ## Parameters + - conn: Plug.Conn struct. + - params: A map containing the query parameters which should include: + - `blockno`: The target block number to countdown to. + + ## Description + This function takes a target block number from the `params` map and calculates the remaining time in seconds until that block is reached, considering the current maximum block number and the average block time. + + ## Responses + - On success: Renders a view with the countdown information including the current block number, target block number, the number of remaining blocks, and the estimated time in seconds until the target block number is reached. + - On failure: Renders an error view with an appropriate message, which could be due to: + - Missing `blockno` parameter. + - Invalid block number provided. + - Average block time calculation being disabled. + - Chain is currently indexing and cannot provide the information. + - The target block number has already been passed. + """ + @spec getblockcountdown(Plug.Conn.t(), map()) :: Plug.Conn.t() def getblockcountdown(conn, params) do with {:block_param, {:ok, unsafe_target_block_number}} <- {:block_param, Map.fetch(params, "blockno")}, {:ok, target_block_number} <- ChainWeb.param_to_block_number(unsafe_target_block_number), @@ -72,6 +106,28 @@ defmodule BlockScoutWeb.API.RPC.BlockController do end end + @doc """ + Retrieves the block number associated with a given timestamp and closest policy. + + ## Parameters + - conn: Plug.Conn struct. + - params: A map containing the query parameters which should include: + - `timestamp`: The timestamp to query the block number for. + - `closest`: The policy to determine which block number to return. It could be a value like 'before' or 'after' to indicate whether the closest block before or after the given timestamp should be returned. + + ## Description + This function finds the block number that is closest to a specific timestamp according to the provided 'closest' policy. + + ## Responses + - On success: Renders a JSON response with the found block number. + - On failure: Renders an error response with an appropriate message, which could be due to: + - Missing `timestamp` parameter. + - Missing `closest` parameter. + - Invalid `timestamp` parameter. + - Invalid `closest` parameter. + - No block corresponding to the given timestamp and closest policy. + """ + @spec getblocknobytime(Plug.Conn.t(), map()) :: Plug.Conn.t() def getblocknobytime(conn, params) do from_api = true @@ -99,6 +155,21 @@ defmodule BlockScoutWeb.API.RPC.BlockController do end end + @doc """ + Fetches the highest block number from the chain. + + ## Parameters + - conn: Plug.Conn struct. + - params: A map containing the query parameters which may include: + - `id`: An optional parameter that defaults to 1 if not provided. + + ## Description + This function retrieves the maximum block number that has been recorded in the blockchain. + + ## Responses + - Renders a JSON response including the maximum block number and the provided or default `id`. + """ + @spec eth_block_number(Plug.Conn.t(), map()) :: Plug.Conn.t() def eth_block_number(conn, params) do id = Map.get(params, "id", 1) max_block_number = BlockNumber.get_max() diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex index fec113b4ee55..4ed52cbebe62 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex @@ -59,7 +59,11 @@ defmodule BlockScoutWeb.API.RPC.StatsController do render(conn, "coinsupply.json", total_supply: cached_coin_total_supply) end - def ethprice(conn, params), do: coinprice(conn, params) + def ethprice(conn, _params) do + rates = Market.get_coin_exchange_rate() + + render(conn, "ethprice.json", rates: rates) + end def coinprice(conn, _params) do rates = Market.get_coin_exchange_rate() diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/stats_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/stats_view.ex index a239c2eb9aac..16635461e057 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/stats_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/stats_view.ex @@ -19,8 +19,12 @@ defmodule BlockScoutWeb.API.RPC.StatsView do RPCView.render("show_value.json", data: total_supply) end + def render("ethprice.json", %{rates: rates}) do + RPCView.render("show.json", data: prepare_rates(rates, "eth")) + end + def render("coinprice.json", %{rates: rates}) do - RPCView.render("show.json", data: prepare_rates(rates)) + RPCView.render("show.json", data: prepare_rates(rates, "coin_")) end def render("totalfees.json", %{total_fees: total_fees}) do @@ -31,22 +35,22 @@ defmodule BlockScoutWeb.API.RPC.StatsView do RPCView.render("error.json", assigns) end - defp prepare_rates(rates) do + defp prepare_rates(rates, prefix) do if rates do timestamp = rates.last_updated && rates.last_updated |> DateTime.to_unix() |> to_string() %{ - "coin_btc" => rates.btc_value && to_string(rates.btc_value), - "coin_btc_timestamp" => timestamp, - "coin_usd" => rates.usd_value && to_string(rates.usd_value), - "coin_usd_timestamp" => timestamp + (prefix <> "btc") => rates.btc_value && to_string(rates.btc_value), + (prefix <> "btc_timestamp") => timestamp, + (prefix <> "usd") => rates.usd_value && to_string(rates.usd_value), + (prefix <> "usd_timestamp") => timestamp } else %{ - "coin_btc" => nil, - "coin_btc_timestamp" => nil, - "coin_usd" => nil, - "coin_usd_timestamp" => nil + (prefix <> "btc") => nil, + (prefix <> "btc_timestamp") => nil, + (prefix <> "usd") => nil, + (prefix <> "usd_timestamp") => nil } end end From 795590a3d8bec1b8c5a93806f08adc34caedeaa0 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 19 Jan 2024 11:40:08 +0300 Subject: [PATCH 349/607] api v1 allow multiple slashes in the path before "api" --- CHANGELOG.md | 1 + .../controllers/api/rpc/rpc_translator.ex | 11 ++++++----- .../controllers/api/rpc/rpc_translator_test.exs | 7 +++++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 500518ccd2f3..fa79777edcec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - [#9241](https://github.com/blockscout/blockscout/pull/9241) - Fix log decoding bug - [#9234](https://github.com/blockscout/blockscout/pull/9234) - Add missing filters by non-pending transactions - [#9229](https://github.com/blockscout/blockscout/pull/9229) - Add missing filter to txlist query +- [#9195](https://github.com/blockscout/blockscout/pull/9195) - API v1 allow multiple slashes in the path before "api" - [#9187](https://github.com/blockscout/blockscout/pull/9187) - Fix Internal Server Error on request for nonexistent token instance - [#9178](https://github.com/blockscout/blockscout/pull/9178) - Change internal txs tracer type to opcode for Hardhat node - [#9173](https://github.com/blockscout/blockscout/pull/9173) - Exclude genesis block from average block time calculation diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex index 7abf738f8915..0263abd96da9 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex @@ -29,7 +29,7 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do end def call(%Conn{params: %{"module" => module, "action" => action}} = conn, translations) do - with {:valid_api_request, true} <- {:valid_api_request, valid_api_request_path(conn)}, + with {:valid_api_v1_request, true} <- {:valid_api_v1_request, valid_api_v1_request_path(conn)}, {:ok, {controller, write_actions}} <- translate_module(translations, module), {:ok, action} <- translate_action(action), true <- action_accessed?(action, write_actions), @@ -65,7 +65,7 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do :rate_limit_reached -> AccessHelper.handle_rate_limit_deny(conn) - {:valid_api_request, false} -> + {:valid_api_v1_request, false} -> conn |> put_status(404) |> put_view(RPCView) @@ -132,9 +132,10 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do {:error, Exception.format(:error, e, __STACKTRACE__)} end - defp valid_api_request_path(conn) do - if conn.request_path == "/api" || conn.request_path == "/api/" || conn.request_path == "/api/v1" || - conn.request_path == "/api/v1/" do + defp valid_api_v1_request_path(conn) do + if String.ends_with?(conn.request_path, "/api") || String.ends_with?(conn.request_path, "/api/") || + String.ends_with?(conn.request_path, "/api/v1") || + String.ends_with?(conn.request_path, "/api/v1/") do true else false diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/rpc_translator_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/rpc_translator_test.exs index fa7222926ed7..bf410d62a14a 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/rpc_translator_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/rpc_translator_test.exs @@ -78,5 +78,12 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslatorTest do result = RPCTranslator.call(conn, %{"test" => {TestController, []}}) assert json_response(result, 200) == %{} end + + test "allow multiple '/' before api", %{conn: conn} do + conn = %Conn{conn | params: %{"module" => "test", "action" => "test_action"}, request_path: "//api"} + + result = RPCTranslator.call(conn, %{"test" => {TestController, []}}) + assert json_response(result, 200) == %{} + end end end From a47d83f0beceb7b4a0934f690306749b2d78e2cc Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Fri, 26 Jan 2024 14:54:39 +0300 Subject: [PATCH 350/607] =?UTF-8?q?Add=20bridged=20tokens=20functionality,?= =?UTF-8?q?=20could=20be=20enabled=20by=20compile=20time=20en=E2=80=A6=20(?= =?UTF-8?q?#9169)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add bridged tokens functionality, could be enabled by compile time env var * Process reviewer's comment * Fix credo * Process review comments * Reset GA cache * Fix warning --- .github/workflows/config.yml | 26 +- CHANGELOG.md | 1 + .../lib/block_scout_web/api_router.ex | 4 + .../lib/block_scout_web/chain.ex | 4 + .../controllers/api/v2/token_controller.ex | 49 +- .../lib/block_scout_web/paging_helper.ex | 16 +- .../views/api/v2/token_view.ex | 28 +- apps/block_scout_web/test/test_helper.exs | 1 + apps/explorer/config/config.exs | 2 + apps/explorer/config/dev.exs | 2 + apps/explorer/config/prod.exs | 4 + apps/explorer/config/test.exs | 3 +- apps/explorer/lib/explorer/application.ex | 4 +- .../lib/explorer/chain/bridged_token.ex | 1049 +++++++++++++++++ .../explorer/chain/import/runner/tokens.ex | 99 +- apps/explorer/lib/explorer/chain/token.ex | 64 +- apps/explorer/lib/explorer/repo.ex | 10 + .../explorer/tags/address_tag_cataloger.ex | 6 +- .../20230919080116_add_bridged_tokens.exs | 29 + apps/explorer/test/support/data_case.ex | 2 + apps/explorer/test/test_helper.exs | 1 + .../calc_lp_tokens_total_liquidity.ex | 52 + .../set_amb_bridged_metadata_for_tokens.ex | 44 + .../set_omni_bridged_metadata_for_tokens.ex | 54 + apps/indexer/lib/indexer/supervisor.ex | 26 +- config/config_helper.exs | 21 +- config/runtime.exs | 7 + config/runtime/dev.exs | 9 + config/runtime/prod.exs | 8 + cspell.json | 3 +- 30 files changed, 1547 insertions(+), 81 deletions(-) create mode 100644 apps/explorer/lib/explorer/chain/bridged_token.ex create mode 100644 apps/explorer/priv/bridged_tokens/migrations/20230919080116_add_bridged_tokens.exs create mode 100644 apps/indexer/lib/indexer/bridged_tokens/calc_lp_tokens_total_liquidity.ex create mode 100644 apps/indexer/lib/indexer/bridged_tokens/set_amb_bridged_metadata_for_tokens.ex create mode 100644 apps/indexer/lib/indexer/bridged_tokens/set_omni_bridged_metadata_for_tokens.ex diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index e9566d63ac52..71d16ae8ce70 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -75,7 +75,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_37-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_38-${{ 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_37-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_38-${{ 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_37-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_38-${{ 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_37-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_38-${{ 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_37-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_38-${{ 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_37-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_38-${{ 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_37-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_38-${{ 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_37-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_38-${{ 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_37-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_38-${{ 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_37-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_38-${{ 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_37-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_38-${{ 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_37-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_38-${{ 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_37-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_38-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" diff --git a/CHANGELOG.md b/CHANGELOG.md index 500518ccd2f3..7fea8f3a5030 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#9169](https://github.com/blockscout/blockscout/pull/9169) - Add bridged tokens functionality to master branch - [#9158](https://github.com/blockscout/blockscout/pull/9158) - Increase shared memory for PostgreSQL containers - [#9155](https://github.com/blockscout/blockscout/pull/9155) - Allow bypassing avg block time in proxy implementation re-fetch ttl calculation - [#9148](https://github.com/blockscout/blockscout/pull/9148) - Add `/api/v2/utils/decode-calldata` diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index f9da5834f4ab..4b32910bc81a 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -247,6 +247,10 @@ defmodule BlockScoutWeb.ApiRouter do end scope "/tokens" do + if Application.compile_env(:explorer, Explorer.Chain.BridgedToken)[:enabled] do + get("/bridged", V2.TokenController, :bridged_tokens_list) + end + get("/", V2.TokenController, :tokens_list) get("/:address_hash_param", V2.TokenController, :token) get("/:address_hash_param/counters", V2.TokenController, :counters) diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index e988dec2bf54..a0798ff247a6 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -532,6 +532,10 @@ defmodule BlockScoutWeb.Chain do } end + defp paging_params({%Token{} = token, _}) do + paging_params(token) + end + defp paging_params(%TagAddress{id: id}) do %{"id" => id} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex index bc4034ad0f1d..e29a2127d511 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.API.V2.{AddressView, TransactionView} alias Explorer.{Chain, Repo} - alias Explorer.Chain.{Address, Token, Token.Instance} + alias Explorer.Chain.{Address, BridgedToken, Token, Token.Instance} alias Indexer.Fetcher.TokenTotalSupplyOnDemand import BlockScoutWeb.Chain, @@ -18,7 +18,12 @@ defmodule BlockScoutWeb.API.V2.TokenController do ] import BlockScoutWeb.PagingHelper, - only: [delete_parameters_from_next_page_params: 1, token_transfers_types_options: 1, tokens_sorting: 1] + only: [ + chain_ids_filter_options: 1, + delete_parameters_from_next_page_params: 1, + token_transfers_types_options: 1, + tokens_sorting: 1 + ] import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] @@ -32,6 +37,27 @@ defmodule BlockScoutWeb.API.V2.TokenController do {:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)} do TokenTotalSupplyOnDemand.trigger_fetch(address_hash) + conn + |> token_response(token, address_hash) + end + end + + if Application.compile_env(:explorer, Explorer.Chain.BridgedToken)[:enabled] do + defp token_response(conn, token, address_hash) do + if token.bridged do + bridged_token = Repo.get_by(BridgedToken, home_token_contract_address_hash: address_hash) + + conn + |> put_status(200) + |> render(:bridged_token, %{token: {token, bridged_token}}) + else + conn + |> put_status(200) + |> render(:token, %{token: token}) + end + end + else + defp token_response(conn, token, _address_hash) do conn |> put_status(200) |> render(:token, %{token: token}) @@ -281,6 +307,25 @@ defmodule BlockScoutWeb.API.V2.TokenController do |> render(:tokens, %{tokens: tokens, next_page_params: next_page_params}) end + def bridged_tokens_list(conn, params) do + filter = params["q"] + + options = + params + |> paging_options() + |> Keyword.merge(chain_ids_filter_options(params)) + |> Keyword.merge(tokens_sorting(params)) + |> Keyword.merge(@api_true) + + {tokens, next_page} = filter |> BridgedToken.list_top_bridged_tokens(options) |> split_list_by_page() + + next_page_params = next_page |> next_page_params(tokens, delete_parameters_from_next_page_params(params)) + + conn + |> put_status(200) + |> render(:bridged_tokens, %{tokens: tokens, next_page_params: next_page_params}) + end + defp put_owner(token_instances, holder_address), do: Enum.map(token_instances, fn token_instance -> %Instance{token_instance | owner: holder_address} end) end diff --git a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex index 406f47b6c761..bb9ecef5ec5b 100644 --- a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.PagingHelper do """ import Explorer.Chain, only: [string_to_transaction_hash: 1] alias Explorer.Chain.Transaction - alias Explorer.{PagingOptions, SortingHelper} + alias Explorer.{Helper, PagingOptions, SortingHelper} @page_size 50 @default_paging_options %PagingOptions{page_size: @page_size + 1} @@ -12,6 +12,7 @@ defmodule BlockScoutWeb.PagingHelper do @allowed_type_labels ["coin_transfer", "contract_call", "contract_creation", "token_transfer", "token_creation"] @allowed_token_transfer_type_labels ["ERC-20", "ERC-721", "ERC-1155"] @allowed_nft_token_type_labels ["ERC-721", "ERC-1155"] + @allowed_chain_id [1, 56, 99] def paging_options(%{"block_number" => block_number_string, "index" => index_string}, [:validated | _]) do with {block_number, ""} <- Integer.parse(block_number_string), @@ -66,6 +67,19 @@ defmodule BlockScoutWeb.PagingHelper do def filter_options(_params, fallback), do: [fallback] + def chain_ids_filter_options(%{"chain_ids" => chain_id}) do + [ + chain_ids: + chain_id + |> String.split(",") + |> Enum.uniq() + |> Enum.map(&Helper.parse_integer/1) + |> Enum.filter(&Enum.member?(@allowed_chain_id, &1)) + ] + end + + def chain_ids_filter_options(_), do: [chain_id: []] + # sobelow_skip ["DOS.StringToAtom"] def type_filter_options(%{"type" => type}) do [type: type |> parse_filter(@allowed_type_labels) |> Enum.map(&String.to_atom/1)] diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex index 616299fde941..a6b5fc99f72b 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex @@ -4,10 +4,10 @@ defmodule BlockScoutWeb.API.V2.TokenView do alias BlockScoutWeb.API.V2.Helper alias BlockScoutWeb.NFTHelper alias Ecto.Association.NotLoaded - alias Explorer.Chain.Address + alias Explorer.Chain.{Address, BridgedToken} alias Explorer.Chain.Token.Instance - def render("token.json", %{token: nil, contract_address_hash: contract_address_hash}) do + def render("token.json", %{token: nil = token, contract_address_hash: contract_address_hash}) do %{ "address" => Address.checksum(contract_address_hash), "symbol" => nil, @@ -20,6 +20,7 @@ defmodule BlockScoutWeb.API.V2.TokenView do "icon_url" => nil, "circulating_market_cap" => nil } + |> maybe_append_bridged_info(token) end def render("token.json", %{token: nil}) do @@ -39,6 +40,7 @@ defmodule BlockScoutWeb.API.V2.TokenView do "icon_url" => token.icon_url, "circulating_market_cap" => token.circulating_market_cap } + |> maybe_append_bridged_info(token) end def render("token_balances.json", %{ @@ -71,6 +73,20 @@ defmodule BlockScoutWeb.API.V2.TokenView do } end + def render("bridged_tokens.json", %{tokens: tokens, next_page_params: next_page_params}) do + %{"items" => Enum.map(tokens, &render("bridged_token.json", %{token: &1})), "next_page_params" => next_page_params} + end + + def render("bridged_token.json", %{token: {token, bridged_token}}) do + "token.json" + |> render(%{token: token}) + |> Map.merge(%{ + foreign_address: Address.checksum(bridged_token.foreign_token_contract_address_hash), + bridge_type: bridged_token.type, + origin_chain_id: bridged_token.foreign_chain_id + }) + end + def exchange_rate(%{fiat_value: fiat_value}) when not is_nil(fiat_value), do: to_string(fiat_value) def exchange_rate(_), do: nil @@ -114,4 +130,12 @@ defmodule BlockScoutWeb.API.V2.TokenView do defp prepare_holders_count(nil), do: nil defp prepare_holders_count(count) when count < 0, do: prepare_holders_count(0) defp prepare_holders_count(count), do: to_string(count) + + defp maybe_append_bridged_info(map, token) do + if BridgedToken.enabled?() do + (token && Map.put(map, "is_bridged", token.bridged || false)) || map + else + map + end + end end diff --git a/apps/block_scout_web/test/test_helper.exs b/apps/block_scout_web/test/test_helper.exs index 8a9fe648fed4..e5c91ee2b664 100644 --- a/apps/block_scout_web/test/test_helper.exs +++ b/apps/block_scout_web/test/test_helper.exs @@ -31,6 +31,7 @@ Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonZkevm, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.RSK, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Shibarium, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, :manual) +Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.BridgedTokens, :manual) Absinthe.Test.prime(BlockScoutWeb.Schema) diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index eafec8dec772..117ca2c0ab4a 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -138,6 +138,8 @@ config :explorer, config :explorer, :http_adapter, HTTPoison +config :explorer, Explorer.Chain.BridgedToken, enabled: ConfigHelper.parse_bool_env_var("BRIDGED_TOKENS_ENABLED") + config :logger, :explorer, # keep synced with `config/config.exs` format: "$dateT$time $metadata[$level] $message\n", diff --git a/apps/explorer/config/dev.exs b/apps/explorer/config/dev.exs index 8996b7e72cbe..1a6f4b2384e5 100644 --- a/apps/explorer/config/dev.exs +++ b/apps/explorer/config/dev.exs @@ -23,6 +23,8 @@ config :explorer, Explorer.Repo.Shibarium, timeout: :timer.seconds(80) config :explorer, Explorer.Repo.Suave, timeout: :timer.seconds(80) +config :explorer, Explorer.Repo.BridgedTokens, timeout: :timer.seconds(80) + config :explorer, Explorer.Tracer, env: "dev", disabled?: true config :logger, :explorer, diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs index e8184837df98..8dbd83fd769d 100644 --- a/apps/explorer/config/prod.exs +++ b/apps/explorer/config/prod.exs @@ -36,6 +36,10 @@ config :explorer, Explorer.Repo.Suave, prepare: :unnamed, timeout: :timer.seconds(60) +config :explorer, Explorer.Repo.BridgedTokens, + prepare: :unnamed, + timeout: :timer.seconds(60) + config :explorer, Explorer.Tracer, env: "production", disabled?: true config :logger, :explorer, diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index 0da1447c6e26..0d55c5633075 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -48,7 +48,8 @@ for repo <- [ Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Shibarium, - Explorer.Repo.Suave + Explorer.Repo.Suave, + Explorer.Repo.BridgedTokens ] do config :explorer, repo, database: "explorer_test", diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 85a94c6a82b3..482abd0af757 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -118,7 +118,6 @@ defmodule Explorer.Application do configure(Explorer.Counters.BlockBurntFeeCounter), configure(Explorer.Counters.BlockPriorityFeeCounter), configure(Explorer.Counters.AverageBlockTime), - configure(Explorer.Counters.Bridge), configure(Explorer.Validator.MetadataProcessor), configure(Explorer.Tags.AddressTag.Cataloger), configure(MinMissingBlockNumber), @@ -143,7 +142,8 @@ defmodule Explorer.Application do Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Shibarium, - Explorer.Repo.Suave + Explorer.Repo.Suave, + Explorer.Repo.BridgedTokens ] else [] diff --git a/apps/explorer/lib/explorer/chain/bridged_token.ex b/apps/explorer/lib/explorer/chain/bridged_token.ex new file mode 100644 index 000000000000..ca4b0074104e --- /dev/null +++ b/apps/explorer/lib/explorer/chain/bridged_token.ex @@ -0,0 +1,1049 @@ +defmodule Explorer.Chain.BridgedToken do + @moduledoc """ + Represents a bridged token. + """ + use Explorer.Schema + + import Ecto.Changeset + import EthereumJSONRPC, only: [json_rpc: 2] + + import Ecto.Query, + only: [ + from: 2, + limit: 2, + where: 2 + ] + + alias ABI.{TypeDecoder, TypeEncoder} + alias Ecto.Changeset + alias EthereumJSONRPC.Contract + alias Explorer.{Chain, PagingOptions, Repo, SortingHelper} + + alias Explorer.Chain.{ + Address, + BridgedToken, + Hash, + InternalTransaction, + Search, + Token, + Transaction + } + + require Logger + + @default_paging_options %PagingOptions{page_size: 50} + + @typedoc """ + * `foreign_chain_id` - chain ID of a foreign token + * `foreign_token_contract_address_hash` - Foreign token's contract hash + * `home_token_contract_address` - The `t:Address.t/0` of the home token's contract + * `home_token_contract_address_hash` - Home token's contract hash foreign key + * `custom_metadata` - Arbitrary string with custom metadata. For instance, tokens/weights for Balance tokens + * `custom_cap` - Custom capitalization for this token + * `lp_token` - Boolean flag: LP token or not + * `type` - omni/amb + """ + @type t :: %BridgedToken{ + foreign_chain_id: Decimal.t(), + foreign_token_contract_address_hash: Hash.Address.t(), + home_token_contract_address: %Ecto.Association.NotLoaded{} | Address.t(), + home_token_contract_address_hash: Hash.Address.t(), + custom_metadata: String.t(), + custom_cap: Decimal.t(), + lp_token: boolean(), + type: String.t(), + exchange_rate: Decimal.t() + } + + @derive {Poison.Encoder, + except: [ + :__meta__, + :home_token_contract_address, + :inserted_at, + :updated_at + ]} + + @derive {Jason.Encoder, + except: [ + :__meta__, + :home_token_contract_address, + :inserted_at, + :updated_at + ]} + + @primary_key false + schema "bridged_tokens" do + field(:foreign_chain_id, :decimal) + field(:foreign_token_contract_address_hash, Hash.Address) + field(:custom_metadata, :string) + field(:custom_cap, :decimal) + field(:lp_token, :boolean) + field(:type, :string) + field(:exchange_rate, :decimal) + + belongs_to( + :home_token_contract_address, + Token, + foreign_key: :home_token_contract_address_hash, + primary_key: true, + references: :contract_address_hash, + type: Hash.Address + ) + + timestamps() + end + + @required_attrs ~w(home_token_contract_address_hash)a + @optional_attrs ~w(foreign_chain_id foreign_token_contract_address_hash custom_metadata custom_cap boolean type exchange_rate)a + + @doc false + def changeset(%BridgedToken{} = bridged_token, params \\ %{}) do + bridged_token + |> cast(params, @required_attrs ++ @optional_attrs) + |> validate_required(@required_attrs) + |> foreign_key_constraint(:home_token_contract_address) + |> unique_constraint(:home_token_contract_address_hash) + end + + def get_unprocessed_mainnet_lp_tokens_list do + query = + from(bt in BridgedToken, + where: bt.foreign_chain_id == ^1, + where: is_nil(bt.lp_token) or bt.lp_token == true, + select: bt + ) + + query + |> Repo.all() + end + + def necessary_envs_passed? do + config = Application.get_env(:explorer, __MODULE__) + eth_omni_bridge_mediator = config[:eth_omni_bridge_mediator] + bsc_omni_bridge_mediator = config[:bsc_omni_bridge_mediator] + poa_omni_bridge_mediator = config[:poa_omni_bridge_mediator] + + (eth_omni_bridge_mediator && eth_omni_bridge_mediator !== "") || + (bsc_omni_bridge_mediator && bsc_omni_bridge_mediator !== "") || + (poa_omni_bridge_mediator && poa_omni_bridge_mediator !== "") + end + + def enabled? do + Application.get_env(:explorer, __MODULE__)[:enabled] + end + + @doc """ + Returns a list of token addresses `t:Address.t/0`s that don't have an + bridged property revealed. + """ + def unprocessed_token_addresses_to_reveal_bridged_tokens do + query = + from(t in Token, + where: is_nil(t.bridged), + select: t.contract_address_hash + ) + + Repo.stream_reduce(query, [], &[&1 | &2]) + end + + @doc """ + Processes AMB tokens from mediators addresses provided + """ + def process_amb_tokens do + amb_bridge_mediators_var = Application.get_env(:explorer, __MODULE__)[:amb_bridge_mediators] + amb_bridge_mediators = (amb_bridge_mediators_var && String.split(amb_bridge_mediators_var, ",")) || [] + + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + + foreign_json_rpc = Application.get_env(:explorer, __MODULE__)[:foreign_json_rpc] + + eth_call_foreign_json_rpc_named_arguments = + compose_foreign_json_rpc_named_arguments(json_rpc_named_arguments, foreign_json_rpc) + + try do + amb_bridge_mediators + |> Enum.each(fn amb_bridge_mediator_hash -> + with {:ok, bridge_contract_hash_resp} <- + get_bridge_contract_hash(amb_bridge_mediator_hash, json_rpc_named_arguments), + bridge_contract_hash <- decode_contract_address_hash_response(bridge_contract_hash_resp), + {:ok, destination_chain_id_resp} <- + get_destination_chain_id(bridge_contract_hash, json_rpc_named_arguments), + foreign_chain_id <- decode_contract_integer_response(destination_chain_id_resp), + {:ok, home_token_contract_hash_resp} <- + get_erc677_token_hash(amb_bridge_mediator_hash, json_rpc_named_arguments), + home_token_contract_hash_string <- decode_contract_address_hash_response(home_token_contract_hash_resp), + {:ok, home_token_contract_hash} <- Chain.string_to_address_hash(home_token_contract_hash_string), + {:ok, foreign_mediator_contract_hash_resp} <- + get_foreign_mediator_contract_hash(amb_bridge_mediator_hash, json_rpc_named_arguments), + foreign_mediator_contract_hash <- + decode_contract_address_hash_response(foreign_mediator_contract_hash_resp), + {:ok, foreign_token_contract_hash_resp} <- + get_erc677_token_hash(foreign_mediator_contract_hash, eth_call_foreign_json_rpc_named_arguments), + foreign_token_contract_hash_string <- + decode_contract_address_hash_response(foreign_token_contract_hash_resp), + {:ok, foreign_token_contract_hash} <- Chain.string_to_address_hash(foreign_token_contract_hash_string) do + insert_bridged_token_metadata(home_token_contract_hash, %{ + foreign_chain_id: foreign_chain_id, + foreign_token_address_hash: foreign_token_contract_hash, + custom_metadata: nil, + custom_cap: nil, + lp_token: nil, + type: "amb" + }) + + set_token_bridged_status(home_token_contract_hash, true) + else + result -> + Logger.debug([ + "failed to fetch metadata for token bridged with AMB mediator #{amb_bridge_mediator_hash}", + inspect(result) + ]) + end + end) + rescue + _ -> + :ok + end + + :ok + end + + @doc """ + Fetches bridged tokens metadata from OmniBridge. + """ + def fetch_omni_bridged_tokens_metadata(token_addresses) do + Enum.each(token_addresses, fn token_address_hash -> + created_from_int_tx_success_query = + from( + it in InternalTransaction, + inner_join: t in assoc(it, :transaction), + where: it.created_contract_address_hash == ^token_address_hash, + where: t.status == ^1 + ) + + created_from_int_tx_success = + created_from_int_tx_success_query + |> limit(1) + |> Repo.one() + + created_from_tx_query = + from( + t in Transaction, + where: t.created_contract_address_hash == ^token_address_hash + ) + + created_from_tx = + created_from_tx_query + |> Repo.all() + |> Enum.count() > 0 + + created_from_int_tx_query = + from( + it in InternalTransaction, + where: it.created_contract_address_hash == ^token_address_hash + ) + + created_from_int_tx = + created_from_int_tx_query + |> Repo.all() + |> Enum.count() > 0 + + cond do + created_from_tx -> + set_token_bridged_status(token_address_hash, false) + + created_from_int_tx && !created_from_int_tx_success -> + set_token_bridged_status(token_address_hash, false) + + created_from_int_tx && created_from_int_tx_success -> + proceed_with_set_omni_status(token_address_hash, created_from_int_tx_success) + + true -> + :ok + end + end) + + :ok + end + + defp proceed_with_set_omni_status(token_address_hash, created_from_int_tx_success) do + {:ok, eth_omni_status} = + extract_omni_bridged_token_metadata_wrapper( + token_address_hash, + created_from_int_tx_success, + :eth_omni_bridge_mediator + ) + + {:ok, bsc_omni_status} = + if eth_omni_status do + {:ok, false} + else + extract_omni_bridged_token_metadata_wrapper( + token_address_hash, + created_from_int_tx_success, + :bsc_omni_bridge_mediator + ) + end + + {:ok, poa_omni_status} = + if eth_omni_status || bsc_omni_status do + {:ok, false} + else + extract_omni_bridged_token_metadata_wrapper( + token_address_hash, + created_from_int_tx_success, + :poa_omni_bridge_mediator + ) + end + + if !eth_omni_status && !bsc_omni_status && !poa_omni_status do + set_token_bridged_status(token_address_hash, false) + end + end + + defp extract_omni_bridged_token_metadata_wrapper(token_address_hash, created_from_int_tx_success, mediator) do + omni_bridge_mediator = Application.get_env(:explorer, __MODULE__)[mediator] + %{transaction_hash: transaction_hash} = created_from_int_tx_success + + if omni_bridge_mediator && omni_bridge_mediator !== "" do + {:ok, omni_bridge_mediator_hash} = Chain.string_to_address_hash(omni_bridge_mediator) + + created_by_amb_mediator_query = + from( + it in InternalTransaction, + where: it.transaction_hash == ^transaction_hash, + where: it.to_address_hash == ^omni_bridge_mediator_hash + ) + + created_by_amb_mediator = + created_by_amb_mediator_query + |> Repo.all() + + if Enum.count(created_by_amb_mediator) > 0 do + extract_omni_bridged_token_metadata( + token_address_hash, + omni_bridge_mediator, + omni_bridge_mediator_hash + ) + + {:ok, true} + else + {:ok, false} + end + else + {:ok, false} + end + end + + defp extract_omni_bridged_token_metadata(token_address_hash, omni_bridge_mediator, omni_bridge_mediator_hash) do + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + + with {:ok, _} <- + get_token_interfaces_version_signature(token_address_hash, json_rpc_named_arguments), + {:ok, foreign_token_address_abi_encoded} <- + get_foreign_token_address(omni_bridge_mediator, token_address_hash, json_rpc_named_arguments), + {:ok, bridge_contract_hash_resp} <- + get_bridge_contract_hash(omni_bridge_mediator_hash, json_rpc_named_arguments) do + foreign_token_address_hash_string = decode_contract_address_hash_response(foreign_token_address_abi_encoded) + {:ok, foreign_token_address_hash} = Chain.string_to_address_hash(foreign_token_address_hash_string) + + multi_token_bridge_hash_string = decode_contract_address_hash_response(bridge_contract_hash_resp) + + {:ok, foreign_chain_id_abi_encoded} = + get_destination_chain_id(multi_token_bridge_hash_string, json_rpc_named_arguments) + + foreign_chain_id = decode_contract_integer_response(foreign_chain_id_abi_encoded) + + foreign_json_rpc = Application.get_env(:explorer, __MODULE__)[:foreign_json_rpc] + + custom_metadata = + if foreign_chain_id == 1 do + get_bridged_token_custom_metadata(foreign_token_address_hash, json_rpc_named_arguments, foreign_json_rpc) + else + nil + end + + bridged_token_metadata = %{ + foreign_chain_id: foreign_chain_id, + foreign_token_address_hash: foreign_token_address_hash, + custom_metadata: custom_metadata, + custom_cap: nil, + lp_token: nil, + type: "omni" + } + + insert_bridged_token_metadata(token_address_hash, bridged_token_metadata) + + set_token_bridged_status(token_address_hash, true) + end + end + + defp get_bridge_contract_hash(mediator_hash, json_rpc_named_arguments) do + # keccak 256 from bridgeContract() + bridge_contract_signature = "0xcd596583" + + perform_eth_call_request(bridge_contract_signature, mediator_hash, json_rpc_named_arguments) + end + + defp get_erc677_token_hash(mediator_hash, json_rpc_named_arguments) do + # keccak 256 from erc677token() + erc677_token_signature = "0x18d8f9c9" + + perform_eth_call_request(erc677_token_signature, mediator_hash, json_rpc_named_arguments) + end + + defp get_foreign_mediator_contract_hash(mediator_hash, json_rpc_named_arguments) do + # keccak 256 from mediatorContractOnOtherSide() + mediator_contract_on_other_side_signature = "0x871c0760" + + perform_eth_call_request(mediator_contract_on_other_side_signature, mediator_hash, json_rpc_named_arguments) + end + + defp get_destination_chain_id(bridge_contract_hash, json_rpc_named_arguments) do + # keccak 256 from destinationChainId() + destination_chain_id_signature = "0xb0750611" + + perform_eth_call_request(destination_chain_id_signature, bridge_contract_hash, json_rpc_named_arguments) + end + + defp get_token_interfaces_version_signature(token_address_hash, json_rpc_named_arguments) do + # keccak 256 from getTokenInterfacesVersion() + get_token_interfaces_version_signature = "0x859ba28c" + + perform_eth_call_request(get_token_interfaces_version_signature, token_address_hash, json_rpc_named_arguments) + end + + defp get_foreign_token_address(omni_bridge_mediator, token_address_hash, json_rpc_named_arguments) do + # keccak 256 from foreignTokenAddress(address) + foreign_token_address_signature = "0x47ac7d6a" + + token_address_hash_abi_encoded = + [token_address_hash.bytes] + |> TypeEncoder.encode([:address]) + |> Base.encode16() + + foreign_token_address_method = foreign_token_address_signature <> token_address_hash_abi_encoded + + perform_eth_call_request(foreign_token_address_method, omni_bridge_mediator, json_rpc_named_arguments) + end + + defp perform_eth_call_request(method, destination, json_rpc_named_arguments) + when not is_nil(json_rpc_named_arguments) do + method + |> Contract.eth_call_request(destination, 1, nil, nil) + |> json_rpc(json_rpc_named_arguments) + end + + defp perform_eth_call_request(_method, _destination, json_rpc_named_arguments) + when is_nil(json_rpc_named_arguments) do + :error + end + + def decode_contract_address_hash_response(resp) do + case resp do + "0x000000000000000000000000" <> address -> + "0x" <> address + + _ -> + nil + end + end + + def decode_contract_integer_response(resp) do + case resp do + "0x" <> integer_encoded -> + {integer_value, _} = Integer.parse(integer_encoded, 16) + integer_value + + _ -> + nil + end + end + + defp set_token_bridged_status(token_address_hash, status) do + case Repo.get(Token, token_address_hash) do + %{bridged: bridged} = target_token -> + if !bridged do + token = Changeset.change(target_token, bridged: status) + + Repo.update(token) + end + + _ -> + :ok + end + end + + defp insert_bridged_token_metadata(token_address_hash, %{ + foreign_chain_id: foreign_chain_id, + foreign_token_address_hash: foreign_token_address_hash, + custom_metadata: custom_metadata, + custom_cap: custom_cap, + lp_token: lp_token, + type: type + }) do + target_token = Repo.get(Token, token_address_hash) + + if target_token do + {:ok, _} = + Repo.insert( + %BridgedToken{ + home_token_contract_address_hash: token_address_hash, + foreign_chain_id: foreign_chain_id, + foreign_token_contract_address_hash: foreign_token_address_hash, + custom_metadata: custom_metadata, + custom_cap: custom_cap, + lp_token: lp_token, + type: type + }, + on_conflict: :nothing + ) + end + end + + # Fetches custom metadata for bridged tokens from the node. + # Currently, gets Balancer token composite tokens with their weights + # from foreign chain + defp get_bridged_token_custom_metadata(foreign_token_address_hash, json_rpc_named_arguments, foreign_json_rpc) + when not is_nil(foreign_json_rpc) and foreign_json_rpc !== "" do + eth_call_foreign_json_rpc_named_arguments = + compose_foreign_json_rpc_named_arguments(json_rpc_named_arguments, foreign_json_rpc) + + balancer_custom_metadata(foreign_token_address_hash, eth_call_foreign_json_rpc_named_arguments) || + sushiswap_custom_metadata(foreign_token_address_hash, eth_call_foreign_json_rpc_named_arguments) + end + + defp get_bridged_token_custom_metadata(_foreign_token_address_hash, _json_rpc_named_arguments, foreign_json_rpc) + when is_nil(foreign_json_rpc) do + nil + end + + defp get_bridged_token_custom_metadata(_foreign_token_address_hash, _json_rpc_named_arguments, foreign_json_rpc) + when foreign_json_rpc == "" do + nil + end + + defp balancer_custom_metadata(foreign_token_address_hash, eth_call_foreign_json_rpc_named_arguments) do + # keccak 256 from getCurrentTokens() + get_current_tokens_signature = "0xcc77828d" + + case get_current_tokens_signature + |> Contract.eth_call_request(foreign_token_address_hash, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments) do + {:ok, "0x"} -> + nil + + {:ok, "0x" <> balancer_current_tokens_encoded} -> + [balancer_current_tokens] = + try do + balancer_current_tokens_encoded + |> Base.decode16!(case: :mixed) + |> TypeDecoder.decode_raw([{:array, :address}]) + rescue + _ -> [] + end + + bridged_token_custom_metadata = + parse_bridged_token_custom_metadata( + balancer_current_tokens, + eth_call_foreign_json_rpc_named_arguments, + foreign_token_address_hash + ) + + tokens_and_weights(bridged_token_custom_metadata) + + _ -> + nil + end + end + + defp tokens_and_weights(bridged_token_custom_metadata) do + with true <- is_map(bridged_token_custom_metadata), + tokens = Map.get(bridged_token_custom_metadata, :tokens), + weights = Map.get(bridged_token_custom_metadata, :weights), + false <- tokens == "" do + if weights !== "", do: "#{tokens} #{weights}", else: tokens + else + _ -> nil + end + end + + defp sushiswap_custom_metadata(foreign_token_address_hash, eth_call_foreign_json_rpc_named_arguments) do + # keccak 256 from token0() + token0_signature = "0x0dfe1681" + + # keccak 256 from token1() + token1_signature = "0xd21220a7" + + # keccak 256 from name() + name_signature = "0x06fdde03" + + # keccak 256 from symbol() + symbol_signature = "0x95d89b41" + + with {:ok, "0x" <> token0_encoded} <- + token0_signature + |> Contract.eth_call_request(foreign_token_address_hash, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments), + {:ok, "0x" <> token1_encoded} <- + token1_signature + |> Contract.eth_call_request(foreign_token_address_hash, 2, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments), + token0_hash <- parse_contract_response(token0_encoded, :address), + token1_hash <- parse_contract_response(token1_encoded, :address), + false <- is_nil(token0_hash), + false <- is_nil(token1_hash), + token0_hash_str <- "0x" <> Base.encode16(token0_hash, case: :lower), + token1_hash_str <- "0x" <> Base.encode16(token1_hash, case: :lower), + {:ok, "0x" <> token0_name_encoded} <- + name_signature + |> Contract.eth_call_request(token0_hash_str, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments), + {:ok, "0x" <> token1_name_encoded} <- + name_signature + |> Contract.eth_call_request(token1_hash_str, 2, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments), + {:ok, "0x" <> token0_symbol_encoded} <- + symbol_signature + |> Contract.eth_call_request(token0_hash_str, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments), + {:ok, "0x" <> token1_symbol_encoded} <- + symbol_signature + |> Contract.eth_call_request(token1_hash_str, 2, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments) do + token0_name = parse_contract_response(token0_name_encoded, :string, {:bytes, 32}) + token1_name = parse_contract_response(token1_name_encoded, :string, {:bytes, 32}) + token0_symbol = parse_contract_response(token0_symbol_encoded, :string, {:bytes, 32}) + token1_symbol = parse_contract_response(token1_symbol_encoded, :string, {:bytes, 32}) + + "#{token0_name}/#{token1_name} (#{token0_symbol}/#{token1_symbol})" + else + _ -> + nil + end + end + + def calc_lp_tokens_total_liquidity do + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + foreign_json_rpc = Application.get_env(:explorer, __MODULE__)[:foreign_json_rpc] + bridged_mainnet_tokens_list = BridgedToken.get_unprocessed_mainnet_lp_tokens_list() + + Enum.each(bridged_mainnet_tokens_list, fn bridged_token -> + case calc_sushiswap_lp_tokens_cap( + bridged_token.home_token_contract_address_hash, + bridged_token.foreign_token_contract_address_hash, + json_rpc_named_arguments, + foreign_json_rpc + ) do + {:ok, new_custom_cap} -> + bridged_token + |> Changeset.change(%{custom_cap: new_custom_cap, lp_token: true}) + |> Repo.update() + + {:error, :not_lp_token} -> + bridged_token + |> Changeset.change(%{lp_token: false}) + |> Repo.update() + end + end) + + Logger.debug(fn -> "Total liquidity fetched for LP tokens" end) + end + + defp calc_sushiswap_lp_tokens_cap( + home_token_contract_address_hash, + foreign_token_address_hash, + json_rpc_named_arguments, + foreign_json_rpc + ) do + eth_call_foreign_json_rpc_named_arguments = + compose_foreign_json_rpc_named_arguments(json_rpc_named_arguments, foreign_json_rpc) + + # keccak 256 from getReserves() + get_reserves_signature = "0x0902f1ac" + + # keccak 256 from token0() + token0_signature = "0x0dfe1681" + + # keccak 256 from token1() + token1_signature = "0xd21220a7" + + # keccak 256 from totalSupply() + total_supply_signature = "0x18160ddd" + + with {:ok, "0x" <> get_reserves_encoded} <- + get_reserves_signature + |> Contract.eth_call_request(foreign_token_address_hash, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments), + {:ok, "0x" <> home_token_total_supply_encoded} <- + total_supply_signature + |> Contract.eth_call_request(home_token_contract_address_hash, 1, nil, nil) + |> json_rpc(json_rpc_named_arguments), + [reserve0, reserve1, _] <- + parse_contract_response(get_reserves_encoded, [{:uint, 112}, {:uint, 112}, {:uint, 32}]), + {:ok, token0_cap_usd} <- + get_lp_token_cap( + home_token_total_supply_encoded, + token0_signature, + reserve0, + foreign_token_address_hash, + eth_call_foreign_json_rpc_named_arguments + ), + {:ok, token1_cap_usd} <- + get_lp_token_cap( + home_token_total_supply_encoded, + token1_signature, + reserve1, + foreign_token_address_hash, + eth_call_foreign_json_rpc_named_arguments + ) do + total_lp_cap = Decimal.add(token0_cap_usd, token1_cap_usd) + {:ok, total_lp_cap} + else + _ -> + {:error, :not_lp_token} + end + end + + defp get_lp_token_cap( + home_token_total_supply_encoded, + token_signature, + reserve, + foreign_token_address_hash, + eth_call_foreign_json_rpc_named_arguments + ) do + # keccak 256 from decimals() + decimals_signature = "0x313ce567" + + # keccak 256 from totalSupply() + total_supply_signature = "0x18160ddd" + + home_token_total_supply = + home_token_total_supply_encoded + |> parse_contract_response({:uint, 256}) + |> Decimal.new() + + case token_signature + |> Contract.eth_call_request(foreign_token_address_hash, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments) do + {:ok, "0x" <> token_encoded} -> + with token_hash <- parse_contract_response(token_encoded, :address), + false <- is_nil(token_hash), + token_hash_str <- "0x" <> Base.encode16(token_hash, case: :lower), + {:ok, "0x" <> token_decimals_encoded} <- + decimals_signature + |> Contract.eth_call_request(token_hash_str, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments), + {:ok, "0x" <> foreign_token_total_supply_encoded} <- + total_supply_signature + |> Contract.eth_call_request(foreign_token_address_hash, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments) do + token_decimals = parse_contract_response(token_decimals_encoded, {:uint, 256}) + + foreign_token_total_supply = + foreign_token_total_supply_encoded + |> parse_contract_response({:uint, 256}) + |> Decimal.new() + + token_decimals_divider = + 10 + |> :math.pow(token_decimals) + |> Decimal.from_float() + + token_cap = + reserve + |> Decimal.div(foreign_token_total_supply) + |> Decimal.mult(home_token_total_supply) + |> Decimal.div(token_decimals_divider) + + token = Token.get_by_contract_address_hash(token_hash_str, []) + + token_cap_usd = + if token && token.fiat_value do + token.fiat_value + |> Decimal.mult(token_cap) + else + 0 + end + + {:ok, token_cap_usd} + else + _ -> :error + end + end + end + + defp parse_contract_response(abi_encoded_value, types) when is_list(types) do + values = + try do + abi_encoded_value + |> Base.decode16!(case: :mixed) + |> TypeDecoder.decode_raw(types) + rescue + _ -> [nil] + end + + values + end + + defp parse_contract_response(abi_encoded_value, type, emergency_type \\ nil) do + [value] = + try do + [res] = decode_contract_response(abi_encoded_value, type) + + [convert_binary_to_string(res, type)] + rescue + _ -> + if emergency_type do + try do + [res] = decode_contract_response(abi_encoded_value, emergency_type) + + [convert_binary_to_string(res, emergency_type)] + rescue + _ -> + [nil] + end + else + [nil] + end + end + + value + end + + defp decode_contract_response(abi_encoded_value, type) do + abi_encoded_value + |> Base.decode16!(case: :mixed) + |> TypeDecoder.decode_raw([type]) + end + + defp convert_binary_to_string(binary, type) do + case type do + {:bytes, _} -> + binary_to_string(binary) + + _ -> + binary + end + end + + defp compose_foreign_json_rpc_named_arguments(json_rpc_named_arguments, foreign_json_rpc) + when foreign_json_rpc != "" do + {_, eth_call_foreign_json_rpc_named_arguments} = + Keyword.get_and_update(json_rpc_named_arguments, :transport_options, fn transport_options -> + {_, updated_transport_options} = + update_transport_options_set_foreign_json_rpc(transport_options, foreign_json_rpc) + + {transport_options, updated_transport_options} + end) + + eth_call_foreign_json_rpc_named_arguments + end + + defp compose_foreign_json_rpc_named_arguments(_json_rpc_named_arguments, foreign_json_rpc) + when foreign_json_rpc == "" do + nil + end + + defp compose_foreign_json_rpc_named_arguments(json_rpc_named_arguments, _foreign_json_rpc) + when is_nil(json_rpc_named_arguments) do + nil + end + + defp update_transport_options_set_foreign_json_rpc(transport_options, foreign_json_rpc) do + Keyword.get_and_update(transport_options, :method_to_url, fn method_to_url -> + {_, updated_method_to_url} = + Keyword.get_and_update(method_to_url, :eth_call, fn eth_call -> + {eth_call, foreign_json_rpc} + end) + + {method_to_url, updated_method_to_url} + end) + end + + defp parse_bridged_token_custom_metadata( + balancer_current_tokens, + eth_call_foreign_json_rpc_named_arguments, + foreign_token_address_hash + ) do + balancer_current_tokens + |> Enum.reduce(%{:tokens => "", :weights => ""}, fn balancer_token_bytes, balancer_tokens_weights -> + balancer_token_hash_without_0x = + balancer_token_bytes + |> Base.encode16(case: :lower) + + balancer_token_hash = "0x" <> balancer_token_hash_without_0x + + # 95d89b41 = keccak256(symbol()) + symbol_signature = "0x95d89b41" + + case symbol_signature + |> Contract.eth_call_request(balancer_token_hash, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments) do + {:ok, "0x" <> symbol_encoded} -> + [symbol] = + symbol_encoded + |> Base.decode16!(case: :mixed) + |> TypeDecoder.decode_raw([:string]) + + # f1b8a9b7 = keccak256(getNormalizedWeight(address)) + get_normalized_weight_signature = "0xf1b8a9b7" + + get_normalized_weight_arg_abi_encoded = + [balancer_token_bytes] + |> TypeEncoder.encode([:address]) + |> Base.encode16(case: :lower) + + get_normalized_weight_abi_encoded = get_normalized_weight_signature <> get_normalized_weight_arg_abi_encoded + + get_normalized_weight_resp = + get_normalized_weight_abi_encoded + |> Contract.eth_call_request(foreign_token_address_hash, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments) + + parse_balancer_weights(get_normalized_weight_resp, balancer_tokens_weights, symbol) + + _ -> + nil + end + end) + end + + defp parse_balancer_weights(get_normalized_weight_resp, balancer_tokens_weights, symbol) do + case get_normalized_weight_resp do + {:ok, "0x" <> normalized_weight_encoded} -> + [normalized_weight] = + try do + normalized_weight_encoded + |> Base.decode16!(case: :mixed) + |> TypeDecoder.decode_raw([{:uint, 256}]) + rescue + _ -> + [] + end + + normalized_weight_to_100_perc = calc_normalized_weight_to_100_perc(normalized_weight) + + normalized_weight_in_perc = + normalized_weight_to_100_perc + |> div(1_000_000_000_000_000_000) + + current_tokens = Map.get(balancer_tokens_weights, :tokens) + current_weights = Map.get(balancer_tokens_weights, :weights) + + tokens_value = combine_tokens_value(current_tokens, symbol) + weights_value = combine_weights_value(current_weights, normalized_weight_in_perc) + + %{:tokens => tokens_value, :weights => weights_value} + + _ -> + nil + end + end + + defp calc_normalized_weight_to_100_perc(normalized_weight) do + if normalized_weight, do: 100 * normalized_weight, else: 0 + end + + defp combine_tokens_value(current_tokens, symbol) do + if current_tokens == "", do: symbol, else: current_tokens <> "/" <> symbol + end + + defp combine_weights_value(current_weights, normalized_weight_in_perc) do + if current_weights == "", + do: "#{normalized_weight_in_perc}", + else: current_weights <> "/" <> "#{normalized_weight_in_perc}" + end + + defp fetch_top_bridged_tokens(chain_ids, paging_options, filter, sorting, options) do + bridged_tokens_query = + __MODULE__ + |> apply_chain_ids_filter(chain_ids) + + base_query = + from(t in Token.base_token_query(nil, sorting), + right_join: bt in subquery(bridged_tokens_query), + on: t.contract_address_hash == bt.home_token_contract_address_hash, + where: t.total_supply > ^0, + where: t.bridged, + select: {t, bt}, + preload: [:contract_address] + ) + + base_query_with_paging = + base_query + |> SortingHelper.page_with_sorting(paging_options, sorting, Token.default_sorting()) + |> limit(^paging_options.page_size) + + query = + if filter && filter !== "" do + case Search.prepare_search_term(filter) do + {:some, filter_term} -> + base_query_with_paging + |> where(fragment("to_tsvector('english', symbol || ' ' || name) @@ to_tsquery(?)", ^filter_term)) + + _ -> + base_query_with_paging + end + else + base_query_with_paging + end + + query + |> Chain.select_repo(options).all() + end + + @spec list_top_bridged_tokens(String.t()) :: [{Token.t(), BridgedToken.t()}] + def list_top_bridged_tokens(filter, options \\ []) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + chain_ids = Keyword.get(options, :chain_ids, nil) + sorting = Keyword.get(options, :sorting, []) + + fetch_top_bridged_tokens(chain_ids, paging_options, filter, sorting, options) + end + + defp apply_chain_ids_filter(query, chain_ids) when chain_ids in [[], nil], do: query + + defp apply_chain_ids_filter(query, chain_ids) when is_list(chain_ids), + do: from(bt in query, where: bt.foreign_chain_id in ^chain_ids) + + def binary_to_string(binary) do + binary + |> :binary.bin_to_list() + |> Enum.filter(fn x -> x != 0 end) + |> List.to_string() + end + + def token_display_name_based_on_bridge_destination(name, foreign_chain_id) do + cond do + Decimal.compare(foreign_chain_id, 1) == :eq -> + name + |> String.replace("on xDai", "from Ethereum") + + Decimal.compare(foreign_chain_id, 56) == :eq -> + name + |> String.replace("on xDai", "from BSC") + + true -> + name + end + end + + def token_display_name_based_on_bridge_destination(name, symbol, foreign_chain_id) do + token_name = + cond do + Decimal.compare(foreign_chain_id, 1) == :eq -> + name + |> String.replace("on xDai", "from Ethereum") + + Decimal.compare(foreign_chain_id, 56) == :eq -> + name + |> String.replace("on xDai", "from BSC") + + true -> + name + end + + "#{token_name} (#{symbol})" + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/tokens.ex b/apps/explorer/lib/explorer/chain/import/runner/tokens.ex index 791ee9daa2f5..638b59a71095 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/tokens.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/tokens.ex @@ -136,36 +136,73 @@ defmodule Explorer.Chain.Import.Runner.Tokens do ) end - def default_on_conflict do - from( - token in Token, - update: [ - set: [ - name: fragment("COALESCE(EXCLUDED.name, ?)", token.name), - symbol: fragment("COALESCE(EXCLUDED.symbol, ?)", token.symbol), - total_supply: fragment("COALESCE(EXCLUDED.total_supply, ?)", token.total_supply), - decimals: fragment("COALESCE(EXCLUDED.decimals, ?)", token.decimals), - type: fragment("COALESCE(EXCLUDED.type, ?)", token.type), - cataloged: fragment("COALESCE(EXCLUDED.cataloged, ?)", token.cataloged), - skip_metadata: fragment("COALESCE(EXCLUDED.skip_metadata, ?)", token.skip_metadata), - # `holder_count` is not updated as a pre-existing token means the `holder_count` is already initialized OR - # need to be migrated with `priv/repo/migrations/scripts/update_new_tokens_holder_count_in_batches.sql.exs` - # Don't update `contract_address_hash` as it is the primary key and used for the conflict target - inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token.inserted_at), - updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token.updated_at) - ] - ], - where: - fragment( - "(EXCLUDED.name, EXCLUDED.symbol, EXCLUDED.total_supply, EXCLUDED.decimals, EXCLUDED.type, EXCLUDED.cataloged, EXCLUDED.skip_metadata) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?)", - token.name, - token.symbol, - token.total_supply, - token.decimals, - token.type, - token.cataloged, - token.skip_metadata - ) - ) + if Application.compile_env(:explorer, Explorer.Chain.BridgedToken)[:enabled] do + def default_on_conflict do + from( + token in Token, + update: [ + set: [ + name: fragment("COALESCE(EXCLUDED.name, ?)", token.name), + symbol: fragment("COALESCE(EXCLUDED.symbol, ?)", token.symbol), + total_supply: fragment("COALESCE(EXCLUDED.total_supply, ?)", token.total_supply), + decimals: fragment("COALESCE(EXCLUDED.decimals, ?)", token.decimals), + type: fragment("COALESCE(EXCLUDED.type, ?)", token.type), + cataloged: fragment("COALESCE(EXCLUDED.cataloged, ?)", token.cataloged), + bridged: fragment("COALESCE(EXCLUDED.bridged, ?)", token.bridged), + skip_metadata: fragment("COALESCE(EXCLUDED.skip_metadata, ?)", token.skip_metadata), + # `holder_count` is not updated as a pre-existing token means the `holder_count` is already initialized OR + # need to be migrated with `priv/repo/migrations/scripts/update_new_tokens_holder_count_in_batches.sql.exs` + # Don't update `contract_address_hash` as it is the primary key and used for the conflict target + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.name, EXCLUDED.symbol, EXCLUDED.total_supply, EXCLUDED.decimals, EXCLUDED.type, EXCLUDED.cataloged, EXCLUDED.bridged, EXCLUDED.skip_metadata) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?)", + token.name, + token.symbol, + token.total_supply, + token.decimals, + token.type, + token.cataloged, + token.bridged, + token.skip_metadata + ) + ) + end + else + def default_on_conflict do + from( + token in Token, + update: [ + set: [ + name: fragment("COALESCE(EXCLUDED.name, ?)", token.name), + symbol: fragment("COALESCE(EXCLUDED.symbol, ?)", token.symbol), + total_supply: fragment("COALESCE(EXCLUDED.total_supply, ?)", token.total_supply), + decimals: fragment("COALESCE(EXCLUDED.decimals, ?)", token.decimals), + type: fragment("COALESCE(EXCLUDED.type, ?)", token.type), + cataloged: fragment("COALESCE(EXCLUDED.cataloged, ?)", token.cataloged), + skip_metadata: fragment("COALESCE(EXCLUDED.skip_metadata, ?)", token.skip_metadata), + # `holder_count` is not updated as a pre-existing token means the `holder_count` is already initialized OR + # need to be migrated with `priv/repo/migrations/scripts/update_new_tokens_holder_count_in_batches.sql.exs` + # Don't update `contract_address_hash` as it is the primary key and used for the conflict target + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.name, EXCLUDED.symbol, EXCLUDED.total_supply, EXCLUDED.decimals, EXCLUDED.type, EXCLUDED.cataloged, EXCLUDED.skip_metadata) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?)", + token.name, + token.symbol, + token.total_supply, + token.decimals, + token.type, + token.cataloged, + token.skip_metadata + ) + ) + end end end diff --git a/apps/explorer/lib/explorer/chain/token.ex b/apps/explorer/lib/explorer/chain/token.ex index 66c3fdd5e2de..b3606466242a 100644 --- a/apps/explorer/lib/explorer/chain/token.ex +++ b/apps/explorer/lib/explorer/chain/token.ex @@ -24,7 +24,7 @@ defmodule Explorer.Chain.Token do alias Ecto.Changeset alias Explorer.{Chain, SortingHelper} - alias Explorer.Chain.{Address, Hash, Search, Token} + alias Explorer.Chain.{Address, BridgedToken, Hash, Search, Token} alias Explorer.SmartContract.Helper @default_sorting [ @@ -35,6 +35,16 @@ defmodule Explorer.Chain.Token do asc: :contract_address_hash ] + if Application.compile_env(:explorer, Explorer.Chain.BridgedToken)[:enabled] do + @bridged_field quote( + do: [ + bridged: boolean() + ] + ) + else + @bridged_field quote(do: []) + end + @typedoc """ * `name` - Name of the token * `symbol` - Trading symbol of the token @@ -51,23 +61,25 @@ defmodule Explorer.Chain.Token do * `icon_url` - URL of the token's icon. * `is_verified_via_admin_panel` - is token verified via admin panel. """ - @type t :: %Token{ - name: String.t(), - symbol: String.t(), - total_supply: Decimal.t() | nil, - decimals: non_neg_integer(), - type: String.t(), - cataloged: boolean(), - contract_address: %Ecto.Association.NotLoaded{} | Address.t(), - contract_address_hash: Hash.Address.t(), - holder_count: non_neg_integer() | nil, - skip_metadata: boolean(), - total_supply_updated_at_block: non_neg_integer() | nil, - fiat_value: Decimal.t() | nil, - circulating_market_cap: Decimal.t() | nil, - icon_url: String.t(), - is_verified_via_admin_panel: boolean() - } + @type t :: + %Token{ + unquote_splicing(@bridged_field), + name: String.t(), + symbol: String.t(), + total_supply: Decimal.t() | nil, + decimals: non_neg_integer(), + type: String.t(), + cataloged: boolean(), + contract_address: %Ecto.Association.NotLoaded{} | Address.t(), + contract_address_hash: Hash.Address.t(), + holder_count: non_neg_integer() | nil, + skip_metadata: boolean(), + total_supply_updated_at_block: non_neg_integer() | nil, + fiat_value: Decimal.t() | nil, + circulating_market_cap: Decimal.t() | nil, + icon_url: String.t(), + is_verified_via_admin_panel: boolean() + } @derive {Poison.Encoder, except: [ @@ -110,6 +122,10 @@ defmodule Explorer.Chain.Token do type: Hash.Address ) + if Application.compile_env(:explorer, BridgedToken)[:enabled] do + field(:bridged, :boolean) + end + timestamps() end @@ -118,8 +134,10 @@ defmodule Explorer.Chain.Token do @doc false def changeset(%Token{} = token, params \\ %{}) do + additional_attrs = if BridgedToken.enabled?(), do: [:bridged], else: [] + token - |> cast(params, @required_attrs ++ @optional_attrs) + |> cast(params, @required_attrs ++ @optional_attrs ++ additional_attrs) |> validate_required(@required_attrs) |> trim_name() |> sanitize_token_input(:name) @@ -168,6 +186,14 @@ defmodule Explorer.Chain.Token do from(token in __MODULE__, where: token.contract_address_hash in ^contract_address_hashes) end + def base_token_query(type, sorting) do + query = from(t in Token, preload: [:contract_address]) + + query |> apply_filter(type) |> SortingHelper.apply_sorting(sorting, @default_sorting) + end + + def default_sorting, do: @default_sorting + @doc """ Lists the top `t:__MODULE__.t/0`'s'. """ diff --git a/apps/explorer/lib/explorer/repo.ex b/apps/explorer/lib/explorer/repo.ex index fd0ad4778b8b..e299150cdb81 100644 --- a/apps/explorer/lib/explorer/repo.ex +++ b/apps/explorer/lib/explorer/repo.ex @@ -200,4 +200,14 @@ defmodule Explorer.Repo do ConfigHelper.init_repo_module(__MODULE__, opts) end end + + defmodule BridgedTokens do + use Ecto.Repo, + otp_app: :explorer, + adapter: Ecto.Adapters.Postgres + + def init(_, opts) do + ConfigHelper.init_repo_module(__MODULE__, opts) + end + end end diff --git a/apps/explorer/lib/explorer/tags/address_tag_cataloger.ex b/apps/explorer/lib/explorer/tags/address_tag_cataloger.ex index a8586313a612..da54d81e2d88 100644 --- a/apps/explorer/lib/explorer/tags/address_tag_cataloger.ex +++ b/apps/explorer/lib/explorer/tags/address_tag_cataloger.ex @@ -165,7 +165,11 @@ defmodule Explorer.Tags.AddressTag.Cataloger do defp set_omni_tag do set_tag_for_multiple_env_var_addresses( - ["ETH_OMNI_BRIDGE_MEDIATOR", "BSC_OMNI_BRIDGE_MEDIATOR", "POA_OMNI_BRIDGE_MEDIATOR"], + [ + "BRIDGED_TOKENS_ETH_OMNI_BRIDGE_MEDIATOR", + "BRIDGED_TOKENS_BSC_OMNI_BRIDGE_MEDIATOR", + "BRIDGED_TOKENS_POA_OMNI_BRIDGE_MEDIATOR" + ], "omni bridge" ) end diff --git a/apps/explorer/priv/bridged_tokens/migrations/20230919080116_add_bridged_tokens.exs b/apps/explorer/priv/bridged_tokens/migrations/20230919080116_add_bridged_tokens.exs new file mode 100644 index 000000000000..2622358c1dff --- /dev/null +++ b/apps/explorer/priv/bridged_tokens/migrations/20230919080116_add_bridged_tokens.exs @@ -0,0 +1,29 @@ +defmodule Explorer.Repo.BridgedTokens.Migrations.AddBridgedTokens do + use Ecto.Migration + + def change do + alter table(:tokens) do + add(:bridged, :boolean, null: true) + end + + create table(:bridged_tokens, primary_key: false) do + add(:foreign_chain_id, :numeric, null: false) + add(:foreign_token_contract_address_hash, :bytea, null: false) + add(:exchange_rate, :decimal) + add(:custom_metadata, :string, null: true) + add(:lp_token, :boolean, null: true) + add(:custom_cap, :decimal, null: true) + add(:type, :string, null: true) + + add( + :home_token_contract_address_hash, + references(:tokens, column: :contract_address_hash, on_delete: :delete_all, type: :bytea), + null: false + ) + + timestamps() + end + + create(unique_index(:bridged_tokens, :home_token_contract_address_hash)) + end +end diff --git a/apps/explorer/test/support/data_case.ex b/apps/explorer/test/support/data_case.ex index 579ea23a096a..d0be3409fb9d 100644 --- a/apps/explorer/test/support/data_case.ex +++ b/apps/explorer/test/support/data_case.ex @@ -40,6 +40,7 @@ defmodule Explorer.DataCase do :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.RSK) :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.Shibarium) :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.Suave) + :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.BridgedTokens) unless tags[:async] do Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()}) @@ -49,6 +50,7 @@ defmodule Explorer.DataCase do Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.RSK, {:shared, self()}) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Shibarium, {:shared, self()}) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, {:shared, self()}) + Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.BridgedTokens, {:shared, self()}) end Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.BlockNumber.child_id()) diff --git a/apps/explorer/test/test_helper.exs b/apps/explorer/test/test_helper.exs index fd2de129984e..3459a12781fd 100644 --- a/apps/explorer/test/test_helper.exs +++ b/apps/explorer/test/test_helper.exs @@ -18,6 +18,7 @@ Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonZkevm, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.RSK, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Shibarium, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, :auto) +Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.BridgedTokens, :auto) Mox.defmock(Explorer.ExchangeRates.Source.TestSource, for: Explorer.ExchangeRates.Source) Mox.defmock(Explorer.Market.History.Source.Price.TestSource, for: Explorer.Market.History.Source.Price) diff --git a/apps/indexer/lib/indexer/bridged_tokens/calc_lp_tokens_total_liquidity.ex b/apps/indexer/lib/indexer/bridged_tokens/calc_lp_tokens_total_liquidity.ex new file mode 100644 index 000000000000..fabf22e75dcf --- /dev/null +++ b/apps/indexer/lib/indexer/bridged_tokens/calc_lp_tokens_total_liquidity.ex @@ -0,0 +1,52 @@ +defmodule Indexer.BridgedTokens.CalcLpTokensTotalLiquidity do + @moduledoc """ + Periodically updates LP tokens total liquidity + """ + + use GenServer + + require Logger + + alias Explorer.Chain.BridgedToken + + @interval :timer.minutes(20) + + def start_link([init_opts, gen_server_opts]) do + start_link(init_opts, gen_server_opts) + end + + def start_link(init_opts, gen_server_opts) do + GenServer.start_link(__MODULE__, init_opts, gen_server_opts) + end + + @impl GenServer + def init(opts) do + interval = opts[:interval] || @interval + + Process.send_after(self(), :calc_total_liquidity, interval) + + {:ok, %{interval: interval}} + end + + @impl GenServer + def handle_info(:calc_total_liquidity, %{interval: interval} = state) do + Logger.debug(fn -> "Calc LP tokens total liquidity" end) + + calc_total_liquidity() + + Process.send_after(self(), :calc_total_liquidity, interval) + + {:noreply, state} + end + + # don't handle other messages (e.g. :ssl_closed) + def handle_info(_, state) do + {:noreply, state} + end + + defp calc_total_liquidity do + BridgedToken.calc_lp_tokens_total_liquidity() + + Logger.debug(fn -> "Total liquidity fetched for LP tokens" end) + end +end diff --git a/apps/indexer/lib/indexer/bridged_tokens/set_amb_bridged_metadata_for_tokens.ex b/apps/indexer/lib/indexer/bridged_tokens/set_amb_bridged_metadata_for_tokens.ex new file mode 100644 index 000000000000..02fcaa970502 --- /dev/null +++ b/apps/indexer/lib/indexer/bridged_tokens/set_amb_bridged_metadata_for_tokens.ex @@ -0,0 +1,44 @@ +defmodule Indexer.BridgedTokens.SetAmbBridgedMetadataForTokens do + @moduledoc """ + Sets token metadata for bridged tokens from AMB extensions. + """ + + use GenServer + + require Logger + + alias Explorer.Chain.BridgedToken + + def start_link([init_opts, gen_server_opts]) do + start_link(init_opts, gen_server_opts) + end + + def start_link(init_opts, gen_server_opts) do + GenServer.start_link(__MODULE__, init_opts, gen_server_opts) + end + + @impl GenServer + def init(_opts) do + send(self(), :process_amb_tokens) + + {:ok, %{}} + end + + @impl GenServer + def handle_info(:process_amb_tokens, state) do + fetch_amb_bridged_tokens_metadata() + + {:noreply, state} + end + + # don't handle other messages (e.g. :ssl_closed) + def handle_info(_, state) do + {:noreply, state} + end + + defp fetch_amb_bridged_tokens_metadata do + :ok = BridgedToken.process_amb_tokens() + + Logger.debug(fn -> "Bridged status fetched for AMB tokens" end) + end +end diff --git a/apps/indexer/lib/indexer/bridged_tokens/set_omni_bridged_metadata_for_tokens.ex b/apps/indexer/lib/indexer/bridged_tokens/set_omni_bridged_metadata_for_tokens.ex new file mode 100644 index 000000000000..e6ba7afc3cb2 --- /dev/null +++ b/apps/indexer/lib/indexer/bridged_tokens/set_omni_bridged_metadata_for_tokens.ex @@ -0,0 +1,54 @@ +defmodule Indexer.BridgedTokens.SetOmniBridgedMetadataForTokens do + @moduledoc """ + Periodically checks unprocessed tokens and sets bridged status. + """ + + use GenServer + + require Logger + + alias Explorer.Chain.BridgedToken + + @interval :timer.minutes(20) + + def start_link([init_opts, gen_server_opts]) do + start_link(init_opts, gen_server_opts) + end + + def start_link(init_opts, gen_server_opts) do + GenServer.start_link(__MODULE__, init_opts, gen_server_opts) + end + + @impl GenServer + def init(opts) do + interval = opts[:interval] || @interval + + send(self(), :reveal_unprocessed_tokens) + + {:ok, %{interval: interval}} + end + + @impl GenServer + def handle_info(:reveal_unprocessed_tokens, %{interval: interval} = state) do + Logger.debug(fn -> "Reveal unprocessed tokens" end) + + {:ok, token_addresses} = BridgedToken.unprocessed_token_addresses_to_reveal_bridged_tokens() + + fetch_omni_bridged_tokens_metadata(token_addresses) + + Process.send_after(self(), :reveal_unprocessed_tokens, interval) + + {:noreply, state} + end + + # don't handle other messages (e.g. :ssl_closed) + def handle_info(_, state) do + {:noreply, state} + end + + defp fetch_omni_bridged_tokens_metadata(token_addresses) do + :ok = BridgedToken.fetch_omni_bridged_tokens_metadata(token_addresses) + + Logger.debug(fn -> "Bridged status fetched for tokens" end) + end +end diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index 7dd38efb31a3..3ee2f6ad350f 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -5,8 +5,13 @@ defmodule Indexer.Supervisor do use Supervisor + alias Explorer.Chain.BridgedToken + alias Indexer.{ Block, + BridgedTokens.CalcLpTokensTotalLiquidity, + BridgedTokens.SetAmbBridgedMetadataForTokens, + BridgedTokens.SetOmniBridgedMetadataForTokens, PendingOpsCleaner, PendingTransactionsSanitizer } @@ -178,12 +183,31 @@ defmodule Indexer.Supervisor do ] |> List.flatten() + all_fetchers = maybe_add_bridged_tokens_fetchers(basic_fetchers) + Supervisor.init( - basic_fetchers, + all_fetchers, strategy: :one_for_one ) end + defp maybe_add_bridged_tokens_fetchers(basic_fetchers) do + extended_fetchers = + if BridgedToken.enabled?() && BridgedToken.necessary_envs_passed?() do + [{CalcLpTokensTotalLiquidity, [[], []]}, {SetOmniBridgedMetadataForTokens, [[], []]}] ++ basic_fetchers + else + basic_fetchers + end + + amb_bridge_mediators = Application.get_env(:explorer, Explorer.Chain.BridgedToken)[:amb_bridge_mediators] + + if BridgedToken.enabled?() && amb_bridge_mediators && amb_bridge_mediators !== "" do + [{SetAmbBridgedMetadataForTokens, [[], []]} | extended_fetchers] + else + extended_fetchers + end + end + defp configure(process, opts) do if Application.get_env(:indexer, process)[:enabled] do [{process, opts}] diff --git a/config/config_helper.exs b/config/config_helper.exs index 645b20286c51..985654e286df 100644 --- a/config/config_helper.exs +++ b/config/config_helper.exs @@ -7,13 +7,20 @@ defmodule ConfigHelper do def repos do base_repos = [Explorer.Repo, Explorer.Repo.Account] - case System.get_env("CHAIN_TYPE") do - "polygon_edge" -> base_repos ++ [Explorer.Repo.PolygonEdge] - "polygon_zkevm" -> base_repos ++ [Explorer.Repo.PolygonZkevm] - "rsk" -> base_repos ++ [Explorer.Repo.RSK] - "shibarium" -> base_repos ++ [Explorer.Repo.Shibarium] - "suave" -> base_repos ++ [Explorer.Repo.Suave] - _ -> base_repos + repos = + case System.get_env("CHAIN_TYPE") do + "polygon_edge" -> base_repos ++ [Explorer.Repo.PolygonEdge] + "polygon_zkevm" -> base_repos ++ [Explorer.Repo.PolygonZkevm] + "rsk" -> base_repos ++ [Explorer.Repo.RSK] + "shibarium" -> base_repos ++ [Explorer.Repo.Shibarium] + "suave" -> base_repos ++ [Explorer.Repo.Suave] + _ -> base_repos + end + + if System.get_env("BRIDGED_TOKENS_ENABLED") do + repos ++ [Explorer.Repo.BridgedTokens] + else + repos end end diff --git a/config/runtime.exs b/config/runtime.exs index 3d3d6f103d68..9a3f329acb92 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -485,6 +485,13 @@ config :explorer, Explorer.Migrator.TransactionsDenormalization, batch_size: ConfigHelper.parse_integer_env_var("DENORMALIZATION_MIGRATION_BATCH_SIZE", 500), concurrency: ConfigHelper.parse_integer_env_var("DENORMALIZATION_MIGRATION_CONCURRENCY", 10) +config :explorer, Explorer.Chain.BridgedToken, + eth_omni_bridge_mediator: System.get_env("BRIDGED_TOKENS_ETH_OMNI_BRIDGE_MEDIATOR"), + bsc_omni_bridge_mediator: System.get_env("BRIDGED_TOKENS_BSC_OMNI_BRIDGE_MEDIATOR"), + poa_omni_bridge_mediator: System.get_env("BRIDGED_TOKENS_POA_OMNI_BRIDGE_MEDIATOR"), + amb_bridge_mediators: System.get_env("BRIDGED_TOKENS_AMB_BRIDGE_MEDIATORS"), + foreign_json_rpc: System.get_env("BRIDGED_TOKENS_FOREIGN_JSON_RPC", "") + ############### ### Indexer ### ############### diff --git a/config/runtime/dev.exs b/config/runtime/dev.exs index d0e69f6937d4..c8c56981d98c 100644 --- a/config/runtime/dev.exs +++ b/config/runtime/dev.exs @@ -115,6 +115,15 @@ config :explorer, Explorer.Repo.Shibarium, url: System.get_env("DATABASE_URL"), pool_size: 1 +# Configures BridgedTokens database +config :explorer, Explorer.Repo.BridgedTokens, + database: database, + hostname: hostname, + url: System.get_env("DATABASE_URL"), + # actually this repo is not started, and its pool size remains unused. + # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type + pool_size: 1 + variant = Variant.get() Code.require_file("#{variant}.exs", "apps/explorer/config/dev") diff --git a/config/runtime/prod.exs b/config/runtime/prod.exs index 7abd88768122..c22e140c9ee4 100644 --- a/config/runtime/prod.exs +++ b/config/runtime/prod.exs @@ -87,6 +87,14 @@ config :explorer, Explorer.Repo.Shibarium, pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() +# Configures BridgedTokens database +config :explorer, Explorer.Repo.BridgedTokens, + url: System.get_env("DATABASE_URL"), + # actually this repo is not started, and its pool size remains unused. + # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type + pool_size: 1, + ssl: ExplorerConfigHelper.ssl_enabled?() + variant = Variant.get() Code.require_file("#{variant}.exs", "apps/explorer/config/prod") diff --git a/cspell.json b/cspell.json index e2289d76f9b2..503b81b9997d 100644 --- a/cspell.json +++ b/cspell.json @@ -568,7 +568,8 @@ "evmversion", "verifyproxycontract", "checkproxyverification", - "NOTOK" + "NOTOK", + "sushiswap" ], "enableFiletypes": [ "dotenv", From 501cf8933876c76e4274cdfe408d33e695f12235 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 18 Jan 2024 14:00:50 +0300 Subject: [PATCH 351/607] User operation in the search --- CHANGELOG.md | 6 + .../lib/block_scout_web/chain.ex | 35 +- .../templates/search/_tile.html.eex | 2 + .../views/api/v2/search_view.ex | 16 +- apps/explorer/lib/explorer/chain/search.ex | 397 ++++++++---------- ...ex => smart_contract_additional_source.ex} | 0 .../lib/explorer/chain/user_operation.ex | 77 ++++ 7 files changed, 305 insertions(+), 228 deletions(-) rename apps/explorer/lib/explorer/chain/{smart_contract_additional_sources.ex => smart_contract_additional_source.ex} (100%) create mode 100644 apps/explorer/lib/explorer/chain/user_operation.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index c4cf82d576a4..52f7ceb8d633 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,15 @@ ### Features +- [#9189](https://github.com/blockscout/blockscout/pull/9189) - User operations in the search - [#9169](https://github.com/blockscout/blockscout/pull/9169) - Add bridged tokens functionality to master branch - [#9158](https://github.com/blockscout/blockscout/pull/9158) - Increase shared memory for PostgreSQL containers - [#9155](https://github.com/blockscout/blockscout/pull/9155) - Allow bypassing avg block time in proxy implementation re-fetch ttl calculation +- [#9145](https://github.com/blockscout/blockscout/pull/9145) - Proxy for Account abstraction microservice +- [#9131](https://github.com/blockscout/blockscout/pull/9131) - Merge addresses stage with address referencing +- [#9072](https://github.com/blockscout/blockscout/pull/9072) - Add tracing by block logic for geth +- [#9056](https://github.com/blockscout/blockscout/pull/9056) - Noves.fi API proxy +- [#9155](https://github.com/blockscout/blockscout/pull/9155) - Allow bypassing avg block time in proxy implementation re-fetch ttl calculation - [#9148](https://github.com/blockscout/blockscout/pull/9148) - Add `/api/v2/utils/decode-calldata` - [#9145](https://github.com/blockscout/blockscout/pull/9145) - Proxy for Account abstraction microservice - [#9132](https://github.com/blockscout/blockscout/pull/9132) - Fetch token image from CoinGecko diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index a0798ff247a6..5df851246141 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -15,6 +15,8 @@ defmodule BlockScoutWeb.Chain do token_contract_address_from_token_name: 1 ] + alias Explorer.Chain.UserOperation + import Explorer.Helper, only: [parse_integer: 1] alias Ecto.Association.NotLoaded @@ -54,7 +56,7 @@ defmodule BlockScoutWeb.Chain do @page_size 50 @default_paging_options %PagingOptions{page_size: @page_size + 1} @address_hash_len 40 - @tx_block_hash_len 64 + @tx_block_op_hash_len 64 def default_paging_options do @default_paging_options @@ -80,20 +82,21 @@ defmodule BlockScoutWeb.Chain do end end - @spec from_param(String.t()) :: {:ok, Address.t() | Block.t() | Transaction.t()} | {:error, :not_found} + @spec from_param(String.t()) :: + {:ok, Address.t() | Block.t() | Transaction.t() | UserOperation.t()} | {:error, :not_found} def from_param(param) def from_param("0x" <> number_string = param) when byte_size(number_string) == @address_hash_len, do: address_from_param(param) - def from_param("0x" <> number_string = param) when byte_size(number_string) == @tx_block_hash_len, - do: block_or_transaction_from_param(param) + def from_param("0x" <> number_string = param) when byte_size(number_string) == @tx_block_op_hash_len, + do: block_or_transaction_or_operation_from_param(param) def from_param(param) when byte_size(param) == @address_hash_len, do: address_from_param("0x" <> param) - def from_param(param) when byte_size(param) == @tx_block_hash_len, - do: block_or_transaction_from_param("0x" <> param) + def from_param(param) when byte_size(param) == @tx_block_op_hash_len, + do: block_or_transaction_or_operation_from_param("0x" <> param) def from_param(string) when is_binary(string) do case param_to_block_number(string) do @@ -670,9 +673,9 @@ defmodule BlockScoutWeb.Chain do %{"fiat_value" => ctb.fiat_value, "value" => value, "id" => id} end - defp block_or_transaction_from_param(param) do + defp block_or_transaction_or_operation_from_param(param) do with {:error, :not_found} <- transaction_from_param(param) do - hash_string_to_block(param) + hash_string_to_block_or_operation(param) end end @@ -686,13 +689,15 @@ defmodule BlockScoutWeb.Chain do end end - defp hash_string_to_block(hash_string) do - case string_to_block_hash(hash_string) do - {:ok, hash} -> - hash_to_block(hash) - - :error -> - {:error, :not_found} + defp hash_string_to_block_or_operation(hash_string) do + with {:ok, hash} <- string_to_block_hash(hash_string), + {:error, :not_found} <- hash_to_block(hash), + {:user_operations_enabled, true} <- {:user_operations_enabled, UserOperation.user_operations_enabled?()} do + UserOperation.hash_to_user_operation(hash) + else + {:user_operations_enabled, false} -> {:error, :not_found} + :error -> {:error, :not_found} + res -> res end end diff --git a/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex index 0b48cedab832..aa4d3cbf89df 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex @@ -80,6 +80,8 @@ <%= render BlockScoutWeb.TransactionView, "_link.html", transaction_hash: "0x" <> Base.encode16(@result.tx_hash, case: :lower) %> + <% "user_operation" -> %> + <%= "0x" <> Base.encode16(@result.user_operation_hash, case: :lower) %> <% "block" -> %> <%= link( "0x" <> Base.encode16(@result.block_hash, case: :lower), diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex index b56c67352056..05a8b95eb089 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.API.V2.SearchView do alias BlockScoutWeb.{BlockView, Endpoint} alias Explorer.Chain - alias Explorer.Chain.{Address, Block, Hash, Transaction} + alias Explorer.Chain.{Address, Block, Hash, Transaction, UserOperation} def render("search_results.json", %{search_results: search_results, next_page_params: next_page_params}) do %{"items" => Enum.map(search_results, &prepare_search_result/1), "next_page_params" => next_page_params} @@ -84,6 +84,16 @@ defmodule BlockScoutWeb.API.V2.SearchView do } end + def prepare_search_result(%{type: "user_operation"} = search_result) do + user_operation_hash = hash_to_string(search_result.user_operation_hash) + + %{ + "type" => search_result.type, + "user_operation_hash" => user_operation_hash, + "timestamp" => search_result.timestamp + } + end + defp hash_to_string(%Hash{bytes: bytes}), do: hash_to_string(bytes) defp hash_to_string(hash), do: "0x" <> Base.encode16(hash, case: :lower) @@ -106,4 +116,8 @@ defmodule BlockScoutWeb.API.V2.SearchView do defp redirect_search_results(%Transaction{} = item) do %{"type" => "transaction", "parameter" => to_string(item.hash)} end + + defp redirect_search_results(%UserOperation{} = item) do + %{"type" => "user_operation", "parameter" => to_string(item.hash)} + end end diff --git a/apps/explorer/lib/explorer/chain/search.ex b/apps/explorer/lib/explorer/chain/search.ex index 20082bccbfb8..56b94d7f9395 100644 --- a/apps/explorer/lib/explorer/chain/search.ex +++ b/apps/explorer/lib/explorer/chain/search.ex @@ -24,7 +24,8 @@ defmodule Explorer.Chain.Search do DenormalizationHelper, SmartContract, Token, - Transaction + Transaction, + UserOperation } @doc """ @@ -39,38 +40,7 @@ defmodule Explorer.Chain.Search do result = case prepare_search_term(string) do {:some, term} -> - tokens_query = search_token_query(string, term) - contracts_query = search_contract_query(term) - labels_query = search_label_query(term) - tx_query = search_tx_query(string) - address_query = search_address_query(string) - block_query = search_block_query(string) - - basic_query = - from( - tokens in subquery(tokens_query), - union: ^contracts_query, - union: ^labels_query - ) - - query = - cond do - address_query -> - basic_query - |> union(^address_query) - - tx_query -> - basic_query - |> union(^tx_query) - |> union(^block_query) - - block_query -> - basic_query - |> union(^block_query) - - true -> - basic_query - end + query = base_joint_query(string, term) ordered_query = from(items in subquery(query), @@ -109,6 +79,50 @@ defmodule Explorer.Chain.Search do result ++ ens_result end + def base_joint_query(string, term) do + tokens_query = search_token_query(string, term) + contracts_query = search_contract_query(term) + labels_query = search_label_query(term) + address_query = search_address_query(string) + block_query = search_block_query(string) + + basic_query = + from( + tokens in subquery(tokens_query), + union: ^contracts_query, + union: ^labels_query + ) + + cond do + address_query -> + basic_query + |> union(^address_query) + + valid_tx_or_op_hash?(string) -> + tx_query = search_tx_query(string) + + if UserOperation.user_operations_enabled?() do + user_operation_query = search_user_operation_query(string) + + basic_query + |> union(^tx_query) + |> union(^user_operation_query) + |> union(^block_query) + else + basic_query + |> union(^tx_query) + |> union(^block_query) + end + + block_query -> + basic_query + |> union(^block_query) + + true -> + basic_query + end + end + defp maybe_run_ens_task(%PagingOptions{key: nil}, query_string, options) do Task.async(fn -> search_ens_name(query_string, options) end) end @@ -158,8 +172,18 @@ defmodule Explorer.Chain.Search do |> select_repo(options).all() tx_result = - if query = search_tx_query(search_query) do - query + if valid_tx_or_op_hash?(search_query) do + search_query + |> search_tx_query() + |> select_repo(options).all() + else + [] + end + + op_result = + if valid_tx_or_op_hash?(search_query) && UserOperation.user_operations_enabled?() do + search_query + |> search_user_operation_query() |> select_repo(options).all() else [] @@ -185,7 +209,16 @@ defmodule Explorer.Chain.Search do ens_result = await_ens_task(ens_task) non_empty_lists = - [tokens_result, contracts_result, labels_result, tx_result, address_result, blocks_result, ens_result] + [ + tokens_result, + contracts_result, + labels_result, + tx_result, + op_result, + address_result, + blocks_result, + ens_result + ] |> Enum.filter(fn list -> Enum.count(list) > 0 end) |> Enum.sort_by(fn list -> Enum.count(list) end, :asc) @@ -232,26 +265,14 @@ defmodule Explorer.Chain.Search do end defp search_label_query(term) do - label_search_fields = %{ - address_hash: dynamic([att, _, _], att.address_hash), - tx_hash: dynamic([_, _, _], type(^nil, :binary)), - block_hash: dynamic([_, _, _], type(^nil, :binary)), - type: "label", - name: dynamic([_, at, _], at.display_name), - symbol: nil, - holder_count: nil, - inserted_at: dynamic([att, _, _], att.inserted_at), - block_number: 0, - icon_url: nil, - token_type: nil, - timestamp: dynamic([_, _, _], type(^nil, :utc_datetime_usec)), - verified: dynamic([_, _, smart_contract], not is_nil(smart_contract)), - exchange_rate: nil, - total_supply: nil, - circulating_market_cap: nil, - priority: 1, - is_verified_via_admin_panel: nil - } + label_search_fields = + search_fields() + |> Map.put(:address_hash, dynamic([att, _, _], att.address_hash)) + |> Map.put(:type, "label") + |> Map.put(:name, dynamic([_, at, _], at.display_name)) + |> Map.put(:inserted_at, dynamic([att, _, _], att.inserted_at)) + |> Map.put(:verified, dynamic([_, _, smart_contract], not is_nil(smart_contract))) + |> Map.put(:priority, 1) inner_query = from(tag in AddressTag, @@ -269,26 +290,21 @@ defmodule Explorer.Chain.Search do end defp search_token_query(string, term) do - token_search_fields = %{ - address_hash: dynamic([token, _], token.contract_address_hash), - tx_hash: dynamic([_, _], type(^nil, :binary)), - block_hash: dynamic([_, _], type(^nil, :binary)), - type: "token", - name: dynamic([token, _], token.name), - symbol: dynamic([token, _], token.symbol), - holder_count: dynamic([token, _], token.holder_count), - inserted_at: dynamic([token, _], token.inserted_at), - block_number: 0, - icon_url: dynamic([token, _], token.icon_url), - token_type: dynamic([token, _], token.type), - timestamp: dynamic([_, _], type(^nil, :utc_datetime_usec)), - verified: dynamic([_, smart_contract], not is_nil(smart_contract)), - exchange_rate: dynamic([token, _], token.fiat_value), - total_supply: dynamic([token, _], token.total_supply), - circulating_market_cap: dynamic([token, _], token.circulating_market_cap), - priority: 0, - is_verified_via_admin_panel: dynamic([token, _], token.is_verified_via_admin_panel) - } + token_search_fields = + search_fields() + |> Map.put(:address_hash, dynamic([token, _], token.contract_address_hash)) + |> Map.put(:type, "token") + |> Map.put(:name, dynamic([token, _], token.name)) + |> Map.put(:symbol, dynamic([token, _], token.symbol)) + |> Map.put(:holder_count, dynamic([token, _], token.holder_count)) + |> Map.put(:inserted_at, dynamic([token, _], token.inserted_at)) + |> Map.put(:icon_url, dynamic([token, _], token.icon_url)) + |> Map.put(:token_type, dynamic([token, _], token.type)) + |> Map.put(:verified, dynamic([_, smart_contract], not is_nil(smart_contract))) + |> Map.put(:exchange_rate, dynamic([token, _], token.fiat_value)) + |> Map.put(:total_supply, dynamic([token, _], token.total_supply)) + |> Map.put(:circulating_market_cap, dynamic([token, _], token.circulating_market_cap)) + |> Map.put(:is_verified_via_admin_panel, dynamic([token, _], token.is_verified_via_admin_panel)) case Chain.string_to_address_hash(string) do {:ok, address_hash} -> @@ -310,26 +326,13 @@ defmodule Explorer.Chain.Search do end defp search_contract_query(term) do - contract_search_fields = %{ - address_hash: dynamic([smart_contract, _], smart_contract.address_hash), - tx_hash: dynamic([_, _], type(^nil, :binary)), - block_hash: dynamic([_, _], type(^nil, :binary)), - type: "contract", - name: dynamic([smart_contract, _], smart_contract.name), - symbol: nil, - holder_count: nil, - inserted_at: dynamic([_, address], address.inserted_at), - block_number: 0, - icon_url: nil, - token_type: nil, - timestamp: dynamic([_, _], type(^nil, :utc_datetime_usec)), - verified: true, - exchange_rate: nil, - total_supply: nil, - circulating_market_cap: nil, - priority: 0, - is_verified_via_admin_panel: nil - } + contract_search_fields = + search_fields() + |> Map.put(:address_hash, dynamic([smart_contract, _], smart_contract.address_hash)) + |> Map.put(:type, "contract") + |> Map.put(:name, dynamic([smart_contract, _], smart_contract.name)) + |> Map.put(:inserted_at, dynamic([_, address], address.inserted_at)) + |> Map.put(:verified, true) from(smart_contract in SmartContract, left_join: address in Address, @@ -342,26 +345,13 @@ defmodule Explorer.Chain.Search do defp search_address_query(term) do case Chain.string_to_address_hash(term) do {:ok, address_hash} -> - address_search_fields = %{ - address_hash: dynamic([address, _], address.hash), - block_hash: dynamic([_, _], type(^nil, :binary)), - tx_hash: dynamic([_, _], type(^nil, :binary)), - type: "address", - name: dynamic([_, address_name], address_name.name), - symbol: nil, - holder_count: nil, - inserted_at: dynamic([address, _], address.inserted_at), - block_number: 0, - icon_url: nil, - token_type: nil, - timestamp: dynamic([_, _], type(^nil, :utc_datetime_usec)), - verified: dynamic([address, _], address.verified), - exchange_rate: nil, - total_supply: nil, - circulating_market_cap: nil, - priority: 0, - is_verified_via_admin_panel: nil - } + address_search_fields = + search_fields() + |> Map.put(:address_hash, dynamic([address, _], address.hash)) + |> Map.put(:type, "address") + |> Map.put(:name, dynamic([_, address_name], address_name.name)) + |> Map.put(:inserted_at, dynamic([_, address_name], address_name.inserted_at)) + |> Map.put(:verified, dynamic([address, _], address.verified)) from(address in Address, left_join: @@ -382,97 +372,73 @@ defmodule Explorer.Chain.Search do end end + defp valid_tx_or_op_hash?(string_input) do + case Chain.string_to_transaction_hash(string_input) do + {:ok, _tx_hash} -> true + _ -> false + end + end + defp search_tx_query(term) do 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 - ) - - _ -> - nil - end + transaction_search_fields = + search_fields() + |> Map.put(:tx_hash, dynamic([transaction], transaction.hash)) + |> Map.put(:block_hash, dynamic([transaction], transaction.block_hash)) + |> Map.put(:type, "transaction") + |> Map.put(:block_number, dynamic([transaction], transaction.block_number)) + |> Map.put(:inserted_at, dynamic([transaction], transaction.inserted_at)) + |> Map.put(:timestamp, dynamic([transaction], transaction.block_timestamp)) + + from(transaction in Transaction, + where: transaction.hash == ^term, + select: ^transaction_search_fields + ) 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 - end + transaction_search_fields = + search_fields() + |> Map.put(:tx_hash, dynamic([transaction, _], transaction.hash)) + |> Map.put(:block_hash, dynamic([transaction, _], transaction.block_hash)) + |> Map.put(:type, "transaction") + |> Map.put(:block_number, dynamic([transaction, _], transaction.block_number)) + |> Map.put(:inserted_at, dynamic([transaction, _], transaction.inserted_at)) + |> Map.put(:timestamp, dynamic([_, block], block.timestamp)) + + from(transaction in Transaction, + left_join: block in Block, + on: transaction.block_hash == block.hash, + where: transaction.hash == ^term, + select: ^transaction_search_fields + ) end end + defp search_user_operation_query(term) do + user_operation_search_fields = + search_fields() + |> Map.put(:user_operation_hash, dynamic([user_operation, _], user_operation.hash)) + |> Map.put(:block_hash, dynamic([user_operation, _], user_operation.block_hash)) + |> Map.put(:type, "user_operation") + |> Map.put(:inserted_at, dynamic([user_operation, _], user_operation.inserted_at)) + |> Map.put(:block_number, dynamic([user_operation, _], user_operation.block_number)) + |> Map.put(:timestamp, dynamic([_, block], block.timestamp)) + + from(user_operation in UserOperation, + left_join: block in Block, + on: user_operation.block_hash == block.hash, + where: user_operation.hash == ^term, + select: ^user_operation_search_fields + ) + end + defp search_block_query(term) do - block_search_fields = %{ - address_hash: dynamic([_], type(^nil, :binary)), - tx_hash: dynamic([_], type(^nil, :binary)), - block_hash: dynamic([block], block.hash), - type: "block", - name: nil, - symbol: nil, - holder_count: nil, - inserted_at: dynamic([block], block.inserted_at), - block_number: dynamic([block], block.number), - 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 - } + block_search_fields = + search_fields() + |> Map.put(:block_hash, dynamic([block], block.hash)) + |> Map.put(:type, "block") + |> Map.put(:block_number, dynamic([block], block.number)) + |> Map.put(:inserted_at, dynamic([block], block.inserted_at)) + |> Map.put(:timestamp, dynamic([block], block.timestamp)) case Chain.string_to_block_hash(term) do {:ok, block_hash} -> @@ -605,11 +571,23 @@ defmodule Explorer.Chain.Search do end defp merge_address_search_result_with_ens_info([], ens_info) do + search_fields() + |> Map.put(:address_hash, ens_info[:address_hash]) + |> Map.put(:type, "address") + |> Map.put(:ens_info, ens_info) + end + + defp merge_address_search_result_with_ens_info([address], ens_info) do + Map.put(address |> compose_result_checksummed_address_hash(), :ens_info, ens_info) + end + + defp search_fields do %{ - address_hash: ens_info[:address_hash], - block_hash: nil, - tx_hash: nil, - type: "address", + address_hash: dynamic([_], type(^nil, :binary)), + tx_hash: dynamic([_], type(^nil, :binary)), + user_operation_hash: dynamic([_], type(^nil, :binary)), + block_hash: dynamic([_], type(^nil, :binary)), + type: nil, name: nil, symbol: nil, holder_count: nil, @@ -617,18 +595,13 @@ defmodule Explorer.Chain.Search do block_number: 0, icon_url: nil, token_type: nil, - timestamp: nil, - verified: false, + timestamp: dynamic([_, _], type(^nil, :utc_datetime_usec)), + verified: nil, exchange_rate: nil, total_supply: nil, circulating_market_cap: nil, priority: 0, - is_verified_via_admin_panel: nil, - ens_info: ens_info + is_verified_via_admin_panel: nil } end - - defp merge_address_search_result_with_ens_info([address], ens_info) do - Map.put(address |> compose_result_checksummed_address_hash(), :ens_info, ens_info) - end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex b/apps/explorer/lib/explorer/chain/smart_contract_additional_source.ex similarity index 100% rename from apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex rename to apps/explorer/lib/explorer/chain/smart_contract_additional_source.ex diff --git a/apps/explorer/lib/explorer/chain/user_operation.ex b/apps/explorer/lib/explorer/chain/user_operation.ex new file mode 100644 index 000000000000..c9c8dbda5586 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/user_operation.ex @@ -0,0 +1,77 @@ +defmodule Explorer.Chain.UserOperation do + @moduledoc """ + The representation of a user operation for account abstraction (EIP-4337). + """ + + require Logger + + import Ecto.Query, + only: [ + where: 2 + ] + + use Explorer.Schema + alias Explorer.Chain + alias Explorer.Chain.Hash + alias Explorer.Utility.Microservice + + @type api? :: {:api?, true | false} + + @typedoc """ + * `hash` - the hash of User operation. + * `block_number` - the block number, where user operation happened. + * `block_hash` - the block hash, where user operation happened. + """ + + @type t :: %Explorer.Chain.UserOperation{ + hash: Hash.Full.t(), + block_number: Explorer.Chain.Block.block_number() | nil, + block_hash: Hash.Full.t() + } + + @primary_key false + schema "user_operations" do + field(:hash, Hash.Full, primary_key: true) + field(:block_number, :integer) + field(:block_hash, Hash.Full) + + timestamps() + end + + def changeset(%__MODULE__{} = user_operation, attrs) do + user_operation + |> cast(attrs, [ + :hash, + :block_number, + :block_hash + ]) + |> validate_required([:hash, :block_number, :block_hash]) + end + + @doc """ + Converts `t:Explorer.Chain.UserOperation.t/0` `hash` to the `t:Explorer.Chain.UserOperation.t/0` with that `hash`. + """ + @spec hash_to_user_operation(Hash.Full.t(), [api?]) :: + {:ok, __MODULE__.t()} | {:error, :not_found} + def hash_to_user_operation(%Hash{byte_count: unquote(Hash.Full.byte_count())} = hash, options \\ []) + when is_list(options) do + __MODULE__ + |> where(hash: ^hash) + |> Chain.select_repo(options).one() + |> case do + nil -> + {:error, :not_found} + + user_operation -> + {:ok, user_operation} + end + end + + def user_operations_enabled? do + if Microservice.check_enabled(Explorer.MicroserviceInterfaces.AccountAbstraction) == :ok do + true + else + false + end + end +end From afe801dfc2c3ebaee755484e9e7d10a449a324b7 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 26 Jan 2024 16:53:06 +0300 Subject: [PATCH 352/607] Process reviewer comments --- CHANGELOG.md | 5 ----- apps/explorer/lib/explorer/chain/search.ex | 8 ++++---- apps/explorer/lib/explorer/chain/user_operation.ex | 6 +----- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52f7ceb8d633..d2c189a001bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,6 @@ - [#9169](https://github.com/blockscout/blockscout/pull/9169) - Add bridged tokens functionality to master branch - [#9158](https://github.com/blockscout/blockscout/pull/9158) - Increase shared memory for PostgreSQL containers - [#9155](https://github.com/blockscout/blockscout/pull/9155) - Allow bypassing avg block time in proxy implementation re-fetch ttl calculation -- [#9145](https://github.com/blockscout/blockscout/pull/9145) - Proxy for Account abstraction microservice -- [#9131](https://github.com/blockscout/blockscout/pull/9131) - Merge addresses stage with address referencing -- [#9072](https://github.com/blockscout/blockscout/pull/9072) - Add tracing by block logic for geth -- [#9056](https://github.com/blockscout/blockscout/pull/9056) - Noves.fi API proxy -- [#9155](https://github.com/blockscout/blockscout/pull/9155) - Allow bypassing avg block time in proxy implementation re-fetch ttl calculation - [#9148](https://github.com/blockscout/blockscout/pull/9148) - Add `/api/v2/utils/decode-calldata` - [#9145](https://github.com/blockscout/blockscout/pull/9145) - Proxy for Account abstraction microservice - [#9132](https://github.com/blockscout/blockscout/pull/9132) - Fetch token image from CoinGecko diff --git a/apps/explorer/lib/explorer/chain/search.ex b/apps/explorer/lib/explorer/chain/search.ex index 56b94d7f9395..ebfc4baf4741 100644 --- a/apps/explorer/lib/explorer/chain/search.ex +++ b/apps/explorer/lib/explorer/chain/search.ex @@ -98,7 +98,7 @@ defmodule Explorer.Chain.Search do basic_query |> union(^address_query) - valid_tx_or_op_hash?(string) -> + valid_full_hash?(string) -> tx_query = search_tx_query(string) if UserOperation.user_operations_enabled?() do @@ -172,7 +172,7 @@ defmodule Explorer.Chain.Search do |> select_repo(options).all() tx_result = - if valid_tx_or_op_hash?(search_query) do + if valid_full_hash?(search_query) do search_query |> search_tx_query() |> select_repo(options).all() @@ -181,7 +181,7 @@ defmodule Explorer.Chain.Search do end op_result = - if valid_tx_or_op_hash?(search_query) && UserOperation.user_operations_enabled?() do + if valid_full_hash?(search_query) && UserOperation.user_operations_enabled?() do search_query |> search_user_operation_query() |> select_repo(options).all() @@ -372,7 +372,7 @@ defmodule Explorer.Chain.Search do end end - defp valid_tx_or_op_hash?(string_input) do + defp valid_full_hash?(string_input) do case Chain.string_to_transaction_hash(string_input) do {:ok, _tx_hash} -> true _ -> false diff --git a/apps/explorer/lib/explorer/chain/user_operation.ex b/apps/explorer/lib/explorer/chain/user_operation.ex index c9c8dbda5586..75300f6ea5fc 100644 --- a/apps/explorer/lib/explorer/chain/user_operation.ex +++ b/apps/explorer/lib/explorer/chain/user_operation.ex @@ -68,10 +68,6 @@ defmodule Explorer.Chain.UserOperation do end def user_operations_enabled? do - if Microservice.check_enabled(Explorer.MicroserviceInterfaces.AccountAbstraction) == :ok do - true - else - false - end + Microservice.check_enabled(Explorer.MicroserviceInterfaces.AccountAbstraction) == :ok end end From 453dd964ff27f6fdbb720bf7e82f5119b749216d Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 26 Jan 2024 17:29:44 +0300 Subject: [PATCH 353/607] 6.1.0 --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/workflows/prerelease.yml | 2 +- .../publish-docker-image-every-push.yml | 2 +- .../publish-docker-image-for-core.yml | 2 +- .../publish-docker-image-for-eth-goerli.yml | 2 +- .../publish-docker-image-for-eth-sepolia.yml | 2 +- .../publish-docker-image-for-eth.yml | 2 +- .../publish-docker-image-for-filecoin.yml | 2 +- .../publish-docker-image-for-fuse.yml | 2 +- .../publish-docker-image-for-immutable.yml | 2 +- .../publish-docker-image-for-l2-staging.yml | 2 +- .../publish-docker-image-for-lukso.yml | 2 +- .../publish-docker-image-for-optimism.yml | 2 +- .../publish-docker-image-for-polygon-edge.yml | 2 +- .../publish-docker-image-for-rsk.yml | 2 +- .../publish-docker-image-for-stability.yml | 2 +- .../publish-docker-image-for-suave.yml | 2 +- .../publish-docker-image-for-xdai.yml | 2 +- .../publish-docker-image-for-zkevm.yml | 2 +- .../publish-docker-image-for-zksync.yml | 2 +- ...publish-docker-image-staging-on-demand.yml | 2 +- .github/workflows/release-additional.yml | 2 +- .github/workflows/release.yml | 2 +- CHANGELOG.md | 38 +++++++++++++++++++ apps/block_scout_web/mix.exs | 2 +- apps/ethereum_jsonrpc/mix.exs | 2 +- apps/explorer/mix.exs | 2 +- apps/indexer/mix.exs | 2 +- docker-compose/docker-compose.yml | 2 +- docker/Makefile | 2 +- mix.exs | 2 +- rel/config.exs | 2 +- 32 files changed, 69 insertions(+), 31 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index dcd9fc29d2b7..5e94950bad16 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -65,7 +65,7 @@ body: attributes: label: Backend version description: The release version of the backend or branch/commit. - placeholder: v6.0.0 + placeholder: v6.1.0 validations: required: true diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 65ea0100a720..446790b07bce 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -16,7 +16,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 steps: - name: Check out the repo uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-every-push.yml b/.github/workflows/publish-docker-image-every-push.yml index 9f8ca4ef7fe3..cd9e8c60bc7d 100644 --- a/.github/workflows/publish-docker-image-every-push.yml +++ b/.github/workflows/publish-docker-image-every-push.yml @@ -11,7 +11,7 @@ on: env: OTP_VERSION: '25.3.2.8' ELIXIR_VERSION: '1.14.5' - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 jobs: push_to_registry: diff --git a/.github/workflows/publish-docker-image-for-core.yml b/.github/workflows/publish-docker-image-for-core.yml index 61cb6f8189a2..1225cb8ed1b2 100644 --- a/.github/workflows/publish-docker-image-for-core.yml +++ b/.github/workflows/publish-docker-image-for-core.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 DOCKER_CHAIN_NAME: poa steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-eth-goerli.yml b/.github/workflows/publish-docker-image-for-eth-goerli.yml index f381283cf359..def03da86809 100644 --- a/.github/workflows/publish-docker-image-for-eth-goerli.yml +++ b/.github/workflows/publish-docker-image-for-eth-goerli.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 DOCKER_CHAIN_NAME: eth-goerli steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-eth-sepolia.yml b/.github/workflows/publish-docker-image-for-eth-sepolia.yml index b24c2571676e..0e5fae2e0654 100644 --- a/.github/workflows/publish-docker-image-for-eth-sepolia.yml +++ b/.github/workflows/publish-docker-image-for-eth-sepolia.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 DOCKER_CHAIN_NAME: eth-sepolia steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-eth.yml b/.github/workflows/publish-docker-image-for-eth.yml index a663380306f2..c18259c96c74 100644 --- a/.github/workflows/publish-docker-image-for-eth.yml +++ b/.github/workflows/publish-docker-image-for-eth.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 DOCKER_CHAIN_NAME: mainnet steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-filecoin.yml b/.github/workflows/publish-docker-image-for-filecoin.yml index 6e98141461fc..e75e0588bee4 100644 --- a/.github/workflows/publish-docker-image-for-filecoin.yml +++ b/.github/workflows/publish-docker-image-for-filecoin.yml @@ -14,7 +14,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 DOCKER_CHAIN_NAME: filecoin steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-fuse.yml b/.github/workflows/publish-docker-image-for-fuse.yml index a9ec8713e782..05647dda0fbc 100644 --- a/.github/workflows/publish-docker-image-for-fuse.yml +++ b/.github/workflows/publish-docker-image-for-fuse.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 DOCKER_CHAIN_NAME: fuse steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-immutable.yml b/.github/workflows/publish-docker-image-for-immutable.yml index 8bd44e3d1ccd..a9ea33b4bffe 100644 --- a/.github/workflows/publish-docker-image-for-immutable.yml +++ b/.github/workflows/publish-docker-image-for-immutable.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 DOCKER_CHAIN_NAME: immutable steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-l2-staging.yml b/.github/workflows/publish-docker-image-for-l2-staging.yml index 1c5f290907a6..5666915dca53 100644 --- a/.github/workflows/publish-docker-image-for-l2-staging.yml +++ b/.github/workflows/publish-docker-image-for-l2-staging.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 DOCKER_CHAIN_NAME: optimism-l2-advanced steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-lukso.yml b/.github/workflows/publish-docker-image-for-lukso.yml index 10efc69fe25a..4f9d75fb92e2 100644 --- a/.github/workflows/publish-docker-image-for-lukso.yml +++ b/.github/workflows/publish-docker-image-for-lukso.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 DOCKER_CHAIN_NAME: lukso steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-optimism.yml b/.github/workflows/publish-docker-image-for-optimism.yml index f0c24fa58221..f8fac34d30a8 100644 --- a/.github/workflows/publish-docker-image-for-optimism.yml +++ b/.github/workflows/publish-docker-image-for-optimism.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 DOCKER_CHAIN_NAME: optimism steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-polygon-edge.yml b/.github/workflows/publish-docker-image-for-polygon-edge.yml index a7384c4f29fc..59708c1e2729 100644 --- a/.github/workflows/publish-docker-image-for-polygon-edge.yml +++ b/.github/workflows/publish-docker-image-for-polygon-edge.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 DOCKER_CHAIN_NAME: polygon-edge steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-rsk.yml b/.github/workflows/publish-docker-image-for-rsk.yml index 615ad8820bca..ce4bc64cfdf6 100644 --- a/.github/workflows/publish-docker-image-for-rsk.yml +++ b/.github/workflows/publish-docker-image-for-rsk.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 DOCKER_CHAIN_NAME: rsk steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-stability.yml b/.github/workflows/publish-docker-image-for-stability.yml index 2807badc6599..a5b4fcf04bd5 100644 --- a/.github/workflows/publish-docker-image-for-stability.yml +++ b/.github/workflows/publish-docker-image-for-stability.yml @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 DOCKER_CHAIN_NAME: stability steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-suave.yml b/.github/workflows/publish-docker-image-for-suave.yml index 41171e878d29..8fd50785cb65 100644 --- a/.github/workflows/publish-docker-image-for-suave.yml +++ b/.github/workflows/publish-docker-image-for-suave.yml @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 DOCKER_CHAIN_NAME: suave steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-xdai.yml b/.github/workflows/publish-docker-image-for-xdai.yml index b53fe18fc50f..a57595bd3ec1 100644 --- a/.github/workflows/publish-docker-image-for-xdai.yml +++ b/.github/workflows/publish-docker-image-for-xdai.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 DOCKER_CHAIN_NAME: xdai steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-zkevm.yml b/.github/workflows/publish-docker-image-for-zkevm.yml index 0e7072a3940e..7976b83f6e85 100644 --- a/.github/workflows/publish-docker-image-for-zkevm.yml +++ b/.github/workflows/publish-docker-image-for-zkevm.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 DOCKER_CHAIN_NAME: zkevm steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-zksync.yml b/.github/workflows/publish-docker-image-for-zksync.yml index 04cdf569a63d..2c707eb8d867 100644 --- a/.github/workflows/publish-docker-image-for-zksync.yml +++ b/.github/workflows/publish-docker-image-for-zksync.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 DOCKER_CHAIN_NAME: zksync steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-staging-on-demand.yml b/.github/workflows/publish-docker-image-staging-on-demand.yml index 1f5b957c3900..b7e80f059441 100644 --- a/.github/workflows/publish-docker-image-staging-on-demand.yml +++ b/.github/workflows/publish-docker-image-staging-on-demand.yml @@ -12,7 +12,7 @@ on: env: OTP_VERSION: '25.3.2.8' ELIXIR_VERSION: '1.14.5' - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 jobs: push_to_registry: diff --git a/.github/workflows/release-additional.yml b/.github/workflows/release-additional.yml index fdb24253ecbe..57eac202c7a2 100644 --- a/.github/workflows/release-additional.yml +++ b/.github/workflows/release-additional.yml @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 steps: - name: Check out the repo uses: actions/checkout@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 979b1931d3d8..31bee78745ac 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 steps: - name: Check out the repo uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index d2c189a001bd..1a0bd292caea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ ### Features +### Fixes + +### Chore + +
+ Dependencies version bumps + +
+ +## 6.1.0 + +### Features + - [#9189](https://github.com/blockscout/blockscout/pull/9189) - User operations in the search - [#9169](https://github.com/blockscout/blockscout/pull/9169) - Add bridged tokens functionality to master branch - [#9158](https://github.com/blockscout/blockscout/pull/9158) - Increase shared memory for PostgreSQL containers @@ -51,6 +64,31 @@
Dependencies version bumps +- [#9119](https://github.com/blockscout/blockscout/pull/9119) - Bump sass from 1.69.6 to 1.69.7 in /apps/block_scout_web/assets +- [#9126](https://github.com/blockscout/blockscout/pull/9126) - Bump follow-redirects from 1.14.8 to 1.15.4 in /apps/explorer +- [#9116](https://github.com/blockscout/blockscout/pull/9116) - Bump ueberauth from 0.10.5 to 0.10.7 +- [#9118](https://github.com/blockscout/blockscout/pull/9118) - Bump postcss from 8.4.32 to 8.4.33 in /apps/block_scout_web/assets +- [#9161](https://github.com/blockscout/blockscout/pull/9161) - Bump sass-loader from 13.3.3 to 14.0.0 in /apps/block_scout_web/assets +- [#9160](https://github.com/blockscout/blockscout/pull/9160) - Bump copy-webpack-plugin from 11.0.0 to 12.0.1 in /apps/block_scout_web/assets +- [#9165](https://github.com/blockscout/blockscout/pull/9165) - Bump sweetalert2 from 11.10.2 to 11.10.3 in /apps/block_scout_web/assets +- [#9163](https://github.com/blockscout/blockscout/pull/9163) - Bump mini-css-extract-plugin from 2.7.6 to 2.7.7 in /apps/block_scout_web/assets +- [#9159](https://github.com/blockscout/blockscout/pull/9159) - Bump @babel/preset-env from 7.23.7 to 7.23.8 in /apps/block_scout_web/assets +- [#9162](https://github.com/blockscout/blockscout/pull/9162) - Bump style-loader from 3.3.3 to 3.3.4 in /apps/block_scout_web/assets +- [#9164](https://github.com/blockscout/blockscout/pull/9164) - Bump css-loader from 6.8.1 to 6.9.0 in /apps/block_scout_web/assets +- [#8686](https://github.com/blockscout/blockscout/pull/8686) - Bump dialyxir from 1.4.1 to 1.4.2 +- [#8861](https://github.com/blockscout/blockscout/pull/8861) - Bump briefly from 51dfe7f to 4836ba3 +- [#9117](https://github.com/blockscout/blockscout/pull/9117) - Bump credo from 1.7.1 to 1.7.3 +- [#9222](https://github.com/blockscout/blockscout/pull/9222) - Bump dialyxir from 1.4.2 to 1.4.3 +- [#9219](https://github.com/blockscout/blockscout/pull/9219) - Bump sass from 1.69.7 to 1.70.0 in /apps/block_scout_web/assets +- [#9224](https://github.com/blockscout/blockscout/pull/9224) - Bump ex_cldr_numbers from 2.32.3 to 2.32.4 +- [#9220](https://github.com/blockscout/blockscout/pull/9220) - Bump copy-webpack-plugin from 12.0.1 to 12.0.2 in /apps/block_scout_web/assets +- [#9216](https://github.com/blockscout/blockscout/pull/9216) - Bump core-js from 3.35.0 to 3.35.1 in /apps/block_scout_web/assets +- [#9218](https://github.com/blockscout/blockscout/pull/9218) - Bump postcss-loader from 7.3.4 to 8.0.0 in /apps/block_scout_web/assets +- [#9223](https://github.com/blockscout/blockscout/pull/9223) - Bump plug_cowboy from 2.6.1 to 2.6.2 +- [#9217](https://github.com/blockscout/blockscout/pull/9217) - Bump css-loader from 6.9.0 to 6.9.1 in /apps/block_scout_web/assets +- [#9215](https://github.com/blockscout/blockscout/pull/9215) - Bump css-minimizer-webpack-plugin from 5.0.1 to 6.0.0 in /apps/block_scout_web/assets +- [#9221](https://github.com/blockscout/blockscout/pull/9221) - Bump autoprefixer from 10.4.16 to 10.4.17 in /apps/block_scout_web/assets +
## 6.0.0 diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index 741558064926..510e06431fa8 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -23,7 +23,7 @@ defmodule BlockScoutWeb.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.0.0", + version: "6.1.0", xref: [exclude: [Explorer.Chain.Zkevm.Reader]] ] end diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index 2446c3e2e28b..2b10d438ef6d 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -23,7 +23,7 @@ defmodule EthereumJsonrpc.MixProject do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.0.0" + version: "6.1.0" ] end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 5f98f045ff91..ff106dc4e1d3 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -24,7 +24,7 @@ defmodule Explorer.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.0.0", + version: "6.1.0", xref: [exclude: [BlockScoutWeb.WebRouter.Helpers]] ] end diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs index 7e349261ccbd..2303df05fc7b 100644 --- a/apps/indexer/mix.exs +++ b/apps/indexer/mix.exs @@ -14,7 +14,7 @@ defmodule Indexer.MixProject do elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", start_permanent: Mix.env() == :prod, - version: "6.0.0" + version: "6.1.0" ] end diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index b00502a5a187..fab930386386 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -34,7 +34,7 @@ services: CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED: "" CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL: "" ADMIN_PANEL_ENABLED: "" - RELEASE_VERSION: 6.0.0 + RELEASE_VERSION: 6.1.0 links: - db:database environment: diff --git a/docker/Makefile b/docker/Makefile index 63bcd32de63b..6a9365e9136a 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -10,7 +10,7 @@ STATS_CONTAINER_NAME := stats STATS_DB_CONTAINER_NAME := stats-postgres PROXY_CONTAINER_NAME := proxy PG_CONTAINER_NAME := postgres -RELEASE_VERSION ?= '6.0.0' +RELEASE_VERSION ?= '6.1.0' TAG := $(RELEASE_VERSION)-commit-$(shell git log -1 --pretty=format:"%h") STABLE_TAG := $(RELEASE_VERSION) diff --git a/mix.exs b/mix.exs index ac067de5bd85..baf86033be79 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule BlockScout.Mixfile do [ # app: :block_scout, # aliases: aliases(config_env()), - version: "6.0.0", + version: "6.1.0", apps_path: "apps", deps: deps(), dialyzer: dialyzer(), diff --git a/rel/config.exs b/rel/config.exs index b7c3f38132d3..ffb5fbc4ae7b 100644 --- a/rel/config.exs +++ b/rel/config.exs @@ -71,7 +71,7 @@ end # will be used by default release :blockscout do - set version: "6.0.0-beta" + set version: "6.1.0-beta" set applications: [ :runtime_tools, block_scout_web: :permanent, From c5f1de1acc71707f7a822115ef797260511bfdb3 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 26 Jan 2024 19:23:03 +0300 Subject: [PATCH 354/607] Return OTP_VERSION: 25.2.1 for prerelease workflow --- .github/workflows/prerelease.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 446790b07bce..8bec7f2c9252 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -8,7 +8,7 @@ on: required: true env: - OTP_VERSION: '25.3.2.8' + OTP_VERSION: '25.2.1' ELIXIR_VERSION: '1.14.5' jobs: From 448810c2b7d9069fb05b71a4a767290dd9c0b935 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 26 Jan 2024 21:28:34 +0300 Subject: [PATCH 355/607] Rollback Dockerfile Elixir/Erlanfg versions update --- .github/workflows/prerelease.yml | 2 +- docker/Dockerfile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 8bec7f2c9252..446790b07bce 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -8,7 +8,7 @@ on: required: true env: - OTP_VERSION: '25.2.1' + OTP_VERSION: '25.3.2.8' ELIXIR_VERSION: '1.14.5' jobs: diff --git a/docker/Dockerfile b/docker/Dockerfile index 205a0cdfe4c6..bc8bb581b4b8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM hexpm/elixir:1.14.5-erlang-25.3.2.8-alpine-3.18.4 AS builder +FROM hexpm/elixir:1.14.5-erlang-24.2.2-alpine-3.18.2 AS builder WORKDIR /app @@ -64,7 +64,7 @@ RUN mkdir -p /opt/release \ && mv _build/${MIX_ENV}/rel/blockscout /opt/release ############################################################## -FROM hexpm/elixir:1.14.5-erlang-25.3.2.8-alpine-3.18.4 +FROM hexpm/elixir:1.14.5-erlang-24.2.2-alpine-3.18.2 ARG RELEASE_VERSION ENV RELEASE_VERSION=${RELEASE_VERSION} From 139cfc926b1cffe11c539f1d2c890863d816906e Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Mon, 29 Jan 2024 01:26:31 +0300 Subject: [PATCH 356/607] Tx summary endpoint: Decode logs via sig provider as fallback; send 500 HTTP code on error --- .github/workflows/config.yml | 26 +++++++++---------- CHANGELOG.md | 1 + .../controllers/api/v2/fallback_controller.ex | 2 +- .../api/v2/transaction_controller.ex | 9 ++++--- .../transaction_interpretation.ex | 14 +++++++--- 5 files changed, 30 insertions(+), 22 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 71d16ae8ce70..b8df611ab523 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -75,7 +75,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_38-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ 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_38-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ 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_38-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ 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_38-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ 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_38-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ 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_38-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ 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_38-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ 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_38-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ 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_38-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ 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_38-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ 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_38-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ 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_38-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ 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_38-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a0bd292caea..c59befb5b0d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ ### Fixes +- [#9275](https://github.com/blockscout/blockscout/pull/9275) - Tx summary endpoint fixes - [#9261](https://github.com/blockscout/blockscout/pull/9261) - Fix pending transactions sanitizer - [#9253](https://github.com/blockscout/blockscout/pull/9253) - Don't fetch first trace for pending transactions - [#9241](https://github.com/blockscout/blockscout/pull/9241) - Fix log decoding bug diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex index d08a4705fb92..52fca8b435af 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex @@ -270,7 +270,7 @@ defmodule BlockScoutWeb.API.V2.FallbackController do def call(conn, {:tx_interpreter_enabled, false}) do conn - |> put_status(404) + |> put_status(:forbidden) |> put_view(ApiView) |> render(:message, %{message: @tx_interpreter_service_disabled}) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex index ff62beb9f5d8..ee5088bf5e4d 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -377,14 +377,15 @@ defmodule BlockScoutWeb.API.V2.TransactionController do def summary(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do with {:tx_interpreter_enabled, true} <- {:tx_interpreter_enabled, TransactionInterpretationService.enabled?()}, {:ok, transaction, _transaction_hash} <- validate_transaction(transaction_hash_string, params) do - response = + {response, code} = case TransactionInterpretationService.interpret(transaction) do - {:ok, response} -> response - {:error, %Jason.DecodeError{}} -> %{error: "Error while tx interpreter response decoding"} - {:error, error} -> %{error: error} + {:ok, response} -> {response, 200} + {:error, %Jason.DecodeError{}} -> {%{error: "Error while tx interpreter response decoding"}, 500} + {{:error, error}, code} -> {%{error: error}, code} end conn + |> put_status(code) |> json(response) end end diff --git a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex index 4b6d7edc7477..d5e388cefa1c 100644 --- a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex +++ b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex @@ -17,7 +17,10 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do @api_true api?: true @items_limit 50 - @spec interpret(Transaction.t()) :: {:error, :disabled | binary | Jason.DecodeError.t()} | {:ok, any} + @spec interpret(Transaction.t()) :: + {{:error, :disabled | binary()}, integer()} + | {:error, Jason.DecodeError.t()} + | {:ok, any} def interpret(transaction) do if enabled?() do url = interpret_url() @@ -26,7 +29,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do http_post_request(url, body) else - {:error, :disabled} + {{:error, :disabled}, 403} end end @@ -53,10 +56,13 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do end) Logger.configure(truncate: old_truncate) - {:error, @request_error_msg} + {{:error, @request_error_msg}, http_response_code(error)} end end + defp http_response_code({:ok, %Response{status_code: status_code}}), do: status_code + defp http_response_code(_), do: 500 + defp config do Application.get_env(:block_scout_web, __MODULE__) end @@ -141,7 +147,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do |> Chain.transaction_to_logs(full_options) |> Enum.take(@items_limit) - decoded_logs = TransactionView.decode_logs(logs, true) + decoded_logs = TransactionView.decode_logs(logs, false) logs |> Enum.zip(decoded_logs) From 799d3243ec0f9f6d003cca9fcf8784305375c36b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:40:28 +0000 Subject: [PATCH 357/607] Bump @amplitude/analytics-browser in /apps/block_scout_web/assets Bumps [@amplitude/analytics-browser](https://github.com/amplitude/Amplitude-TypeScript) from 2.3.8 to 2.4.0. - [Release notes](https://github.com/amplitude/Amplitude-TypeScript/releases) - [Commits](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@2.3.8...@amplitude/analytics-browser@2.4.0) --- updated-dependencies: - dependency-name: "@amplitude/analytics-browser" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 130 +++++++++--------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 66 insertions(+), 66 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index d1c9b8ef82dc..d55aa2531f87 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -7,7 +7,7 @@ "name": "blockscout", "license": "GPL-3.0", "dependencies": { - "@amplitude/analytics-browser": "^2.3.8", + "@amplitude/analytics-browser": "^2.4.0", "@fortawesome/fontawesome-free": "^6.5.1", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", @@ -116,15 +116,15 @@ } }, "node_modules/@amplitude/analytics-browser": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.8.tgz", - "integrity": "sha512-K+12aAVJPzAtWIi8Ok5Q5dvg7v7IF4G0cI8PW0COWo3uTyY103r45OcpgrpRVpVAr+41d1eiMo36jqOke89uPA==", - "dependencies": { - "@amplitude/analytics-client-common": "^2.0.10", - "@amplitude/analytics-core": "^2.1.3", - "@amplitude/analytics-types": "^2.3.1", - "@amplitude/plugin-page-view-tracking-browser": "^2.0.18", - "@amplitude/plugin-web-attribution-browser": "^2.0.18", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.4.0.tgz", + "integrity": "sha512-QHEHCxGiEYeYv052MCnRWmPVD56micO65kODsR5hqWMkJQcemql8gvZfwYPt0rDGxxmvTxEN95uxHbbV2p3bpw==", + "dependencies": { + "@amplitude/analytics-client-common": "^2.0.11", + "@amplitude/analytics-core": "^2.2.0", + "@amplitude/analytics-types": "^2.4.0", + "@amplitude/plugin-page-view-tracking-browser": "^2.1.0", + "@amplitude/plugin-web-attribution-browser": "^2.1.0", "tslib": "^2.4.1" } }, @@ -134,13 +134,13 @@ "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" }, "node_modules/@amplitude/analytics-client-common": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.10.tgz", - "integrity": "sha512-IaERGgBN3dmCGFbFd7SFTpTBguJIQzE/uDK44KEnLj0qw9wdoTxpLhoXQpqe5WKWsr46eONL9ROCJybHs4Efnw==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.11.tgz", + "integrity": "sha512-f2zzo7Sk1hz8/WT7kPB6HZqAyHJrxMzw9nTqbLoRsaG9xm4YOJ0WCwtlu42RVRqIoWR89RTmIkqIjqimqMaHEQ==", "dependencies": { "@amplitude/analytics-connector": "^1.4.8", - "@amplitude/analytics-core": "^2.1.3", - "@amplitude/analytics-types": "^2.3.1", + "@amplitude/analytics-core": "^2.2.0", + "@amplitude/analytics-types": "^2.4.0", "tslib": "^2.4.1" } }, @@ -155,11 +155,11 @@ "integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g==" }, "node_modules/@amplitude/analytics-core": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.3.tgz", - "integrity": "sha512-WHXf9g33t63jYy4a/1uOpq/zHPMfEj5N2HHgJrg7Eu7v4w3kOWtPSMPBAllzFWxC5Ay5HeR9n0hqlJG0yffQWg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.2.0.tgz", + "integrity": "sha512-JVx1chKa/sfqpBNEZn6jaZZV3DW/6cOoJxx8b5oqHIn+5HXizEOV85aLFY0rrtZ/uR/HrdffxrMKzr/uBFlV+A==", "dependencies": { - "@amplitude/analytics-types": "^2.3.1", + "@amplitude/analytics-types": "^2.4.0", "tslib": "^2.4.1" } }, @@ -169,17 +169,17 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@amplitude/analytics-types": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-2.3.1.tgz", - "integrity": "sha512-yojBG20qvph0rpCJKb4i/FJa+otqLINEwv//hfzvjnCOcPPyS0YscI8oiRBM0rG7kZIDgaL9a6jPwkqK4ACmcw==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-2.4.0.tgz", + "integrity": "sha512-GVj9W4X3XMVyGfqXdES2vFU8pqTIHvihj/vNOjOrwYHVdia3GjlcGl77GXuERCIwr52MoiUVTGXmXn3adf+A+Q==" }, "node_modules/@amplitude/plugin-page-view-tracking-browser": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.18.tgz", - "integrity": "sha512-s7PkGOgrx6U06/emzM8k+KRGDuyP9Z2L4OyGdeQwJcURJjiZDVQsmKlTZ5/SeGvxHYgq/4QJYUmMSmzByiGTCA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.1.0.tgz", + "integrity": "sha512-iAZzLcmk6RGFf0emihBLtVBX2mZwmW6pyQOHQT7dLxCE8+L/Sgj2zhMyoRomL3RBoUUnXfu03ET3L9fv30LMGA==", "dependencies": { - "@amplitude/analytics-client-common": "^2.0.10", - "@amplitude/analytics-types": "^2.3.1", + "@amplitude/analytics-client-common": "^2.0.11", + "@amplitude/analytics-types": "^2.4.0", "tslib": "^2.4.1" } }, @@ -189,13 +189,13 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@amplitude/plugin-web-attribution-browser": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.18.tgz", - "integrity": "sha512-mPlXu0fEYCCXhT6WpNJoM0FYkp+HIEm7L+KBEa2IHd3GD3+mh2AVDkZmgXLl3LKb++HY8mCiqC5/NcJ2AzTNhA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.1.0.tgz", + "integrity": "sha512-2b/LTZifOLvx8XTVPfyaxh39uX5ANNbvypByk9zywprucbKUj+1hzycgP57lN8Xw2iuqRfypQz7V2YGdR9w4MQ==", "dependencies": { - "@amplitude/analytics-client-common": "^2.0.10", - "@amplitude/analytics-core": "^2.1.3", - "@amplitude/analytics-types": "^2.3.1", + "@amplitude/analytics-client-common": "^2.0.11", + "@amplitude/analytics-core": "^2.2.0", + "@amplitude/analytics-types": "^2.4.0", "tslib": "^2.4.1" } }, @@ -17881,15 +17881,15 @@ "dev": true }, "@amplitude/analytics-browser": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.8.tgz", - "integrity": "sha512-K+12aAVJPzAtWIi8Ok5Q5dvg7v7IF4G0cI8PW0COWo3uTyY103r45OcpgrpRVpVAr+41d1eiMo36jqOke89uPA==", - "requires": { - "@amplitude/analytics-client-common": "^2.0.10", - "@amplitude/analytics-core": "^2.1.3", - "@amplitude/analytics-types": "^2.3.1", - "@amplitude/plugin-page-view-tracking-browser": "^2.0.18", - "@amplitude/plugin-web-attribution-browser": "^2.0.18", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.4.0.tgz", + "integrity": "sha512-QHEHCxGiEYeYv052MCnRWmPVD56micO65kODsR5hqWMkJQcemql8gvZfwYPt0rDGxxmvTxEN95uxHbbV2p3bpw==", + "requires": { + "@amplitude/analytics-client-common": "^2.0.11", + "@amplitude/analytics-core": "^2.2.0", + "@amplitude/analytics-types": "^2.4.0", + "@amplitude/plugin-page-view-tracking-browser": "^2.1.0", + "@amplitude/plugin-web-attribution-browser": "^2.1.0", "tslib": "^2.4.1" }, "dependencies": { @@ -17901,13 +17901,13 @@ } }, "@amplitude/analytics-client-common": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.10.tgz", - "integrity": "sha512-IaERGgBN3dmCGFbFd7SFTpTBguJIQzE/uDK44KEnLj0qw9wdoTxpLhoXQpqe5WKWsr46eONL9ROCJybHs4Efnw==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.11.tgz", + "integrity": "sha512-f2zzo7Sk1hz8/WT7kPB6HZqAyHJrxMzw9nTqbLoRsaG9xm4YOJ0WCwtlu42RVRqIoWR89RTmIkqIjqimqMaHEQ==", "requires": { "@amplitude/analytics-connector": "^1.4.8", - "@amplitude/analytics-core": "^2.1.3", - "@amplitude/analytics-types": "^2.3.1", + "@amplitude/analytics-core": "^2.2.0", + "@amplitude/analytics-types": "^2.4.0", "tslib": "^2.4.1" }, "dependencies": { @@ -17924,11 +17924,11 @@ "integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g==" }, "@amplitude/analytics-core": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.3.tgz", - "integrity": "sha512-WHXf9g33t63jYy4a/1uOpq/zHPMfEj5N2HHgJrg7Eu7v4w3kOWtPSMPBAllzFWxC5Ay5HeR9n0hqlJG0yffQWg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.2.0.tgz", + "integrity": "sha512-JVx1chKa/sfqpBNEZn6jaZZV3DW/6cOoJxx8b5oqHIn+5HXizEOV85aLFY0rrtZ/uR/HrdffxrMKzr/uBFlV+A==", "requires": { - "@amplitude/analytics-types": "^2.3.1", + "@amplitude/analytics-types": "^2.4.0", "tslib": "^2.4.1" }, "dependencies": { @@ -17940,17 +17940,17 @@ } }, "@amplitude/analytics-types": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-2.3.1.tgz", - "integrity": "sha512-yojBG20qvph0rpCJKb4i/FJa+otqLINEwv//hfzvjnCOcPPyS0YscI8oiRBM0rG7kZIDgaL9a6jPwkqK4ACmcw==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-2.4.0.tgz", + "integrity": "sha512-GVj9W4X3XMVyGfqXdES2vFU8pqTIHvihj/vNOjOrwYHVdia3GjlcGl77GXuERCIwr52MoiUVTGXmXn3adf+A+Q==" }, "@amplitude/plugin-page-view-tracking-browser": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.18.tgz", - "integrity": "sha512-s7PkGOgrx6U06/emzM8k+KRGDuyP9Z2L4OyGdeQwJcURJjiZDVQsmKlTZ5/SeGvxHYgq/4QJYUmMSmzByiGTCA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.1.0.tgz", + "integrity": "sha512-iAZzLcmk6RGFf0emihBLtVBX2mZwmW6pyQOHQT7dLxCE8+L/Sgj2zhMyoRomL3RBoUUnXfu03ET3L9fv30LMGA==", "requires": { - "@amplitude/analytics-client-common": "^2.0.10", - "@amplitude/analytics-types": "^2.3.1", + "@amplitude/analytics-client-common": "^2.0.11", + "@amplitude/analytics-types": "^2.4.0", "tslib": "^2.4.1" }, "dependencies": { @@ -17962,13 +17962,13 @@ } }, "@amplitude/plugin-web-attribution-browser": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.18.tgz", - "integrity": "sha512-mPlXu0fEYCCXhT6WpNJoM0FYkp+HIEm7L+KBEa2IHd3GD3+mh2AVDkZmgXLl3LKb++HY8mCiqC5/NcJ2AzTNhA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.1.0.tgz", + "integrity": "sha512-2b/LTZifOLvx8XTVPfyaxh39uX5ANNbvypByk9zywprucbKUj+1hzycgP57lN8Xw2iuqRfypQz7V2YGdR9w4MQ==", "requires": { - "@amplitude/analytics-client-common": "^2.0.10", - "@amplitude/analytics-core": "^2.1.3", - "@amplitude/analytics-types": "^2.3.1", + "@amplitude/analytics-client-common": "^2.0.11", + "@amplitude/analytics-core": "^2.2.0", + "@amplitude/analytics-types": "^2.4.0", "tslib": "^2.4.1" }, "dependencies": { diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index e308946e150f..521e3484b4a6 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^6.5.1", - "@amplitude/analytics-browser": "^2.3.8", + "@amplitude/analytics-browser": "^2.4.0", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", "assert": "^2.1.0", From 914ecb0fbe4f9187fa5c4f18bd3ec7f832ad25f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:41:13 +0000 Subject: [PATCH 358/607] Bump @babel/preset-env in /apps/block_scout_web/assets Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.23.8 to 7.23.9. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.9/packages/babel-preset-env) --- updated-dependencies: - dependency-name: "@babel/preset-env" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 134 +++++++++--------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 68 insertions(+), 68 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index d1c9b8ef82dc..e0b8e7a4f12c 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -72,7 +72,7 @@ }, "devDependencies": { "@babel/core": "^7.23.7", - "@babel/preset-env": "^7.23.8", + "@babel/preset-env": "^7.23.9", "autoprefixer": "^10.4.17", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", @@ -991,9 +991,9 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.7.tgz", - "integrity": "sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz", + "integrity": "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -1349,9 +1349,9 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", - "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.9.tgz", + "integrity": "sha512-KDlPRM6sLo4o1FkiSlXoAa8edLXFsKKIda779fbLrvmeuc3itnjCtaO6RrtoaANsIJANj+Vk1zqbZIMhkCAHVw==", "dev": true, "dependencies": { "@babel/helper-hoist-variables": "^7.22.5", @@ -1779,9 +1779,9 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.8", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.8.tgz", - "integrity": "sha512-lFlpmkApLkEP6woIKprO6DO60RImpatTQKtz4sUcDjVcK8M8mQ4sZsuxaTMNOZf0sqAq/ReYW1ZBHnOQwKpLWA==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.9.tgz", + "integrity": "sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A==", "dev": true, "dependencies": { "@babel/compat-data": "^7.23.5", @@ -1811,7 +1811,7 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.7", + "@babel/plugin-transform-async-generator-functions": "^7.23.9", "@babel/plugin-transform-async-to-generator": "^7.23.3", "@babel/plugin-transform-block-scoped-functions": "^7.23.3", "@babel/plugin-transform-block-scoping": "^7.23.4", @@ -1833,7 +1833,7 @@ "@babel/plugin-transform-member-expression-literals": "^7.23.3", "@babel/plugin-transform-modules-amd": "^7.23.3", "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-modules-systemjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.9", "@babel/plugin-transform-modules-umd": "^7.23.3", "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", "@babel/plugin-transform-new-target": "^7.23.3", @@ -1859,9 +1859,9 @@ "@babel/plugin-transform-unicode-regex": "^7.23.3", "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.7", - "babel-plugin-polyfill-corejs3": "^0.8.7", - "babel-plugin-polyfill-regenerator": "^0.5.4", + "babel-plugin-polyfill-corejs2": "^0.4.8", + "babel-plugin-polyfill-corejs3": "^0.9.0", + "babel-plugin-polyfill-regenerator": "^0.5.5", "core-js-compat": "^3.31.0", "semver": "^6.3.1" }, @@ -1873,9 +1873,9 @@ } }, "node_modules/@babel/preset-env/node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", - "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", + "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", "dev": true, "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", @@ -1889,13 +1889,13 @@ } }, "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz", - "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==", + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz", + "integrity": "sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==", "dev": true, "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.4", + "@babel/helper-define-polyfill-provider": "^0.5.0", "semver": "^6.3.1" }, "peerDependencies": { @@ -1903,12 +1903,12 @@ } }, "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz", - "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz", + "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.4" + "@babel/helper-define-polyfill-provider": "^0.5.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -4863,22 +4863,22 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", - "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz", + "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.4", - "core-js-compat": "^3.33.1" + "@babel/helper-define-polyfill-provider": "^0.5.0", + "core-js-compat": "^3.34.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", - "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", + "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", "dev": true, "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", @@ -18538,9 +18538,9 @@ } }, "@babel/plugin-transform-async-generator-functions": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.7.tgz", - "integrity": "sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz", + "integrity": "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==", "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.22.20", @@ -18764,9 +18764,9 @@ } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", - "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.9.tgz", + "integrity": "sha512-KDlPRM6sLo4o1FkiSlXoAa8edLXFsKKIda779fbLrvmeuc3itnjCtaO6RrtoaANsIJANj+Vk1zqbZIMhkCAHVw==", "dev": true, "requires": { "@babel/helper-hoist-variables": "^7.22.5", @@ -19037,9 +19037,9 @@ } }, "@babel/preset-env": { - "version": "7.23.8", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.8.tgz", - "integrity": "sha512-lFlpmkApLkEP6woIKprO6DO60RImpatTQKtz4sUcDjVcK8M8mQ4sZsuxaTMNOZf0sqAq/ReYW1ZBHnOQwKpLWA==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.9.tgz", + "integrity": "sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A==", "dev": true, "requires": { "@babel/compat-data": "^7.23.5", @@ -19069,7 +19069,7 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.7", + "@babel/plugin-transform-async-generator-functions": "^7.23.9", "@babel/plugin-transform-async-to-generator": "^7.23.3", "@babel/plugin-transform-block-scoped-functions": "^7.23.3", "@babel/plugin-transform-block-scoping": "^7.23.4", @@ -19091,7 +19091,7 @@ "@babel/plugin-transform-member-expression-literals": "^7.23.3", "@babel/plugin-transform-modules-amd": "^7.23.3", "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-modules-systemjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.9", "@babel/plugin-transform-modules-umd": "^7.23.3", "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", "@babel/plugin-transform-new-target": "^7.23.3", @@ -19117,17 +19117,17 @@ "@babel/plugin-transform-unicode-regex": "^7.23.3", "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.7", - "babel-plugin-polyfill-corejs3": "^0.8.7", - "babel-plugin-polyfill-regenerator": "^0.5.4", + "babel-plugin-polyfill-corejs2": "^0.4.8", + "babel-plugin-polyfill-corejs3": "^0.9.0", + "babel-plugin-polyfill-regenerator": "^0.5.5", "core-js-compat": "^3.31.0", "semver": "^6.3.1" }, "dependencies": { "@babel/helper-define-polyfill-provider": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", - "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", + "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", "dev": true, "requires": { "@babel/helper-compilation-targets": "^7.22.6", @@ -19138,23 +19138,23 @@ } }, "babel-plugin-polyfill-corejs2": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz", - "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==", + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz", + "integrity": "sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==", "dev": true, "requires": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.4", + "@babel/helper-define-polyfill-provider": "^0.5.0", "semver": "^6.3.1" } }, "babel-plugin-polyfill-regenerator": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz", - "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz", + "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.4.4" + "@babel/helper-define-polyfill-provider": "^0.5.0" } } } @@ -21421,19 +21421,19 @@ } }, "babel-plugin-polyfill-corejs3": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", - "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz", + "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.4.4", - "core-js-compat": "^3.33.1" + "@babel/helper-define-polyfill-provider": "^0.5.0", + "core-js-compat": "^3.34.0" }, "dependencies": { "@babel/helper-define-polyfill-provider": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", - "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", + "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", "dev": true, "requires": { "@babel/helper-compilation-targets": "^7.22.6", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index e308946e150f..ccb3b676e7cc 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -84,7 +84,7 @@ }, "devDependencies": { "@babel/core": "^7.23.7", - "@babel/preset-env": "^7.23.8", + "@babel/preset-env": "^7.23.9", "autoprefixer": "^10.4.17", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", From 170052745deef5078165e9e09f5f03276eb6ccd4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:54:07 +0000 Subject: [PATCH 359/607] Bump solc from 0.8.23 to 0.8.24 in /apps/explorer Bumps [solc](https://github.com/ethereum/solc-js) from 0.8.23 to 0.8.24. - [Commits](https://github.com/ethereum/solc-js/compare/v0.8.23...v0.8.24) --- updated-dependencies: - dependency-name: solc dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/explorer/package-lock.json | 35 +++++++-------------------------- apps/explorer/package.json | 2 +- 2 files changed, 8 insertions(+), 29 deletions(-) diff --git a/apps/explorer/package-lock.json b/apps/explorer/package-lock.json index 88f5dd663846..6d5940bb3258 100644 --- a/apps/explorer/package-lock.json +++ b/apps/explorer/package-lock.json @@ -7,7 +7,7 @@ "name": "blockscout", "license": "GPL-3.0", "dependencies": { - "solc": "0.8.23" + "solc": "0.8.24" }, "engines": { "node": "18.x", @@ -59,20 +59,6 @@ "node": ">= 0.10.0" } }, - "node_modules/n": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/n/-/n-9.2.0.tgz", - "integrity": "sha512-R8mFN2OWwNVc+r1f9fDzcT34DnDwUIHskrpTesZ6SdluaXBBnRtTu5tlfaSPloBi1Z/eGJoPO9nhyawWPad5UQ==", - "os": [ - "!win32" - ], - "bin": { - "n": "bin/n" - }, - "engines": { - "node": "*" - } - }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -90,16 +76,15 @@ } }, "node_modules/solc": { - "version": "0.8.23", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.23.tgz", - "integrity": "sha512-uqe69kFWfJc3cKdxj+Eg9CdW1CP3PLZDPeyJStQVWL8Q9jjjKD0VuRAKBFR8mrWiq5A7gJqERxJFYJsklrVsfA==", + "version": "0.8.24", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.24.tgz", + "integrity": "sha512-G5yUqjTUPc8Np74sCFwfsevhBPlUifUOfhYrgyu6CmYlC6feSw0YS6eZW47XDT23k3JYdKx5nJ+Q7whCEmNcoA==", "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", - "n": "^9.2.0", "semver": "^5.5.0", "tmp": "0.0.33" }, @@ -148,11 +133,6 @@ "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=" }, - "n": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/n/-/n-9.2.0.tgz", - "integrity": "sha512-R8mFN2OWwNVc+r1f9fDzcT34DnDwUIHskrpTesZ6SdluaXBBnRtTu5tlfaSPloBi1Z/eGJoPO9nhyawWPad5UQ==" - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -164,16 +144,15 @@ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" }, "solc": { - "version": "0.8.23", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.23.tgz", - "integrity": "sha512-uqe69kFWfJc3cKdxj+Eg9CdW1CP3PLZDPeyJStQVWL8Q9jjjKD0VuRAKBFR8mrWiq5A7gJqERxJFYJsklrVsfA==", + "version": "0.8.24", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.24.tgz", + "integrity": "sha512-G5yUqjTUPc8Np74sCFwfsevhBPlUifUOfhYrgyu6CmYlC6feSw0YS6eZW47XDT23k3JYdKx5nJ+Q7whCEmNcoA==", "requires": { "command-exists": "^1.2.8", "commander": "^8.1.0", "follow-redirects": "^1.12.1", "js-sha3": "0.8.0", "memorystream": "^0.3.1", - "n": "^9.2.0", "semver": "^5.5.0", "tmp": "0.0.33" } diff --git a/apps/explorer/package.json b/apps/explorer/package.json index 01333da43558..47db342df7aa 100644 --- a/apps/explorer/package.json +++ b/apps/explorer/package.json @@ -13,6 +13,6 @@ }, "scripts": {}, "dependencies": { - "solc": "0.8.23" + "solc": "0.8.24" } } From 93bf13b57c0b2463d190610947de9123af7bde4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:58:22 +0000 Subject: [PATCH 360/607] Bump ex_doc from 0.31.0 to 0.31.1 Bumps [ex_doc](https://github.com/elixir-lang/ex_doc) from 0.31.0 to 0.31.1. - [Release notes](https://github.com/elixir-lang/ex_doc/releases) - [Changelog](https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md) - [Commits](https://github.com/elixir-lang/ex_doc/compare/v0.31.0...v0.31.1) --- updated-dependencies: - dependency-name: ex_doc dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 79a27ea3fb66..f3e2ae72486b 100644 --- a/mix.lock +++ b/mix.lock @@ -47,7 +47,7 @@ "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.10.2", "c8dbb3324ca35cea3679a96f4c774cdf4bdd425786a44c4f52aacb57a0cee446", [:mix], [{:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "0cc2124eccffa5438045c2504dd3365490b64065131f58ecc27f344db1edb4b8"}, "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.32.4", "5562148dfc631b04712983975093d2aac29df30b3bf2f7257e0c94b85b72e91b", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "6fd5a82f0785418fa8b698c0be2b1845dff92b77f1b3172c763d37868fb503d2"}, "ex_cldr_units": {:hex, :ex_cldr_units, "3.16.4", "fee054e9ebed40ef05cbb405cb0c7e7c9fda201f8f03ec0d1e54e879af413246", [:mix], [{:cldr_utils, "~> 2.24", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.31", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7c15c6357dd555a5bc6c72fdeb243e4706a04065753dbd2f40150f062ca996c7"}, - "ex_doc": {:hex, :ex_doc, "0.31.0", "06eb1dfd787445d9cab9a45088405593dd3bb7fe99e097eaa71f37ba80c7a676", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5350cafa6b7f77bdd107aa2199fe277acf29d739aba5aee7e865fc680c62a110"}, + "ex_doc": {:hex, :ex_doc, "0.31.1", "8a2355ac42b1cc7b2379da9e40243f2670143721dd50748bf6c3b1184dae2089", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "3178c3a407c557d8343479e1ff117a96fd31bafe52a039079593fb0524ef61b0"}, "ex_json_schema": {:hex, :ex_json_schema, "0.10.2", "7c4b8c1481fdeb1741e2ce66223976edfb9bccebc8014f6aec35d4efe964fb71", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "37f43be60f8407659d4d0155a7e45e7f406dab1f827051d3d35858a709baf6a6"}, "ex_keccak": {:hex, :ex_keccak, "0.7.3", "33298f97159f6b0acd28f6e96ce5ea975a0f4a19f85fe615b4f4579b88b24d06", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6.1", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "4c5e6d9d5f77b64ab48769a0166a9814180d40ced68ed74ce60a5174ab55b3fc"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, From 76a52d236e8c79f10b294530c615a66f0fb67e82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 19:05:32 +0000 Subject: [PATCH 361/607] Bump floki from 0.35.2 to 0.35.3 Bumps [floki](https://github.com/philss/floki) from 0.35.2 to 0.35.3. - [Release notes](https://github.com/philss/floki/releases) - [Changelog](https://github.com/philss/floki/blob/main/CHANGELOG.md) - [Commits](https://github.com/philss/floki/compare/v0.35.2...v0.35.3) --- updated-dependencies: - dependency-name: floki dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 79a27ea3fb66..41650d882177 100644 --- a/mix.lock +++ b/mix.lock @@ -60,7 +60,7 @@ "exvcr": {:hex, :exvcr, "0.15.0", "432a4f4b94494f996c96dd2b9b9d3306b70db269ddbdeb9e324a4371f62ce32d", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.16", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "8b7e451f5fd37d1dc1252d08e55291fcb80b55b00cfd84ea41bf64be23cb142c"}, "file_info": {:hex, :file_info, "0.0.4", "2e0e77f211e833f38ead22cb29ce53761d457d80b3ffe0ffe0eb93880b0963b2", [:mix], [{:mimetype_parser, "~> 0.1.2", [hex: :mimetype_parser, repo: "hexpm", optional: false]}], "hexpm", "50e7ad01c2c8b9339010675fe4dc4a113b8d6ca7eddce24d1d74fd0e762781a5"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"}, + "floki": {:hex, :floki, "0.35.3", "0c8c6234aa71cb2b069cf801e8f8f30f8d096eb452c3dae2ccc409510ec32720", [:mix], [], "hexpm", "6d9f07f3fc76599f3b66c39f4a81ac62c8f4d9631140268db92aacad5d0e56d4"}, "flow": {:hex, :flow, "1.2.4", "1dd58918287eb286656008777cb32714b5123d3855956f29aa141ebae456922d", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "874adde96368e71870f3510b91e35bc31652291858c86c0e75359cbdd35eb211"}, "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, From e34589965904c009b1564861e373a3d75bf73cb6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 19:09:37 +0000 Subject: [PATCH 362/607] Bump exvcr from 0.15.0 to 0.15.1 Bumps [exvcr](https://github.com/parroty/exvcr) from 0.15.0 to 0.15.1. - [Release notes](https://github.com/parroty/exvcr/releases) - [Changelog](https://github.com/parroty/exvcr/blob/master/CHANGELOG.md) - [Commits](https://github.com/parroty/exvcr/compare/v0.15.0...v0.15.1) --- updated-dependencies: - dependency-name: exvcr dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 79a27ea3fb66..9dcc6d4a1383 100644 --- a/mix.lock +++ b/mix.lock @@ -57,7 +57,7 @@ "exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], [], "hexpm", "1222419f706e01bfa1095aec9acf6421367dcfab798a6f67c54cf784733cd6b5"}, "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm", "32e95820a97cffea67830e91514a2ad53b888850442d6d395f53a1ac60c82e07"}, "expo": {:hex, :expo, "0.5.1", "249e826a897cac48f591deba863b26c16682b43711dd15ee86b92f25eafd96d9", [:mix], [], "hexpm", "68a4233b0658a3d12ee00d27d37d856b1ba48607e7ce20fd376958d0ba6ce92b"}, - "exvcr": {:hex, :exvcr, "0.15.0", "432a4f4b94494f996c96dd2b9b9d3306b70db269ddbdeb9e324a4371f62ce32d", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.16", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "8b7e451f5fd37d1dc1252d08e55291fcb80b55b00cfd84ea41bf64be23cb142c"}, + "exvcr": {:hex, :exvcr, "0.15.1", "772db4d065f5136c6a984c302799a79e4ade3e52701c95425fa2229dd6426886", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.16", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "de4fc18b1d672d9b72bc7468735e19779aa50ea963a1f859ef82cd9e294b13e3"}, "file_info": {:hex, :file_info, "0.0.4", "2e0e77f211e833f38ead22cb29ce53761d457d80b3ffe0ffe0eb93880b0963b2", [:mix], [{:mimetype_parser, "~> 0.1.2", [hex: :mimetype_parser, repo: "hexpm", optional: false]}], "hexpm", "50e7ad01c2c8b9339010675fe4dc4a113b8d6ca7eddce24d1d74fd0e762781a5"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"}, From 1bc0e50245bc4d88a2f39cbb5a22ef8deb7db8bb Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 30 Jan 2024 13:47:08 +0400 Subject: [PATCH 363/607] feat: blobs in search --- .../lib/block_scout_web/chain.ex | 54 ++++++++++--------- .../controllers/api/v2/blob_controller.ex | 2 +- .../templates/search/_tile.html.eex | 2 + .../views/api/v2/search_view.ex | 16 +++++- .../lib/explorer/chain/beacon/reader.ex | 24 ++++++--- apps/explorer/lib/explorer/chain/search.ex | 51 +++++++++++++++--- 6 files changed, 107 insertions(+), 42 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index 5df851246141..c2c812f9dbbb 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -15,8 +15,6 @@ defmodule BlockScoutWeb.Chain do token_contract_address_from_token_name: 1 ] - alias Explorer.Chain.UserOperation - import Explorer.Helper, only: [parse_integer: 1] alias Ecto.Association.NotLoaded @@ -27,6 +25,8 @@ defmodule BlockScoutWeb.Chain do Address, Address.CoinBalance, Address.CurrentTokenBalance, + Beacon, + Beacon.Blob, Block, Hash, InternalTransaction, @@ -37,6 +37,7 @@ defmodule BlockScoutWeb.Chain do TokenTransfer, Transaction, Transaction.StateChange, + UserOperation, Wei, Withdrawal } @@ -56,7 +57,7 @@ defmodule BlockScoutWeb.Chain do @page_size 50 @default_paging_options %PagingOptions{page_size: @page_size + 1} @address_hash_len 40 - @tx_block_op_hash_len 64 + @full_hash_len 64 def default_paging_options do @default_paging_options @@ -83,20 +84,20 @@ defmodule BlockScoutWeb.Chain do end @spec from_param(String.t()) :: - {:ok, Address.t() | Block.t() | Transaction.t() | UserOperation.t()} | {:error, :not_found} + {:ok, Address.t() | Block.t() | Transaction.t() | UserOperation.t() | Blob.t()} | {:error, :not_found} def from_param(param) def from_param("0x" <> number_string = param) when byte_size(number_string) == @address_hash_len, do: address_from_param(param) - def from_param("0x" <> number_string = param) when byte_size(number_string) == @tx_block_op_hash_len, - do: block_or_transaction_or_operation_from_param(param) + def from_param("0x" <> number_string = param) when byte_size(number_string) == @full_hash_len, + do: block_or_transaction_or_operation_or_blob_from_param(param) def from_param(param) when byte_size(param) == @address_hash_len, do: address_from_param("0x" <> param) - def from_param(param) when byte_size(param) == @tx_block_op_hash_len, - do: block_or_transaction_or_operation_from_param("0x" <> param) + def from_param(param) when byte_size(param) == @full_hash_len, + do: block_or_transaction_or_operation_or_blob_from_param("0x" <> param) def from_param(string) when is_binary(string) do case param_to_block_number(string) do @@ -673,31 +674,32 @@ defmodule BlockScoutWeb.Chain do %{"fiat_value" => ctb.fiat_value, "value" => value, "id" => id} end - defp block_or_transaction_or_operation_from_param(param) do - with {:error, :not_found} <- transaction_from_param(param) do - hash_string_to_block_or_operation(param) + defp block_or_transaction_or_operation_or_blob_from_param(param) do + with {:ok, hash} <- string_to_transaction_hash(param), + {:error, :not_found} <- hash_to_transaction(hash), + {:error, :not_found} <- hash_to_block(hash), + {:error, :not_found} <- hash_to_user_operation(hash), + {:error, :not_found} <- hash_to_blob(hash) do + {:error, :not_found} + else + :error -> {:error, :not_found} + res -> res end end - defp transaction_from_param(param) do - case string_to_transaction_hash(param) do - {:ok, hash} -> - hash_to_transaction(hash) - - :error -> - {:error, :not_found} + defp hash_to_user_operation(hash) do + if UserOperation.user_operations_enabled?() do + UserOperation.hash_to_user_operation(hash) + else + {:error, :not_found} end end - defp hash_string_to_block_or_operation(hash_string) do - with {:ok, hash} <- string_to_block_hash(hash_string), - {:error, :not_found} <- hash_to_block(hash), - {:user_operations_enabled, true} <- {:user_operations_enabled, UserOperation.user_operations_enabled?()} do - UserOperation.hash_to_user_operation(hash) + defp hash_to_blob(hash) do + if Application.get_env(:explorer, :chain_type) == "ethereum" do + Beacon.Reader.blob(hash, false) else - {:user_operations_enabled, false} -> {:error, :not_found} - :error -> {:error, :not_found} - res -> res + {:error, :not_found} end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex index 54ec3b96d9e5..724b4b8e9bbf 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex @@ -14,7 +14,7 @@ defmodule BlockScoutWeb.API.V2.BlobController do with {:format, {:ok, blob_hash}} <- {:format, Chain.string_to_transaction_hash(blob_hash_string)} do transaction_hashes = Reader.blob_hash_to_transactions(blob_hash, api?: true) - case Reader.blob(blob_hash, api?: true) do + case Reader.blob(blob_hash, true, api?: true) do {:ok, blob} -> conn |> put_status(200) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex index aa4d3cbf89df..85ccdffd56b0 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex @@ -82,6 +82,8 @@ transaction_hash: "0x" <> Base.encode16(@result.tx_hash, case: :lower) %> <% "user_operation" -> %> <%= "0x" <> Base.encode16(@result.user_operation_hash, case: :lower) %> + <% "blob" -> %> + <%= "0x" <> Base.encode16(@result.blob_hash, case: :lower) %> <% "block" -> %> <%= link( "0x" <> Base.encode16(@result.block_hash, case: :lower), diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex index 05a8b95eb089..4d9fbea846ad 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.API.V2.SearchView do alias BlockScoutWeb.{BlockView, Endpoint} alias Explorer.Chain - alias Explorer.Chain.{Address, Block, Hash, Transaction, UserOperation} + alias Explorer.Chain.{Address, Beacon.Blob, Block, Hash, Transaction, UserOperation} def render("search_results.json", %{search_results: search_results, next_page_params: next_page_params}) do %{"items" => Enum.map(search_results, &prepare_search_result/1), "next_page_params" => next_page_params} @@ -94,6 +94,16 @@ defmodule BlockScoutWeb.API.V2.SearchView do } end + def prepare_search_result(%{type: "blob"} = search_result) do + blob_hash = hash_to_string(search_result.blob_hash) + + %{ + "type" => search_result.type, + "blob_hash" => blob_hash, + "timestamp" => search_result.timestamp + } + end + defp hash_to_string(%Hash{bytes: bytes}), do: hash_to_string(bytes) defp hash_to_string(hash), do: "0x" <> Base.encode16(hash, case: :lower) @@ -120,4 +130,8 @@ defmodule BlockScoutWeb.API.V2.SearchView do defp redirect_search_results(%UserOperation{} = item) do %{"type" => "user_operation", "parameter" => to_string(item.hash)} end + + defp redirect_search_results(%Blob{} = item) do + %{"type" => "blob", "parameter" => to_string(item.hash)} + end end diff --git a/apps/explorer/lib/explorer/chain/beacon/reader.ex b/apps/explorer/lib/explorer/chain/beacon/reader.ex index c90e2f08475d..19a51ca0be22 100644 --- a/apps/explorer/lib/explorer/chain/beacon/reader.ex +++ b/apps/explorer/lib/explorer/chain/beacon/reader.ex @@ -11,7 +11,8 @@ defmodule Explorer.Chain.Beacon.Reader do where: 2, where: 3, join: 5, - select: 3 + select: 3, + select_merge: 3 ] import Explorer.Chain, only: [select_repo: 1] @@ -26,7 +27,7 @@ defmodule Explorer.Chain.Beacon.Reader do Returns `{:ok, %Explorer.Chain.Beacon.Blob{}}` if found iex> %Explorer.Chain.Beacon.Blob{hash: hash} = insert(:blob) - iex> {:ok, %Explorer.Chain.Beacon.Blob{hash: found_hash}} = Explorer.Chain.Beacon.Reader.blob(hash) + iex> {:ok, %Explorer.Chain.Beacon.Blob{hash: found_hash}} = Explorer.Chain.Beacon.Reader.blob(hash, true) iex> found_hash == hash true @@ -35,14 +36,23 @@ defmodule Explorer.Chain.Beacon.Reader do iex> {:ok, hash} = Explorer.Chain.string_to_transaction_hash( ...> "0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b" ...> ) - iex> Explorer.Chain.Beacon.Reader.blob(hash) + iex> Explorer.Chain.Beacon.Reader.blob(hash, true) {:error, :not_found} """ - @spec blob(Hash.Full.t(), [Chain.api?()]) :: {:error, :not_found} | {:ok, Blob.t()} - def blob(hash, options \\ []) when is_list(options) do - Blob - |> where(hash: ^hash) + @spec blob(Hash.Full.t(), boolean(), [Chain.api?()]) :: {:error, :not_found} | {:ok, Blob.t()} + def blob(hash, with_data, options \\ []) when is_list(options) do + query = + if with_data do + Blob + |> where(hash: ^hash) + else + Blob + |> where(hash: ^hash) + |> select_merge([_], %{blob_data: nil}) + end + + query |> select_repo(options).one() |> case do nil -> {:error, :not_found} diff --git a/apps/explorer/lib/explorer/chain/search.ex b/apps/explorer/lib/explorer/chain/search.ex index ebfc4baf4741..36189b422a4e 100644 --- a/apps/explorer/lib/explorer/chain/search.ex +++ b/apps/explorer/lib/explorer/chain/search.ex @@ -20,6 +20,7 @@ defmodule Explorer.Chain.Search do alias Explorer.Chain.{ Address, + Beacon.Blob, Block, DenormalizationHelper, SmartContract, @@ -101,17 +102,28 @@ defmodule Explorer.Chain.Search do valid_full_hash?(string) -> tx_query = search_tx_query(string) - if UserOperation.user_operations_enabled?() do - user_operation_query = search_user_operation_query(string) - + tx_block_query = basic_query |> union(^tx_query) - |> union(^user_operation_query) |> union(^block_query) + + tx_block_op_query = + if UserOperation.user_operations_enabled?() do + user_operation_query = search_user_operation_query(string) + + tx_block_query + |> union(^user_operation_query) + else + tx_block_query + end + + if Application.get_env(:explorer, :chain_type) == "ethereum" do + blob_query = search_blob_query(string) + + tx_block_op_query + |> union(^blob_query) else - basic_query - |> union(^tx_query) - |> union(^block_query) + tx_block_op_query end block_query -> @@ -137,6 +149,7 @@ defmodule Explorer.Chain.Search do 2. Results couldn't be paginated """ @spec balanced_unpaginated_search(PagingOptions.t(), binary(), [Chain.api?()] | []) :: list + # credo:disable-for-next-line def balanced_unpaginated_search(paging_options, raw_search_query, options \\ []) do search_query = String.trim(raw_search_query) ens_task = Task.async(fn -> search_ens_name(raw_search_query, options) end) @@ -189,6 +202,15 @@ defmodule Explorer.Chain.Search do [] end + blob_result = + if valid_full_hash?(search_query) && Application.get_env(:explorer, :chain_type) == "ethereum" do + search_query + |> search_blob_query() + |> select_repo(options).all() + else + [] + end + address_result = if query = search_address_query(search_query) do query @@ -215,6 +237,7 @@ defmodule Explorer.Chain.Search do labels_result, tx_result, op_result, + blob_result, address_result, blocks_result, ens_result @@ -431,6 +454,19 @@ defmodule Explorer.Chain.Search do ) end + defp search_blob_query(term) do + blob_search_fields = + search_fields() + |> Map.put(:blob_hash, dynamic([blob, _], blob.hash)) + |> Map.put(:type, "blob") + |> Map.put(:inserted_at, dynamic([blob, _], blob.inserted_at)) + + from(blob in Blob, + where: blob.hash == ^term, + select: ^blob_search_fields + ) + end + defp search_block_query(term) do block_search_fields = search_fields() @@ -586,6 +622,7 @@ defmodule Explorer.Chain.Search do address_hash: dynamic([_], type(^nil, :binary)), tx_hash: dynamic([_], type(^nil, :binary)), user_operation_hash: dynamic([_], type(^nil, :binary)), + blob_hash: dynamic([_], type(^nil, :binary)), block_hash: dynamic([_], type(^nil, :binary)), type: nil, name: nil, From 41008abb696a1453595b7e7c13915a31a133d47a Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 30 Jan 2024 14:40:19 +0400 Subject: [PATCH 364/607] fix: tests --- .../lib/block_scout_web/chain.ex | 4 ++-- .../test/indexer/fetcher/beacon/blob_test.exs | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index c2c812f9dbbb..489e8c79c476 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -19,13 +19,13 @@ defmodule BlockScoutWeb.Chain do alias Ecto.Association.NotLoaded alias Explorer.Account.{TagAddress, TagTransaction, WatchlistAddress} + alias Explorer.Chain.Beacon.Reader, as: BeaconReader alias Explorer.Chain.Block.Reward alias Explorer.Chain.{ Address, Address.CoinBalance, Address.CurrentTokenBalance, - Beacon, Beacon.Blob, Block, Hash, @@ -697,7 +697,7 @@ defmodule BlockScoutWeb.Chain do defp hash_to_blob(hash) do if Application.get_env(:explorer, :chain_type) == "ethereum" do - Beacon.Reader.blob(hash, false) + BeaconReader.blob(hash, false) else {:error, :not_found} end diff --git a/apps/indexer/test/indexer/fetcher/beacon/blob_test.exs b/apps/indexer/test/indexer/fetcher/beacon/blob_test.exs index bf51708221de..3d41909b78a6 100644 --- a/apps/indexer/test/indexer/fetcher/beacon/blob_test.exs +++ b/apps/indexer/test/indexer/fetcher/beacon/blob_test.exs @@ -40,10 +40,10 @@ defmodule Indexer.Fetcher.Beacon.BlobTest do insert(:blob_transaction, hash: transaction_b_hash, blob_versioned_hashes: [blob_c.hash]) insert(:blob_transaction, hash: transaction_c_hash, blob_versioned_hashes: [blob_d.hash]) - assert {:error, :not_found} = Reader.blob(blob_a.hash) - assert {:error, :not_found} = Reader.blob(blob_b.hash) - assert {:error, :not_found} = Reader.blob(blob_c.hash) - assert {:ok, _} = Reader.blob(blob_d.hash) + assert {:error, :not_found} = Reader.blob(blob_a.hash, true) + assert {:error, :not_found} = Reader.blob(blob_b.hash, true) + assert {:error, :not_found} = Reader.blob(blob_c.hash, true) + assert {:ok, _} = Reader.blob(blob_d.hash, true) Application.put_env(:explorer, :http_adapter, Explorer.Mox.HTTPoison) @@ -96,10 +96,10 @@ defmodule Indexer.Fetcher.Beacon.BlobTest do Repo.one!(from(blob in Blob, where: blob.hash == ^blob_a.hash)) end) - assert {:ok, _} = Reader.blob(blob_a.hash) - assert {:ok, _} = Reader.blob(blob_b.hash) - assert {:ok, _} = Reader.blob(blob_c.hash) - assert {:ok, _} = Reader.blob(blob_d.hash) + assert {:ok, _} = Reader.blob(blob_a.hash, true) + assert {:ok, _} = Reader.blob(blob_b.hash, true) + assert {:ok, _} = Reader.blob(blob_c.hash, true) + assert {:ok, _} = Reader.blob(blob_d.hash, true) Application.put_env(:explorer, :http_adapter, HTTPoison) end @@ -154,7 +154,7 @@ defmodule Indexer.Fetcher.Beacon.BlobTest do Repo.one!(from(blob in Blob, where: blob.hash == ^blob_hash_a)) end) - assert {:ok, blob} = Reader.blob(blob_hash_a) + assert {:ok, blob} = Reader.blob(blob_hash_a, true) assert %{ hash: ^blob_hash_a, From e4ccb3642e850637b64011d8b952835e9f5125e9 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 30 Jan 2024 15:09:44 +0400 Subject: [PATCH 365/607] fix: one more test --- .../lib/block_scout_web/views/api/v2/block_view.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex index 60eaab5943de..6e7b251e0899 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex @@ -134,7 +134,7 @@ defmodule BlockScoutWeb.API.V2.BlockView do "ethereum" -> if single_block? do blob_gas_price = Block.transaction_blob_gas_price(block.transactions) - burnt_blob_transaction_fees = block |> Map.get(:blob_gas_used, 0) |> Decimal.mult(blob_gas_price || 0) + burnt_blob_transaction_fees = Decimal.mult(block.blob_gas_used || 0, blob_gas_price || 0) result |> Map.put("blob_gas_used", block.blob_gas_used) From f857b6b39be5a7d9db67545591333ed080a9b441 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 30 Jan 2024 18:38:46 +0400 Subject: [PATCH 366/607] fix: too many connections in tests --- apps/explorer/config/test.exs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index 1eb16cbe3b07..728b931ac91a 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -60,7 +60,8 @@ for repo <- [ ownership_timeout: :timer.minutes(1), timeout: :timer.seconds(60), queue_target: 1000, - log: false + log: false, + pool_size: 1 end config :logger, :explorer, From 0275316d9354e4dbcc1b6366d010e3ec0056678e Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 30 Jan 2024 22:11:01 +0400 Subject: [PATCH 367/607] chore: try to fix connection timeout --- apps/explorer/test/support/data_case.ex | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/apps/explorer/test/support/data_case.ex b/apps/explorer/test/support/data_case.ex index 28a2ffad9653..da18760983cc 100644 --- a/apps/explorer/test/support/data_case.ex +++ b/apps/explorer/test/support/data_case.ex @@ -35,24 +35,10 @@ defmodule Explorer.DataCase do :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo) :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.Account) - :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.PolygonEdge) - :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.PolygonZkevm) - :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.RSK) - :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.Shibarium) - :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.Suave) - :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.Beacon) - :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo.BridgedTokens) unless tags[:async] do Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()}) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Account, {:shared, self()}) - Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonEdge, {:shared, self()}) - Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonZkevm, {:shared, self()}) - Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.RSK, {:shared, self()}) - Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Shibarium, {:shared, self()}) - Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, {:shared, self()}) - Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Beacon, {:shared, self()}) - Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.BridgedTokens, {:shared, self()}) end Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.BlockNumber.child_id()) From 176c3d83582d8bee54571c314fa1a659386f5b27 Mon Sep 17 00:00:00 2001 From: varasev <33550681+varasev@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:47:52 +0300 Subject: [PATCH 368/607] Add workflow for Shibarium (#9303) * Add publish-docker-image-for-shibarium.yaml * Release and prerelease for shibarium * Update changelog * Remove arm64 from prerelease.yml for Shibarium --------- Co-authored-by: POA <33550681+poa@users.noreply.github.com> Co-authored-by: Nick Zenchik --- .github/workflows/prerelease.yml | 25 ++++++++++++++++++++++++- .github/workflows/release.yml | 22 +++++++++++++++++++++- CHANGELOG.md | 2 ++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 446790b07bce..b1aa39f30268 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -62,4 +62,27 @@ jobs: AMPLITUDE_API_KEY= CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha - RELEASE_VERSION=${{ env.RELEASE_VERSION }} \ No newline at end of file + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + + - name: Build & Push Docker image for Shibarium + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + cache-from: type=registry,ref=blockscout/blockscout-shibarium:buildcache + cache-to: type=registry,ref=blockscout/blockscout-shibarium:buildcache,mode=max + tags: blockscout/blockscout-shibarium:latest, blockscout/blockscout-shibarium:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + platforms: | + linux/amd64 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=shibarium \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 31bee78745ac..d0145c4bdcef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -109,7 +109,27 @@ jobs: BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta RELEASE_VERSION=${{ env.RELEASE_VERSION }} CHAIN_TYPE=suave - + - name: Build and push Docker image for Shibarium + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-shibarium:latest, blockscout/blockscout-shibarium:${{ env.RELEASE_VERSION }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=shibarium - name: Send release announcement to Slack workflow id: slack uses: slackapi/slack-github-action@v1.24.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index c59befb5b0d7..27ee1027dca2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Chore +- [#9303](https://github.com/blockscout/blockscout/pull/9303) - Add workflow for Shibarium +
Dependencies version bumps From 7264dd755ed77348484a9301bdcce7e3d20e684d Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Fri, 2 Feb 2024 14:52:03 +0300 Subject: [PATCH 369/607] Add smart contract and names info to AA response --- .../api/v2/proxy/account_abstraction_controller.ex | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex index 89fb25900ecd..398d946b49d9 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex @@ -153,7 +153,17 @@ defmodule BlockScoutWeb.API.V2.Proxy.AccountAbstractionController do defp address_info_from_hash_string(address_hash_string) do with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), - {:ok, address} <- Chain.hash_to_address(address_hash, [], false) do + {:ok, address} <- + Chain.hash_to_address( + address_hash, + [ + necessity_by_association: %{ + :names => :optional, + :smart_contract => :optional + } + ], + false + ) do Helper.address_with_info(address, address_hash_string) else _ -> address_hash_string From 29b7f18354f5309fd95deed72971eeaa38821629 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Fri, 2 Feb 2024 14:57:00 +0300 Subject: [PATCH 370/607] Changelog --- CHANGELOG.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27ee1027dca2..6ea300b1190e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,6 @@ ### Chore -- [#9303](https://github.com/blockscout/blockscout/pull/9303) - Add workflow for Shibarium -
Dependencies version bumps @@ -24,7 +22,7 @@ - [#9158](https://github.com/blockscout/blockscout/pull/9158) - Increase shared memory for PostgreSQL containers - [#9155](https://github.com/blockscout/blockscout/pull/9155) - Allow bypassing avg block time in proxy implementation re-fetch ttl calculation - [#9148](https://github.com/blockscout/blockscout/pull/9148) - Add `/api/v2/utils/decode-calldata` -- [#9145](https://github.com/blockscout/blockscout/pull/9145) - Proxy for Account abstraction microservice +- [#9145](https://github.com/blockscout/blockscout/pull/9145), [#9309](https://github.com/blockscout/blockscout/pull/9309) - Proxy for Account abstraction microservice - [#9132](https://github.com/blockscout/blockscout/pull/9132) - Fetch token image from CoinGecko - [#9131](https://github.com/blockscout/blockscout/pull/9131) - Merge addresses stage with address referencing - [#9120](https://github.com/blockscout/blockscout/pull/9120) - Add GET and POST `/api/v2/smart-contracts/:address_hash/audit-reports` @@ -57,6 +55,7 @@ ### Chore +- [#9303](https://github.com/blockscout/blockscout/pull/9303) - Add workflow for Shibarium - [#9233](https://github.com/blockscout/blockscout/pull/9233) - "cataloged" index on tokens table - [#9198](https://github.com/blockscout/blockscout/pull/9198) - Make Postgres@15 default option - [#9197](https://github.com/blockscout/blockscout/pull/9197) - Add `MARKET_HISTORY_FETCH_INTERVAL` env From e49bd8d136f457800a70d121aca21b2fd0a9b736 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 5 Feb 2024 13:02:16 +0300 Subject: [PATCH 371/607] Create repo setup actions --- .../setup-repo-and-short-sha/action.yml | 23 ++++++++ .github/actions/setup-repo/action.yml | 29 ++++++++++ .github/workflows/config.yml | 4 +- .github/workflows/prerelease.yml | 27 +++------ .../publish-docker-image-every-push.yml | 29 +++------- .../publish-docker-image-for-core.yml | 28 ++------- .../publish-docker-image-for-eth-goerli.yml | 28 ++------- .../publish-docker-image-for-eth-sepolia.yml | 28 ++------- .../publish-docker-image-for-eth.yml | 28 ++------- .../publish-docker-image-for-filecoin.yml | 28 ++------- .../publish-docker-image-for-fuse.yml | 31 ++-------- ...publish-docker-image-for-gnosis-chain.yml} | 33 +++-------- .../publish-docker-image-for-immutable.yml | 31 ++-------- .../publish-docker-image-for-l2-staging.yml | 28 ++------- .../publish-docker-image-for-lukso.yml | 28 ++------- .../publish-docker-image-for-optimism.yml | 31 ++-------- .../publish-docker-image-for-polygon-edge.yml | 28 ++------- ...=> publish-docker-image-for-rootstock.yml} | 28 ++------- .../publish-docker-image-for-stability.yml | 35 +++--------- .../publish-docker-image-for-suave.yml | 35 +++--------- .../publish-docker-image-for-zkevm.yml | 28 ++------- .../publish-docker-image-for-zksync.yml | 28 ++------- ...publish-docker-image-staging-on-demand.yml | 30 +++------- .github/workflows/release-additional.yml | 57 ++++++++++--------- .github/workflows/release.yml | 55 +++--------------- CHANGELOG.md | 1 + 26 files changed, 226 insertions(+), 533 deletions(-) create mode 100644 .github/actions/setup-repo-and-short-sha/action.yml create mode 100644 .github/actions/setup-repo/action.yml rename .github/workflows/{publish-docker-image-for-xdai.yml => publish-docker-image-for-gnosis-chain.yml} (55%) rename .github/workflows/{publish-docker-image-for-rsk.yml => publish-docker-image-for-rootstock.yml} (56%) diff --git a/.github/actions/setup-repo-and-short-sha/action.yml b/.github/actions/setup-repo-and-short-sha/action.yml new file mode 100644 index 000000000000..d3cce9891a3a --- /dev/null +++ b/.github/actions/setup-repo-and-short-sha/action.yml @@ -0,0 +1,23 @@ +name: 'Setup repo and calc short SHA commit' +description: 'Setup repo: checkout/login/extract metadata, Set up Docker Buildx and calculate short SHA commit' +inputs: + docker-username: + description: 'Docker username' + required: true + docker-password: + description: 'Docker password' + required: true +runs: + using: "composite" + + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + with: + docker-username: ${{ inputs.docker-username }} + docker-password: ${{ inputs.docker-password }} + + - name: Add SHORT_SHA env property with commit short sha + shell: bash + run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV \ No newline at end of file diff --git a/.github/actions/setup-repo/action.yml b/.github/actions/setup-repo/action.yml new file mode 100644 index 000000000000..2c3533159e15 --- /dev/null +++ b/.github/actions/setup-repo/action.yml @@ -0,0 +1,29 @@ +name: 'Setup repo' +description: 'Setup repo: checkout/login/extract metadata, Set up Docker Buildx' +inputs: + docker-username: + description: 'Docker username' + required: true + docker-password: + description: 'Docker password' + required: true +runs: + using: "composite" + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ inputs.docker-username }} + password: ${{ inputs.docker-password }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: blockscout/blockscout \ No newline at end of file diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index b8df611ab523..95cbf4d45cf3 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -35,8 +35,8 @@ on: env: MIX_ENV: test - OTP_VERSION: "25.3.2.8" - ELIXIR_VERSION: "1.14.5" + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} ACCOUNT_AUTH0_DOMAIN: "blockscoutcom.us.auth0.com" jobs: diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index b1aa39f30268..fede0f8020bb 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -8,33 +8,22 @@ on: required: true env: - OTP_VERSION: '25.3.2.8' - ELIXIR_VERSION: '1.14.5' + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo with: - images: blockscout/blockscout + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build & Push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-every-push.yml b/.github/workflows/publish-docker-image-every-push.yml index cd9e8c60bc7d..6ec06ee0e85d 100644 --- a/.github/workflows/publish-docker-image-every-push.yml +++ b/.github/workflows/publish-docker-image-every-push.yml @@ -9,9 +9,9 @@ on: - '**/README.md' - 'docker-compose/*' env: - OTP_VERSION: '25.3.2.8' - ELIXIR_VERSION: '1.14.5' - RELEASE_VERSION: 6.1.0 + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} jobs: push_to_registry: @@ -21,26 +21,13 @@ jobs: release-version: ${{ steps.output-step.outputs.release-version }} short-sha: ${{ steps.output-step.outputs.short-sha }} steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - images: blockscout/blockscout + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV - name: Add outputs run: | diff --git a/.github/workflows/publish-docker-image-for-core.yml b/.github/workflows/publish-docker-image-for-core.yml index 1225cb8ed1b2..9f726bfbbd56 100644 --- a/.github/workflows/publish-docker-image-for-core.yml +++ b/.github/workflows/publish-docker-image-for-core.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: POA Core Publish Docker image on: @@ -15,26 +10,15 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} DOCKER_CHAIN_NAME: poa steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-for-eth-goerli.yml b/.github/workflows/publish-docker-image-for-eth-goerli.yml index def03da86809..5fc153cf9e41 100644 --- a/.github/workflows/publish-docker-image-for-eth-goerli.yml +++ b/.github/workflows/publish-docker-image-for-eth-goerli.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: ETH Goerli Publish Docker image on: @@ -15,26 +10,15 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} DOCKER_CHAIN_NAME: eth-goerli steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-for-eth-sepolia.yml b/.github/workflows/publish-docker-image-for-eth-sepolia.yml index 0e5fae2e0654..e476ad5f47a4 100644 --- a/.github/workflows/publish-docker-image-for-eth-sepolia.yml +++ b/.github/workflows/publish-docker-image-for-eth-sepolia.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: ETH Sepolia Publish Docker image on: @@ -15,26 +10,15 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} DOCKER_CHAIN_NAME: eth-sepolia steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-for-eth.yml b/.github/workflows/publish-docker-image-for-eth.yml index c18259c96c74..02635daa56c7 100644 --- a/.github/workflows/publish-docker-image-for-eth.yml +++ b/.github/workflows/publish-docker-image-for-eth.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: ETH Publish Docker image on: @@ -15,26 +10,15 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} DOCKER_CHAIN_NAME: mainnet steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-for-filecoin.yml b/.github/workflows/publish-docker-image-for-filecoin.yml index e75e0588bee4..d77f39ac7f99 100644 --- a/.github/workflows/publish-docker-image-for-filecoin.yml +++ b/.github/workflows/publish-docker-image-for-filecoin.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: Publish Docker image for specific chain branches on: @@ -14,26 +9,15 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} DOCKER_CHAIN_NAME: filecoin steps: - - name: Check out the repo - uses: actions/checkout@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v4 - with: - images: blockscout/blockscout - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-for-fuse.yml b/.github/workflows/publish-docker-image-for-fuse.yml index 05647dda0fbc..00a0c0be04f4 100644 --- a/.github/workflows/publish-docker-image-for-fuse.yml +++ b/.github/workflows/publish-docker-image-for-fuse.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: Fuse Publish Docker image on: @@ -15,29 +10,15 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} DOCKER_CHAIN_NAME: fuse steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-for-xdai.yml b/.github/workflows/publish-docker-image-for-gnosis-chain.yml similarity index 55% rename from .github/workflows/publish-docker-image-for-xdai.yml rename to .github/workflows/publish-docker-image-for-gnosis-chain.yml index a57595bd3ec1..93706a4d9112 100644 --- a/.github/workflows/publish-docker-image-for-xdai.yml +++ b/.github/workflows/publish-docker-image-for-gnosis-chain.yml @@ -1,9 +1,4 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Gnosis chain Publish Docker image +name: Gnosis Chain Publish Docker image on: workflow_dispatch: @@ -15,29 +10,15 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} DOCKER_CHAIN_NAME: xdai steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-for-immutable.yml b/.github/workflows/publish-docker-image-for-immutable.yml index a9ea33b4bffe..21ea26f23395 100644 --- a/.github/workflows/publish-docker-image-for-immutable.yml +++ b/.github/workflows/publish-docker-image-for-immutable.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: Immutable Publish Docker image on: @@ -15,29 +10,15 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} DOCKER_CHAIN_NAME: immutable steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-for-l2-staging.yml b/.github/workflows/publish-docker-image-for-l2-staging.yml index 5666915dca53..4ced3d5a35d1 100644 --- a/.github/workflows/publish-docker-image-for-l2-staging.yml +++ b/.github/workflows/publish-docker-image-for-l2-staging.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: L2 staging Publish Docker image on: @@ -15,26 +10,15 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} DOCKER_CHAIN_NAME: optimism-l2-advanced steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-for-lukso.yml b/.github/workflows/publish-docker-image-for-lukso.yml index 4f9d75fb92e2..35e01599c831 100644 --- a/.github/workflows/publish-docker-image-for-lukso.yml +++ b/.github/workflows/publish-docker-image-for-lukso.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: LUKSO Publish Docker image on: @@ -15,26 +10,15 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} DOCKER_CHAIN_NAME: lukso steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-for-optimism.yml b/.github/workflows/publish-docker-image-for-optimism.yml index f8fac34d30a8..c47114afc547 100644 --- a/.github/workflows/publish-docker-image-for-optimism.yml +++ b/.github/workflows/publish-docker-image-for-optimism.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: Optimism Publish Docker image on: @@ -15,29 +10,15 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} DOCKER_CHAIN_NAME: optimism steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-for-polygon-edge.yml b/.github/workflows/publish-docker-image-for-polygon-edge.yml index 59708c1e2729..e5bcbf6b2a34 100644 --- a/.github/workflows/publish-docker-image-for-polygon-edge.yml +++ b/.github/workflows/publish-docker-image-for-polygon-edge.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: Polygon Edge Publish Docker image on: @@ -15,26 +10,15 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} DOCKER_CHAIN_NAME: polygon-edge steps: - - name: Check out the repo - uses: actions/checkout@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v4 - with: - images: blockscout/blockscout - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-for-rsk.yml b/.github/workflows/publish-docker-image-for-rootstock.yml similarity index 56% rename from .github/workflows/publish-docker-image-for-rsk.yml rename to .github/workflows/publish-docker-image-for-rootstock.yml index ce4bc64cfdf6..4a4c90e6f178 100644 --- a/.github/workflows/publish-docker-image-for-rsk.yml +++ b/.github/workflows/publish-docker-image-for-rootstock.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: Rootstock Publish Docker image on: @@ -15,26 +10,15 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} DOCKER_CHAIN_NAME: rsk steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-for-stability.yml b/.github/workflows/publish-docker-image-for-stability.yml index a5b4fcf04bd5..b5f486595e0e 100644 --- a/.github/workflows/publish-docker-image-for-stability.yml +++ b/.github/workflows/publish-docker-image-for-stability.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: Stability Publish Docker image on: @@ -11,36 +6,22 @@ on: branches: - production-stability env: - OTP_VERSION: '25.3.2.8' - ELIXIR_VERSION: '1.14.5' + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} DOCKER_CHAIN_NAME: stability steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-for-suave.yml b/.github/workflows/publish-docker-image-for-suave.yml index 8fd50785cb65..d7d28a9e0fa2 100644 --- a/.github/workflows/publish-docker-image-for-suave.yml +++ b/.github/workflows/publish-docker-image-for-suave.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: SUAVE Publish Docker image on: @@ -11,36 +6,22 @@ on: branches: - production-suave env: - OTP_VERSION: '25.3.2.8' - ELIXIR_VERSION: '1.14.5' + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} DOCKER_CHAIN_NAME: suave steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-for-zkevm.yml b/.github/workflows/publish-docker-image-for-zkevm.yml index 7976b83f6e85..74ab92177a9f 100644 --- a/.github/workflows/publish-docker-image-for-zkevm.yml +++ b/.github/workflows/publish-docker-image-for-zkevm.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: Zkevm publish Docker image on: @@ -15,26 +10,15 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} DOCKER_CHAIN_NAME: zkevm steps: - - name: Check out the repo - uses: actions/checkout@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v4 - with: - images: blockscout/blockscout - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-for-zksync.yml b/.github/workflows/publish-docker-image-for-zksync.yml index 2c707eb8d867..3cd9c2ad750d 100644 --- a/.github/workflows/publish-docker-image-for-zksync.yml +++ b/.github/workflows/publish-docker-image-for-zksync.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: Zksync publish Docker image on: @@ -15,26 +10,15 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} DOCKER_CHAIN_NAME: zksync steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-action@v5 diff --git a/.github/workflows/publish-docker-image-staging-on-demand.yml b/.github/workflows/publish-docker-image-staging-on-demand.yml index b7e80f059441..bf3f48c890bc 100644 --- a/.github/workflows/publish-docker-image-staging-on-demand.yml +++ b/.github/workflows/publish-docker-image-staging-on-demand.yml @@ -10,9 +10,9 @@ on: - '**/README.md' - 'docker-compose/*' env: - OTP_VERSION: '25.3.2.8' - ELIXIR_VERSION: '1.14.5' - RELEASE_VERSION: 6.1.0 + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} jobs: push_to_registry: @@ -22,26 +22,12 @@ jobs: release-version: ${{ steps.output-step.outputs.release-version }} short-sha: ${{ steps.output-step.outputs.short-sha }} steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout-staging - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Add outputs run: | diff --git a/.github/workflows/release-additional.yml b/.github/workflows/release-additional.yml index 57eac202c7a2..6e251e1a1864 100644 --- a/.github/workflows/release-additional.yml +++ b/.github/workflows/release-additional.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: Release additional on: @@ -10,33 +5,22 @@ on: types: [published] env: - OTP_VERSION: '25.3.2.8' - ELIXIR_VERSION: '1.14.5' + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image for Rootstock uses: docker/build-push-action@v5 @@ -82,7 +66,7 @@ jobs: RELEASE_VERSION=${{ env.RELEASE_VERSION }} CHAIN_TYPE=polygon_edge - - name: Build and push Docker image + - name: Build and push Docker image for Stability uses: docker/build-push-action@v5 with: context: . @@ -102,4 +86,25 @@ jobs: CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=stability \ No newline at end of file + CHAIN_TYPE=stability + - name: Build and push Docker image for Shibarium + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-shibarium:latest, blockscout/blockscout-shibarium:${{ env.RELEASE_VERSION }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=shibarium \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d0145c4bdcef..a5b98c420728 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: Release on: @@ -10,35 +5,24 @@ on: types: [published] env: - OTP_VERSION: '25.3.2.8' - ELIXIR_VERSION: '1.14.5' + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} steps: - - name: Check out the repo - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: blockscout/blockscout - - - name: Build & Push Docker image + - name: Build & Push Core Docker image uses: docker/build-push-action@v5 with: context: . @@ -109,27 +93,6 @@ jobs: BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta RELEASE_VERSION=${{ env.RELEASE_VERSION }} CHAIN_TYPE=suave - - name: Build and push Docker image for Shibarium - uses: docker/build-push-action@v5 - with: - context: . - file: ./docker/Dockerfile - push: true - tags: blockscout/blockscout-shibarium:latest, blockscout/blockscout-shibarium:${{ env.RELEASE_VERSION }} - platforms: | - linux/amd64 - linux/arm64/v8 - build-args: | - CACHE_EXCHANGE_RATES_PERIOD= - API_V1_READ_METHODS_DISABLED=false - DISABLE_WEBAPP=false - API_V1_WRITE_METHODS_DISABLED=false - CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= - ADMIN_PANEL_ENABLED=false - CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= - BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} - RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=shibarium - name: Send release announcement to Slack workflow id: slack uses: slackapi/slack-github-action@v1.24.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ea300b1190e..89af5e38c30b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ ### Chore +- [#9322](https://github.com/blockscout/blockscout/pull/9322) - Create repo setup actions - [#9303](https://github.com/blockscout/blockscout/pull/9303) - Add workflow for Shibarium - [#9233](https://github.com/blockscout/blockscout/pull/9233) - "cataloged" index on tokens table - [#9198](https://github.com/blockscout/blockscout/pull/9198) - Make Postgres@15 default option From 8f5588db4862c2db071f510b3db6677facaefaa6 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 5 Feb 2024 16:57:08 +0300 Subject: [PATCH 372/607] Change index creation to concurrent --- CHANGELOG.md | 1 + ...0240114181404_enhanced_unfetched_token_balances_index.exs | 4 +++- .../migrations/20240123102336_add_tokens_cataloged_index.exs | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89af5e38c30b..a0f3dcfdb52b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ ### Chore +- [#9323](https://github.com/blockscout/blockscout/pull/9323) - Change index creation to concurrent - [#9322](https://github.com/blockscout/blockscout/pull/9322) - Create repo setup actions - [#9303](https://github.com/blockscout/blockscout/pull/9303) - Add workflow for Shibarium - [#9233](https://github.com/blockscout/blockscout/pull/9233) - "cataloged" index on tokens table diff --git a/apps/explorer/priv/repo/migrations/20240114181404_enhanced_unfetched_token_balances_index.exs b/apps/explorer/priv/repo/migrations/20240114181404_enhanced_unfetched_token_balances_index.exs index 287d5ae42efa..dcab0dc92050 100644 --- a/apps/explorer/priv/repo/migrations/20240114181404_enhanced_unfetched_token_balances_index.exs +++ b/apps/explorer/priv/repo/migrations/20240114181404_enhanced_unfetched_token_balances_index.exs @@ -1,9 +1,11 @@ defmodule Explorer.Repo.Migrations.EnhancedUnfetchedTokenBalancesIndex do use Ecto.Migration + @disable_ddl_transaction true + @disable_migration_lock true def up do execute(""" - CREATE INDEX unfetched_address_token_balances_index on address_token_balances(id) + CREATE INDEX CONCURRENTLY unfetched_address_token_balances_index on address_token_balances(id) WHERE ( ((address_hash != '\\x0000000000000000000000000000000000000000' AND token_type = 'ERC-721') OR token_type = 'ERC-20' OR token_type = 'ERC-1155') AND (value_fetched_at IS NULL OR value IS NULL) ); diff --git a/apps/explorer/priv/repo/migrations/20240123102336_add_tokens_cataloged_index.exs b/apps/explorer/priv/repo/migrations/20240123102336_add_tokens_cataloged_index.exs index e3d4e02be5f3..b0157fb6a9c5 100644 --- a/apps/explorer/priv/repo/migrations/20240123102336_add_tokens_cataloged_index.exs +++ b/apps/explorer/priv/repo/migrations/20240123102336_add_tokens_cataloged_index.exs @@ -1,5 +1,7 @@ defmodule Explorer.Repo.Migrations.AddTokensCatalogedIndex do use Ecto.Migration + @disable_ddl_transaction true + @disable_migration_lock true def change do create( @@ -7,7 +9,8 @@ defmodule Explorer.Repo.Migrations.AddTokensCatalogedIndex do :tokens, ~w(cataloged)a, name: :uncataloged_tokens, - where: ~s|"cataloged" = false| + where: ~s|"cataloged" = false|, + concurrently: true ) ) end From 77d984d9155d24d93cdc34efd2bdb10f8a66251a Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 5 Feb 2024 18:46:33 +0300 Subject: [PATCH 373/607] Fix Blockscout version in pre-release workflow --- .github/workflows/prerelease.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index fede0f8020bb..4ec067e47968 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -50,7 +50,7 @@ jobs: AMPLITUDE_URL= AMPLITUDE_API_KEY= CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= - BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} RELEASE_VERSION=${{ env.RELEASE_VERSION }} - name: Build & Push Docker image for Shibarium @@ -72,6 +72,6 @@ jobs: CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= ADMIN_PANEL_ENABLED=false CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= - BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} RELEASE_VERSION=${{ env.RELEASE_VERSION }} CHAIN_TYPE=shibarium \ No newline at end of file From 9473b921d9dd32cb101cc372a33805f4c7cea483 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:22:33 +0000 Subject: [PATCH 374/607] Bump hammer from 6.1.0 to 6.2.0 Bumps [hammer](https://github.com/ExHammer/hammer) from 6.1.0 to 6.2.0. - [Changelog](https://github.com/ExHammer/hammer/blob/master/CHANGELOG.md) - [Commits](https://github.com/ExHammer/hammer/compare/v6.1.0...v6.2.0) --- updated-dependencies: - dependency-name: hammer dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 79a27ea3fb66..7bf38bb2d7b2 100644 --- a/mix.lock +++ b/mix.lock @@ -65,7 +65,7 @@ "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~>2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, - "hammer": {:hex, :hammer, "6.1.0", "f263e3c3e9946bd410ea0336b2abe0cb6260af4afb3a221e1027540706e76c55", [:make, :mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b47e415a562a6d072392deabcd58090d8a41182cf9044cdd6b0d0faaaf68ba57"}, + "hammer": {:hex, :hammer, "6.2.0", "956e578f210ee67f7801caf7109b0e1145d2dad77ed5a0e5c0041a04739ede36", [:mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "1431a30e1f9c816e0fc58d2587de2d5f4c709b74bf81be77515dc902e35bb3a7"}, "hammer_backend_redis": {:hex, :hammer_backend_redis, "6.1.2", "eb296bb4924928e24135308b2afc189201fd09411c870c6bbadea444a49b2f2c", [:mix], [{:hammer, "~> 6.0", [hex: :hammer, repo: "hexpm", optional: false]}, {:redix, "~> 1.1", [hex: :redix, repo: "hexpm", optional: false]}], "hexpm", "217ea066278910543a5e9b577d5bf2425419446b94fe76bdd9f255f39feec9fa"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"}, From 0410efd139ba4c99517d94b0aef716eef1666c2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:23:02 +0000 Subject: [PATCH 375/607] Bump logger_json from 5.1.2 to 5.1.3 Bumps [logger_json](https://github.com/Nebo15/logger_json) from 5.1.2 to 5.1.3. - [Release notes](https://github.com/Nebo15/logger_json/releases) - [Commits](https://github.com/Nebo15/logger_json/commits) --- updated-dependencies: - dependency-name: logger_json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 79a27ea3fb66..0f3eab3b3149 100644 --- a/mix.lock +++ b/mix.lock @@ -75,7 +75,7 @@ "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm", "fc3499fed7a726995aa659143a248534adc754ebd16ccd437cd93b649a95091f"}, "junit_formatter": {:hex, :junit_formatter, "3.3.1", "c729befb848f1b9571f317d2fefa648e9d4869befc4b2980daca7c1edc468e40", [:mix], [], "hexpm", "761fc5be4b4c15d8ba91a6dafde0b2c2ae6db9da7b8832a55b5a1deb524da72b"}, "logger_file_backend": {:hex, :logger_file_backend, "0.0.13", "df07b14970e9ac1f57362985d76e6f24e3e1ab05c248055b7d223976881977c2", [:mix], [], "hexpm", "71a453a7e6e899ae4549fb147b1c6621f4233f8f48f58ca10a64ec67b6c50018"}, - "logger_json": {:hex, :logger_json, "5.1.2", "7dde5f6dff814aba033f045a3af9408f5459bac72357dc533276b47045371ecf", [:mix], [{:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "ed42047e5c57a60d0fa1450aef36bc016d0f9a5e6c0807ebb0c03d8895fb6ebc"}, + "logger_json": {:hex, :logger_json, "5.1.3", "fe931b54826e7ba3b1233ede5c13d87cd670a23563f8d146d0ee22985549dbb5", [:mix], [{:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "ecc67e24f9ccf1688c5e48c3d6b7889a0ab5d398fe32a7fec69c461303a9b89c"}, "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"}, @@ -105,7 +105,7 @@ "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.6.2", "753611b23b29231fb916b0cdd96028084b12aff57bfd7b71781bd04b1dbeb5c9", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "951ed2433df22f4c97b85fdb145d4cee561f36b74854d64c06d896d7cd2921a7"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.0", "3ae9369c60641084363b08fe90267cbdd316df57e3557ea522114b30b63256ea", [:mix], [{:cowboy, "~> 2.7.0 or ~> 2.8.0 or ~> 2.9.0 or ~> 2.10.0", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d85444fb8aa1f2fc62eabe83bbe387d81510d773886774ebdcb429b3da3c1a4a"}, "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, From 1e704e50f8a3d20137b415d53bfe60020b97d9d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:43:37 +0000 Subject: [PATCH 376/607] Bump sweetalert2 from 11.10.3 to 11.10.5 in /apps/block_scout_web/assets Bumps [sweetalert2](https://github.com/sweetalert2/sweetalert2) from 11.10.3 to 11.10.5. - [Release notes](https://github.com/sweetalert2/sweetalert2/releases) - [Changelog](https://github.com/sweetalert2/sweetalert2/blob/main/CHANGELOG.md) - [Commits](https://github.com/sweetalert2/sweetalert2/compare/v11.10.3...v11.10.5) --- updated-dependencies: - dependency-name: sweetalert2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index d1c9b8ef82dc..c20b617aff91 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -61,7 +61,7 @@ "redux": "^5.0.1", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", - "sweetalert2": "^11.10.3", + "sweetalert2": "^11.10.5", "urijs": "^1.19.11", "url": "^0.11.3", "util": "^0.12.5", @@ -16101,9 +16101,9 @@ } }, "node_modules/sweetalert2": { - "version": "11.10.3", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.3.tgz", - "integrity": "sha512-mZYtQR7v+khyEruq0SsVUa6XIdI9Aue8s2XAIpAwdlLN1T0w7mxKEjyubiBZ3/bLbHC/wGS4wNABvXWubCizvA==", + "version": "11.10.5", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.5.tgz", + "integrity": "sha512-q9eE3EKhMcpIDU/Xcz7z5lk8axCGkgxwK47gXGrrfncnBJWxHPPHnBVAjfsVXcTt8Yi8U6HNEcBRSu+qGeyFdA==", "funding": { "type": "individual", "url": "https://github.com/sponsors/limonte" @@ -29920,9 +29920,9 @@ } }, "sweetalert2": { - "version": "11.10.3", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.3.tgz", - "integrity": "sha512-mZYtQR7v+khyEruq0SsVUa6XIdI9Aue8s2XAIpAwdlLN1T0w7mxKEjyubiBZ3/bLbHC/wGS4wNABvXWubCizvA==" + "version": "11.10.5", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.5.tgz", + "integrity": "sha512-q9eE3EKhMcpIDU/Xcz7z5lk8axCGkgxwK47gXGrrfncnBJWxHPPHnBVAjfsVXcTt8Yi8U6HNEcBRSu+qGeyFdA==" }, "symbol-tree": { "version": "3.2.4", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index e308946e150f..745a51cda4cb 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -73,7 +73,7 @@ "redux": "^5.0.1", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", - "sweetalert2": "^11.10.3", + "sweetalert2": "^11.10.5", "urijs": "^1.19.11", "url": "^0.11.3", "util": "^0.12.5", From a24a6c69029e478f7fd054aae8fd5cb4e83ccbe4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:44:09 +0000 Subject: [PATCH 377/607] Bump mini-css-extract-plugin in /apps/block_scout_web/assets Bumps [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) from 2.7.7 to 2.8.0. - [Release notes](https://github.com/webpack-contrib/mini-css-extract-plugin/releases) - [Changelog](https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/mini-css-extract-plugin/compare/v2.7.7...v2.8.0) --- updated-dependencies: - dependency-name: mini-css-extract-plugin dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 20 ++++++++++--------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index d1c9b8ef82dc..d1990e5c9162 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -86,7 +86,7 @@ "file-loader": "^6.2.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "mini-css-extract-plugin": "^2.7.7", + "mini-css-extract-plugin": "^2.8.0", "postcss": "^8.4.33", "postcss-loader": "^8.0.0", "sass": "^1.70.0", @@ -12847,12 +12847,13 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.7.7", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.7.tgz", - "integrity": "sha512-+0n11YGyRavUR3IlaOzJ0/4Il1avMvJ1VJfhWfCn24ITQXhRr1gghbhhrda6tgtNcpZaWKdSuwKq20Jb7fnlyw==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.0.tgz", + "integrity": "sha512-CxmUYPFcTgET1zImteG/LZOy/4T5rTojesQXkSNBiquhydn78tfbCE9sjIjnJ/UcjNjOC1bphTCCW5rrS7cXAg==", "dev": true, "dependencies": { - "schema-utils": "^4.0.0" + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" }, "engines": { "node": ">= 12.13.0" @@ -27572,12 +27573,13 @@ } }, "mini-css-extract-plugin": { - "version": "2.7.7", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.7.tgz", - "integrity": "sha512-+0n11YGyRavUR3IlaOzJ0/4Il1avMvJ1VJfhWfCn24ITQXhRr1gghbhhrda6tgtNcpZaWKdSuwKq20Jb7fnlyw==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.0.tgz", + "integrity": "sha512-CxmUYPFcTgET1zImteG/LZOy/4T5rTojesQXkSNBiquhydn78tfbCE9sjIjnJ/UcjNjOC1bphTCCW5rrS7cXAg==", "dev": true, "requires": { - "schema-utils": "^4.0.0" + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" } }, "minimalistic-assert": { diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index e308946e150f..0bde32e935ac 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -98,7 +98,7 @@ "file-loader": "^6.2.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "mini-css-extract-plugin": "^2.7.7", + "mini-css-extract-plugin": "^2.8.0", "postcss": "^8.4.33", "postcss-loader": "^8.0.0", "sass": "^1.70.0", From 280408d7c9b0da649a469365206d788866b5fcfc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:44:44 +0000 Subject: [PATCH 378/607] Bump web3 from 1.10.3 to 1.10.4 in /apps/block_scout_web/assets Bumps [web3](https://github.com/ChainSafe/web3.js) from 1.10.3 to 1.10.4. - [Release notes](https://github.com/ChainSafe/web3.js/releases) - [Changelog](https://github.com/web3/web3.js/blob/v1.10.4/CHANGELOG.md) - [Commits](https://github.com/ChainSafe/web3.js/compare/v1.10.3...v1.10.4) --- updated-dependencies: - dependency-name: web3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 742 +++++++++--------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 372 insertions(+), 372 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index d1c9b8ef82dc..136bd77d6bc0 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -66,7 +66,7 @@ "url": "^0.11.3", "util": "^0.12.5", "viewerjs": "^1.11.6", - "web3": "^1.10.3", + "web3": "^1.10.4", "web3modal": "^1.9.12", "xss": "^1.0.14" }, @@ -2173,14 +2173,14 @@ } }, "node_modules/@ethereumjs/util/node_modules/ethereum-cryptography": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz", - "integrity": "sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", + "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", "dependencies": { - "@noble/curves": "1.1.0", - "@noble/hashes": "1.3.1", - "@scure/bip32": "1.3.1", - "@scure/bip39": "1.2.1" + "@noble/curves": "1.3.0", + "@noble/hashes": "1.3.3", + "@scure/bip32": "1.3.3", + "@scure/bip39": "1.2.2" } }, "node_modules/@ethersproject/abi": { @@ -3361,20 +3361,20 @@ "integrity": "sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==" }, "node_modules/@noble/curves": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", - "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", + "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", "dependencies": { - "@noble/hashes": "1.3.1" + "@noble/hashes": "1.3.3" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@noble/hashes": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", - "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", "engines": { "node": ">= 16" }, @@ -3418,33 +3418,33 @@ } }, "node_modules/@scure/base": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.3.tgz", - "integrity": "sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", + "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==", "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@scure/bip32": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz", - "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.3.tgz", + "integrity": "sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==", "dependencies": { - "@noble/curves": "~1.1.0", - "@noble/hashes": "~1.3.1", - "@scure/base": "~1.1.0" + "@noble/curves": "~1.3.0", + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@scure/bip39": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", - "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.2.tgz", + "integrity": "sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==", "dependencies": { - "@noble/hashes": "~1.3.0", - "@scure/base": "~1.1.0" + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -3632,9 +3632,9 @@ } }, "node_modules/@types/http-cache-semantics": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz", - "integrity": "sha512-V46MYLFp08Wf2mmaBhvgjStM3tPa+2GAdy/iqoX+noX1//zje2x4XmrIU0cAwyClATsTmahbtoQ2EwP7I5WSiA==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", @@ -3705,9 +3705,9 @@ } }, "node_modules/@types/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha512-/4YQT5Kp6HxUDb4yhRkm0bJ7TbjvTddqX7PZ5hz6qV3pxSo72f/6YPRo+Mu2DU307tm9IioO69l7uAwn5XNcFA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", "dependencies": { "@types/node": "*" } @@ -8379,9 +8379,9 @@ } }, "node_modules/ethereumjs-util/node_modules/@types/bn.js": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.3.tgz", - "integrity": "sha512-wT1B4iIO82ecXkdN6waCK8Ou7E71WU+mP1osDA5Q8c6Ur+ozU2vIKUIhSpUr6uE5L2YHocKS1Z2jG2fBC1YVeg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", + "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", "dependencies": { "@types/node": "*" } @@ -9556,9 +9556,9 @@ } }, "node_modules/http2-wrapper": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.0.tgz", - "integrity": "sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.2.0" @@ -16849,27 +16849,27 @@ } }, "node_modules/web3": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.10.3.tgz", - "integrity": "sha512-DgUdOOqC/gTqW+VQl1EdPxrVRPB66xVNtuZ5KD4adVBtko87hkgM8BTZ0lZ8IbUfnQk6DyjcDujMiH3oszllAw==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.10.4.tgz", + "integrity": "sha512-kgJvQZjkmjOEKimx/tJQsqWfRDPTTcBfYPa9XletxuHLpHcXdx67w8EFn5AW3eVxCutE9dTVHgGa9VYe8vgsEA==", "hasInstallScript": true, "dependencies": { - "web3-bzz": "1.10.3", - "web3-core": "1.10.3", - "web3-eth": "1.10.3", - "web3-eth-personal": "1.10.3", - "web3-net": "1.10.3", - "web3-shh": "1.10.3", - "web3-utils": "1.10.3" + "web3-bzz": "1.10.4", + "web3-core": "1.10.4", + "web3-eth": "1.10.4", + "web3-eth-personal": "1.10.4", + "web3-net": "1.10.4", + "web3-shh": "1.10.4", + "web3-utils": "1.10.4" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-bzz": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.10.3.tgz", - "integrity": "sha512-XDIRsTwekdBXtFytMpHBuun4cK4x0ZMIDXSoo1UVYp+oMyZj07c7gf7tNQY5qZ/sN+CJIas4ilhN25VJcjSijQ==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.10.4.tgz", + "integrity": "sha512-ZZ/X4sJ0Uh2teU9lAGNS8EjveEppoHNQiKlOXAjedsrdWuaMErBPdLQjXfcrYvN6WM6Su9PMsAxf3FXXZ+HwQw==", "hasInstallScript": true, "dependencies": { "@types/node": "^12.12.6", @@ -16886,53 +16886,53 @@ "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" }, "node_modules/web3-core": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.10.3.tgz", - "integrity": "sha512-Vbk0/vUNZxJlz3RFjAhNNt7qTpX8yE3dn3uFxfX5OHbuon5u65YEOd3civ/aQNW745N0vGUlHFNxxmn+sG9DIw==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.10.4.tgz", + "integrity": "sha512-B6elffYm81MYZDTrat7aEhnhdtVE3lDBUZft16Z8awYMZYJDbnykEbJVS+l3mnA7AQTnSDr/1MjWofGDLBJPww==", "dependencies": { "@types/bn.js": "^5.1.1", "@types/node": "^12.12.6", "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.10.3", - "web3-core-method": "1.10.3", - "web3-core-requestmanager": "1.10.3", - "web3-utils": "1.10.3" + "web3-core-helpers": "1.10.4", + "web3-core-method": "1.10.4", + "web3-core-requestmanager": "1.10.4", + "web3-utils": "1.10.4" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-core-helpers": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.10.3.tgz", - "integrity": "sha512-Yv7dQC3B9ipOc5sWm3VAz1ys70Izfzb8n9rSiQYIPjpqtJM+3V4EeK6ghzNR6CO2es0+Yu9CtCkw0h8gQhrTxA==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.10.4.tgz", + "integrity": "sha512-r+L5ylA17JlD1vwS8rjhWr0qg7zVoVMDvWhajWA5r5+USdh91jRUYosp19Kd1m2vE034v7Dfqe1xYRoH2zvG0g==", "dependencies": { - "web3-eth-iban": "1.10.3", - "web3-utils": "1.10.3" + "web3-eth-iban": "1.10.4", + "web3-utils": "1.10.4" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-core-method": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.10.3.tgz", - "integrity": "sha512-VZ/Dmml4NBmb0ep5PTSg9oqKoBtG0/YoMPei/bq/tUdlhB2dMB79sbeJPwx592uaV0Vpk7VltrrrBv5hTM1y4Q==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.10.4.tgz", + "integrity": "sha512-uZTb7flr+Xl6LaDsyTeE2L1TylokCJwTDrIVfIfnrGmnwLc6bmTWCCrm71sSrQ0hqs6vp/MKbQYIYqUN0J8WyA==", "dependencies": { "@ethersproject/transactions": "^5.6.2", - "web3-core-helpers": "1.10.3", - "web3-core-promievent": "1.10.3", - "web3-core-subscriptions": "1.10.3", - "web3-utils": "1.10.3" + "web3-core-helpers": "1.10.4", + "web3-core-promievent": "1.10.4", + "web3-core-subscriptions": "1.10.4", + "web3-utils": "1.10.4" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-core-promievent": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.10.3.tgz", - "integrity": "sha512-HgjY+TkuLm5uTwUtaAfkTgRx/NzMxvVradCi02gy17NxDVdg/p6svBHcp037vcNpkuGeFznFJgULP+s2hdVgUQ==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.10.4.tgz", + "integrity": "sha512-2de5WnJQ72YcIhYwV/jHLc4/cWJnznuoGTJGD29ncFQHAfwW/MItHFSVKPPA5v8AhJe+r6y4Y12EKvZKjQVBvQ==", "dependencies": { "eventemitter3": "4.0.4" }, @@ -16941,36 +16941,36 @@ } }, "node_modules/web3-core-requestmanager": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.10.3.tgz", - "integrity": "sha512-VT9sKJfgM2yBOIxOXeXiDuFMP4pxzF6FT+y8KTLqhDFHkbG3XRe42Vm97mB/IvLQCJOmokEjl3ps8yP1kbggyw==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.10.4.tgz", + "integrity": "sha512-vqP6pKH8RrhT/2MoaU+DY/OsYK9h7HmEBNCdoMj+4ZwujQtw/Mq2JifjwsJ7gits7Q+HWJwx8q6WmQoVZAWugg==", "dependencies": { "util": "^0.12.5", - "web3-core-helpers": "1.10.3", - "web3-providers-http": "1.10.3", - "web3-providers-ipc": "1.10.3", - "web3-providers-ws": "1.10.3" + "web3-core-helpers": "1.10.4", + "web3-providers-http": "1.10.4", + "web3-providers-ipc": "1.10.4", + "web3-providers-ws": "1.10.4" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-core-subscriptions": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.10.3.tgz", - "integrity": "sha512-KW0Mc8sgn70WadZu7RjQ4H5sNDJ5Lx8JMI3BWos+f2rW0foegOCyWhRu33W1s6ntXnqeBUw5rRCXZRlA3z+HNA==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.10.4.tgz", + "integrity": "sha512-o0lSQo/N/f7/L76C0HV63+S54loXiE9fUPfHFcTtpJRQNDBVsSDdWRdePbWwR206XlsBqD5VHApck1//jEafTw==", "dependencies": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.10.3" + "web3-core-helpers": "1.10.4" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-core/node_modules/@types/bn.js": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.3.tgz", - "integrity": "sha512-wT1B4iIO82ecXkdN6waCK8Ou7E71WU+mP1osDA5Q8c6Ur+ozU2vIKUIhSpUr6uE5L2YHocKS1Z2jG2fBC1YVeg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", + "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", "dependencies": { "@types/node": "*" } @@ -16981,43 +16981,43 @@ "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" }, "node_modules/web3-eth": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.10.3.tgz", - "integrity": "sha512-Uk1U2qGiif2mIG8iKu23/EQJ2ksB1BQXy3wF3RvFuyxt8Ft9OEpmGlO7wOtAyJdoKzD5vcul19bJpPcWSAYZhA==", - "dependencies": { - "web3-core": "1.10.3", - "web3-core-helpers": "1.10.3", - "web3-core-method": "1.10.3", - "web3-core-subscriptions": "1.10.3", - "web3-eth-abi": "1.10.3", - "web3-eth-accounts": "1.10.3", - "web3-eth-contract": "1.10.3", - "web3-eth-ens": "1.10.3", - "web3-eth-iban": "1.10.3", - "web3-eth-personal": "1.10.3", - "web3-net": "1.10.3", - "web3-utils": "1.10.3" + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.10.4.tgz", + "integrity": "sha512-Sql2kYKmgt+T/cgvg7b9ce24uLS7xbFrxE4kuuor1zSCGrjhTJ5rRNG8gTJUkAJGKJc7KgnWmgW+cOfMBPUDSA==", + "dependencies": { + "web3-core": "1.10.4", + "web3-core-helpers": "1.10.4", + "web3-core-method": "1.10.4", + "web3-core-subscriptions": "1.10.4", + "web3-eth-abi": "1.10.4", + "web3-eth-accounts": "1.10.4", + "web3-eth-contract": "1.10.4", + "web3-eth-ens": "1.10.4", + "web3-eth-iban": "1.10.4", + "web3-eth-personal": "1.10.4", + "web3-net": "1.10.4", + "web3-utils": "1.10.4" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-eth-abi": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.10.3.tgz", - "integrity": "sha512-O8EvV67uhq0OiCMekqYsDtb6FzfYzMXT7VMHowF8HV6qLZXCGTdB/NH4nJrEh2mFtEwVdS6AmLFJAQd2kVyoMQ==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.10.4.tgz", + "integrity": "sha512-cZ0q65eJIkd/jyOlQPDjr8X4fU6CRL1eWgdLwbWEpo++MPU/2P4PFk5ZLAdye9T5Sdp+MomePPJ/gHjLMj2VfQ==", "dependencies": { "@ethersproject/abi": "^5.6.3", - "web3-utils": "1.10.3" + "web3-utils": "1.10.4" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-eth-accounts": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.10.3.tgz", - "integrity": "sha512-8MipGgwusDVgn7NwKOmpeo3gxzzd+SmwcWeBdpXknuyDiZSQy9tXe+E9LeFGrmys/8mLLYP79n3jSbiTyv+6pQ==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.10.4.tgz", + "integrity": "sha512-ysy5sVTg9snYS7tJjxVoQAH6DTOTkRGR8emEVCWNGLGiB9txj+qDvSeT0izjurS/g7D5xlMAgrEHLK1Vi6I3yg==", "dependencies": { "@ethereumjs/common": "2.6.5", "@ethereumjs/tx": "3.5.2", @@ -17025,10 +17025,10 @@ "eth-lib": "0.2.8", "scrypt-js": "^3.0.1", "uuid": "^9.0.0", - "web3-core": "1.10.3", - "web3-core-helpers": "1.10.3", - "web3-core-method": "1.10.3", - "web3-utils": "1.10.3" + "web3-core": "1.10.4", + "web3-core-helpers": "1.10.4", + "web3-core-method": "1.10.4", + "web3-utils": "1.10.4" }, "engines": { "node": ">=8.0.0" @@ -17062,72 +17062,72 @@ } }, "node_modules/web3-eth-contract": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.10.3.tgz", - "integrity": "sha512-Y2CW61dCCyY4IoUMD4JsEQWrILX4FJWDWC/Txx/pr3K/+fGsBGvS9kWQN5EsVXOp4g7HoFOfVh9Lf7BmVVSRmg==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.10.4.tgz", + "integrity": "sha512-Q8PfolOJ4eV9TvnTj1TGdZ4RarpSLmHnUnzVxZ/6/NiTfe4maJz99R0ISgwZkntLhLRtw0C7LRJuklzGYCNN3A==", "dependencies": { "@types/bn.js": "^5.1.1", - "web3-core": "1.10.3", - "web3-core-helpers": "1.10.3", - "web3-core-method": "1.10.3", - "web3-core-promievent": "1.10.3", - "web3-core-subscriptions": "1.10.3", - "web3-eth-abi": "1.10.3", - "web3-utils": "1.10.3" + "web3-core": "1.10.4", + "web3-core-helpers": "1.10.4", + "web3-core-method": "1.10.4", + "web3-core-promievent": "1.10.4", + "web3-core-subscriptions": "1.10.4", + "web3-eth-abi": "1.10.4", + "web3-utils": "1.10.4" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-eth-contract/node_modules/@types/bn.js": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.3.tgz", - "integrity": "sha512-wT1B4iIO82ecXkdN6waCK8Ou7E71WU+mP1osDA5Q8c6Ur+ozU2vIKUIhSpUr6uE5L2YHocKS1Z2jG2fBC1YVeg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", + "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", "dependencies": { "@types/node": "*" } }, "node_modules/web3-eth-ens": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.10.3.tgz", - "integrity": "sha512-hR+odRDXGqKemw1GFniKBEXpjYwLgttTES+bc7BfTeoUyUZXbyDHe5ifC+h+vpzxh4oS0TnfcIoarK0Z9tFSiQ==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.10.4.tgz", + "integrity": "sha512-LLrvxuFeVooRVZ9e5T6OWKVflHPFgrVjJ/jtisRWcmI7KN/b64+D/wJzXqgmp6CNsMQcE7rpmf4CQmJCrTdsgg==", "dependencies": { "content-hash": "^2.5.2", "eth-ens-namehash": "2.0.8", - "web3-core": "1.10.3", - "web3-core-helpers": "1.10.3", - "web3-core-promievent": "1.10.3", - "web3-eth-abi": "1.10.3", - "web3-eth-contract": "1.10.3", - "web3-utils": "1.10.3" + "web3-core": "1.10.4", + "web3-core-helpers": "1.10.4", + "web3-core-promievent": "1.10.4", + "web3-eth-abi": "1.10.4", + "web3-eth-contract": "1.10.4", + "web3-utils": "1.10.4" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-eth-iban": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.10.3.tgz", - "integrity": "sha512-ZCfOjYKAjaX2TGI8uif5ah+J3BYFuo+47JOIV1RIz2l7kD9VfnxvRH5UiQDRyMALQC7KFd2hUqIEtHklapNyKA==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.10.4.tgz", + "integrity": "sha512-0gE5iNmOkmtBmbKH2aTodeompnNE8jEyvwFJ6s/AF6jkw9ky9Op9cqfzS56AYAbrqEFuClsqB/AoRves7LDELw==", "dependencies": { "bn.js": "^5.2.1", - "web3-utils": "1.10.3" + "web3-utils": "1.10.4" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-eth-personal": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.10.3.tgz", - "integrity": "sha512-avrQ6yWdADIvuNQcFZXmGLCEzulQa76hUOuVywN7O3cklB4nFc/Gp3yTvD3bOAaE7DhjLQfhUTCzXL7WMxVTsw==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.10.4.tgz", + "integrity": "sha512-BRa/hs6jU1hKHz+AC/YkM71RP3f0Yci1dPk4paOic53R4ZZG4MgwKRkJhgt3/GPuPliwS46f/i5A7fEGBT4F9w==", "dependencies": { "@types/node": "^12.12.6", - "web3-core": "1.10.3", - "web3-core-helpers": "1.10.3", - "web3-core-method": "1.10.3", - "web3-net": "1.10.3", - "web3-utils": "1.10.3" + "web3-core": "1.10.4", + "web3-core-helpers": "1.10.4", + "web3-core-method": "1.10.4", + "web3-net": "1.10.4", + "web3-utils": "1.10.4" }, "engines": { "node": ">=8.0.0" @@ -17139,13 +17139,13 @@ "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" }, "node_modules/web3-net": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.10.3.tgz", - "integrity": "sha512-IoSr33235qVoI1vtKssPUigJU9Fc/Ph0T9CgRi15sx+itysmvtlmXMNoyd6Xrgm9LuM4CIhxz7yDzH93B79IFg==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.10.4.tgz", + "integrity": "sha512-mKINnhOOnZ4koA+yV2OT5s5ztVjIx7IY9a03w6s+yao/BUn+Luuty0/keNemZxTr1E8Ehvtn28vbOtW7Ids+Ow==", "dependencies": { - "web3-core": "1.10.3", - "web3-core-method": "1.10.3", - "web3-utils": "1.10.3" + "web3-core": "1.10.4", + "web3-core-method": "1.10.4", + "web3-utils": "1.10.4" }, "engines": { "node": ">=8.0.0" @@ -17230,14 +17230,14 @@ } }, "node_modules/web3-providers-http": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.10.3.tgz", - "integrity": "sha512-6dAgsHR3MxJ0Qyu3QLFlQEelTapVfWNTu5F45FYh8t7Y03T1/o+YAkVxsbY5AdmD+y5bXG/XPJ4q8tjL6MgZHw==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.10.4.tgz", + "integrity": "sha512-m2P5Idc8hdiO0l60O6DSCPw0kw64Zgi0pMjbEFRmxKIck2Py57RQMu4bxvkxJwkF06SlGaEQF8rFZBmuX7aagQ==", "dependencies": { "abortcontroller-polyfill": "^1.7.5", "cross-fetch": "^4.0.0", "es6-promise": "^4.2.8", - "web3-core-helpers": "1.10.3" + "web3-core-helpers": "1.10.4" }, "engines": { "node": ">=8.0.0" @@ -17252,24 +17252,24 @@ } }, "node_modules/web3-providers-ipc": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.10.3.tgz", - "integrity": "sha512-vP5WIGT8FLnGRfswTxNs9rMfS1vCbMezj/zHbBe/zB9GauBRTYVrUo2H/hVrhLg8Ut7AbsKZ+tCJ4mAwpKi2hA==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.10.4.tgz", + "integrity": "sha512-YRF/bpQk9z3WwjT+A6FI/GmWRCASgd+gC0si7f9zbBWLXjwzYAKG73bQBaFRAHex1hl4CVcM5WUMaQXf3Opeuw==", "dependencies": { "oboe": "2.1.5", - "web3-core-helpers": "1.10.3" + "web3-core-helpers": "1.10.4" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-providers-ws": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.10.3.tgz", - "integrity": "sha512-/filBXRl48INxsh6AuCcsy4v5ndnTZ/p6bl67kmO9aK1wffv7CT++DrtclDtVMeDGCgB3van+hEf9xTAVXur7Q==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.10.4.tgz", + "integrity": "sha512-j3FBMifyuFFmUIPVQR4pj+t5ILhAexAui0opgcpu9R5LxQrLRUZxHSnU+YO25UycSOa/NAX8A+qkqZNpcFAlxA==", "dependencies": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.10.3", + "web3-core-helpers": "1.10.4", "websocket": "^1.0.32" }, "engines": { @@ -17277,24 +17277,24 @@ } }, "node_modules/web3-shh": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.10.3.tgz", - "integrity": "sha512-cAZ60CPvs9azdwMSQ/PSUdyV4PEtaW5edAZhu3rCXf6XxQRliBboic+AvwUvB6j3eswY50VGa5FygfVmJ1JVng==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.10.4.tgz", + "integrity": "sha512-cOH6iFFM71lCNwSQrC3niqDXagMqrdfFW85hC9PFUrAr3PUrIem8TNstTc3xna2bwZeWG6OBy99xSIhBvyIACw==", "hasInstallScript": true, "dependencies": { - "web3-core": "1.10.3", - "web3-core-method": "1.10.3", - "web3-core-subscriptions": "1.10.3", - "web3-net": "1.10.3" + "web3-core": "1.10.4", + "web3-core-method": "1.10.4", + "web3-core-subscriptions": "1.10.4", + "web3-net": "1.10.4" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-utils": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.3.tgz", - "integrity": "sha512-OqcUrEE16fDBbGoQtZXWdavsPzbGIDc5v3VrRTZ0XrIpefC/viZ1ZU9bGEemazyS0catk/3rkOOxpzTfY+XsyQ==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.4.tgz", + "integrity": "sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A==", "dependencies": { "@ethereumjs/util": "^8.1.0", "bn.js": "^5.2.1", @@ -17310,14 +17310,14 @@ } }, "node_modules/web3-utils/node_modules/ethereum-cryptography": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz", - "integrity": "sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", + "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", "dependencies": { - "@noble/curves": "1.1.0", - "@noble/hashes": "1.3.1", - "@scure/bip32": "1.3.1", - "@scure/bip39": "1.2.1" + "@noble/curves": "1.3.0", + "@noble/hashes": "1.3.3", + "@scure/bip32": "1.3.3", + "@scure/bip39": "1.2.2" } }, "node_modules/web3modal": { @@ -19360,14 +19360,14 @@ }, "dependencies": { "ethereum-cryptography": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz", - "integrity": "sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", + "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", "requires": { - "@noble/curves": "1.1.0", - "@noble/hashes": "1.3.1", - "@scure/bip32": "1.3.1", - "@scure/bip39": "1.2.1" + "@noble/curves": "1.3.0", + "@noble/hashes": "1.3.3", + "@scure/bip32": "1.3.3", + "@scure/bip39": "1.2.2" } } } @@ -20180,17 +20180,17 @@ "integrity": "sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==" }, "@noble/curves": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", - "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", + "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", "requires": { - "@noble/hashes": "1.3.1" + "@noble/hashes": "1.3.3" } }, "@noble/hashes": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", - "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==" }, "@nodelib/fs.scandir": { "version": "2.1.5", @@ -20219,27 +20219,27 @@ } }, "@scure/base": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.3.tgz", - "integrity": "sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==" + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", + "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==" }, "@scure/bip32": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz", - "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.3.tgz", + "integrity": "sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==", "requires": { - "@noble/curves": "~1.1.0", - "@noble/hashes": "~1.3.1", - "@scure/base": "~1.1.0" + "@noble/curves": "~1.3.0", + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" } }, "@scure/bip39": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", - "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.2.tgz", + "integrity": "sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==", "requires": { - "@noble/hashes": "~1.3.0", - "@scure/base": "~1.1.0" + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" } }, "@sinclair/typebox": { @@ -20392,9 +20392,9 @@ } }, "@types/http-cache-semantics": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz", - "integrity": "sha512-V46MYLFp08Wf2mmaBhvgjStM3tPa+2GAdy/iqoX+noX1//zje2x4XmrIU0cAwyClATsTmahbtoQ2EwP7I5WSiA==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" }, "@types/istanbul-lib-coverage": { "version": "2.0.4", @@ -20465,9 +20465,9 @@ } }, "@types/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha512-/4YQT5Kp6HxUDb4yhRkm0bJ7TbjvTddqX7PZ5hz6qV3pxSo72f/6YPRo+Mu2DU307tm9IioO69l7uAwn5XNcFA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", "requires": { "@types/node": "*" } @@ -24136,9 +24136,9 @@ }, "dependencies": { "@types/bn.js": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.3.tgz", - "integrity": "sha512-wT1B4iIO82ecXkdN6waCK8Ou7E71WU+mP1osDA5Q8c6Ur+ozU2vIKUIhSpUr6uE5L2YHocKS1Z2jG2fBC1YVeg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", + "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", "requires": { "@types/node": "*" } @@ -25054,9 +25054,9 @@ } }, "http2-wrapper": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.0.tgz", - "integrity": "sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "requires": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.2.0" @@ -30474,23 +30474,23 @@ } }, "web3": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.10.3.tgz", - "integrity": "sha512-DgUdOOqC/gTqW+VQl1EdPxrVRPB66xVNtuZ5KD4adVBtko87hkgM8BTZ0lZ8IbUfnQk6DyjcDujMiH3oszllAw==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.10.4.tgz", + "integrity": "sha512-kgJvQZjkmjOEKimx/tJQsqWfRDPTTcBfYPa9XletxuHLpHcXdx67w8EFn5AW3eVxCutE9dTVHgGa9VYe8vgsEA==", "requires": { - "web3-bzz": "1.10.3", - "web3-core": "1.10.3", - "web3-eth": "1.10.3", - "web3-eth-personal": "1.10.3", - "web3-net": "1.10.3", - "web3-shh": "1.10.3", - "web3-utils": "1.10.3" + "web3-bzz": "1.10.4", + "web3-core": "1.10.4", + "web3-eth": "1.10.4", + "web3-eth-personal": "1.10.4", + "web3-net": "1.10.4", + "web3-shh": "1.10.4", + "web3-utils": "1.10.4" } }, "web3-bzz": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.10.3.tgz", - "integrity": "sha512-XDIRsTwekdBXtFytMpHBuun4cK4x0ZMIDXSoo1UVYp+oMyZj07c7gf7tNQY5qZ/sN+CJIas4ilhN25VJcjSijQ==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.10.4.tgz", + "integrity": "sha512-ZZ/X4sJ0Uh2teU9lAGNS8EjveEppoHNQiKlOXAjedsrdWuaMErBPdLQjXfcrYvN6WM6Su9PMsAxf3FXXZ+HwQw==", "requires": { "@types/node": "^12.12.6", "got": "12.1.0", @@ -30505,23 +30505,23 @@ } }, "web3-core": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.10.3.tgz", - "integrity": "sha512-Vbk0/vUNZxJlz3RFjAhNNt7qTpX8yE3dn3uFxfX5OHbuon5u65YEOd3civ/aQNW745N0vGUlHFNxxmn+sG9DIw==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.10.4.tgz", + "integrity": "sha512-B6elffYm81MYZDTrat7aEhnhdtVE3lDBUZft16Z8awYMZYJDbnykEbJVS+l3mnA7AQTnSDr/1MjWofGDLBJPww==", "requires": { "@types/bn.js": "^5.1.1", "@types/node": "^12.12.6", "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.10.3", - "web3-core-method": "1.10.3", - "web3-core-requestmanager": "1.10.3", - "web3-utils": "1.10.3" + "web3-core-helpers": "1.10.4", + "web3-core-method": "1.10.4", + "web3-core-requestmanager": "1.10.4", + "web3-utils": "1.10.4" }, "dependencies": { "@types/bn.js": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.3.tgz", - "integrity": "sha512-wT1B4iIO82ecXkdN6waCK8Ou7E71WU+mP1osDA5Q8c6Ur+ozU2vIKUIhSpUr6uE5L2YHocKS1Z2jG2fBC1YVeg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", + "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", "requires": { "@types/node": "*" } @@ -30534,87 +30534,87 @@ } }, "web3-core-helpers": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.10.3.tgz", - "integrity": "sha512-Yv7dQC3B9ipOc5sWm3VAz1ys70Izfzb8n9rSiQYIPjpqtJM+3V4EeK6ghzNR6CO2es0+Yu9CtCkw0h8gQhrTxA==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.10.4.tgz", + "integrity": "sha512-r+L5ylA17JlD1vwS8rjhWr0qg7zVoVMDvWhajWA5r5+USdh91jRUYosp19Kd1m2vE034v7Dfqe1xYRoH2zvG0g==", "requires": { - "web3-eth-iban": "1.10.3", - "web3-utils": "1.10.3" + "web3-eth-iban": "1.10.4", + "web3-utils": "1.10.4" } }, "web3-core-method": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.10.3.tgz", - "integrity": "sha512-VZ/Dmml4NBmb0ep5PTSg9oqKoBtG0/YoMPei/bq/tUdlhB2dMB79sbeJPwx592uaV0Vpk7VltrrrBv5hTM1y4Q==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.10.4.tgz", + "integrity": "sha512-uZTb7flr+Xl6LaDsyTeE2L1TylokCJwTDrIVfIfnrGmnwLc6bmTWCCrm71sSrQ0hqs6vp/MKbQYIYqUN0J8WyA==", "requires": { "@ethersproject/transactions": "^5.6.2", - "web3-core-helpers": "1.10.3", - "web3-core-promievent": "1.10.3", - "web3-core-subscriptions": "1.10.3", - "web3-utils": "1.10.3" + "web3-core-helpers": "1.10.4", + "web3-core-promievent": "1.10.4", + "web3-core-subscriptions": "1.10.4", + "web3-utils": "1.10.4" } }, "web3-core-promievent": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.10.3.tgz", - "integrity": "sha512-HgjY+TkuLm5uTwUtaAfkTgRx/NzMxvVradCi02gy17NxDVdg/p6svBHcp037vcNpkuGeFznFJgULP+s2hdVgUQ==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.10.4.tgz", + "integrity": "sha512-2de5WnJQ72YcIhYwV/jHLc4/cWJnznuoGTJGD29ncFQHAfwW/MItHFSVKPPA5v8AhJe+r6y4Y12EKvZKjQVBvQ==", "requires": { "eventemitter3": "4.0.4" } }, "web3-core-requestmanager": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.10.3.tgz", - "integrity": "sha512-VT9sKJfgM2yBOIxOXeXiDuFMP4pxzF6FT+y8KTLqhDFHkbG3XRe42Vm97mB/IvLQCJOmokEjl3ps8yP1kbggyw==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.10.4.tgz", + "integrity": "sha512-vqP6pKH8RrhT/2MoaU+DY/OsYK9h7HmEBNCdoMj+4ZwujQtw/Mq2JifjwsJ7gits7Q+HWJwx8q6WmQoVZAWugg==", "requires": { "util": "^0.12.5", - "web3-core-helpers": "1.10.3", - "web3-providers-http": "1.10.3", - "web3-providers-ipc": "1.10.3", - "web3-providers-ws": "1.10.3" + "web3-core-helpers": "1.10.4", + "web3-providers-http": "1.10.4", + "web3-providers-ipc": "1.10.4", + "web3-providers-ws": "1.10.4" } }, "web3-core-subscriptions": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.10.3.tgz", - "integrity": "sha512-KW0Mc8sgn70WadZu7RjQ4H5sNDJ5Lx8JMI3BWos+f2rW0foegOCyWhRu33W1s6ntXnqeBUw5rRCXZRlA3z+HNA==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.10.4.tgz", + "integrity": "sha512-o0lSQo/N/f7/L76C0HV63+S54loXiE9fUPfHFcTtpJRQNDBVsSDdWRdePbWwR206XlsBqD5VHApck1//jEafTw==", "requires": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.10.3" + "web3-core-helpers": "1.10.4" } }, "web3-eth": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.10.3.tgz", - "integrity": "sha512-Uk1U2qGiif2mIG8iKu23/EQJ2ksB1BQXy3wF3RvFuyxt8Ft9OEpmGlO7wOtAyJdoKzD5vcul19bJpPcWSAYZhA==", - "requires": { - "web3-core": "1.10.3", - "web3-core-helpers": "1.10.3", - "web3-core-method": "1.10.3", - "web3-core-subscriptions": "1.10.3", - "web3-eth-abi": "1.10.3", - "web3-eth-accounts": "1.10.3", - "web3-eth-contract": "1.10.3", - "web3-eth-ens": "1.10.3", - "web3-eth-iban": "1.10.3", - "web3-eth-personal": "1.10.3", - "web3-net": "1.10.3", - "web3-utils": "1.10.3" + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.10.4.tgz", + "integrity": "sha512-Sql2kYKmgt+T/cgvg7b9ce24uLS7xbFrxE4kuuor1zSCGrjhTJ5rRNG8gTJUkAJGKJc7KgnWmgW+cOfMBPUDSA==", + "requires": { + "web3-core": "1.10.4", + "web3-core-helpers": "1.10.4", + "web3-core-method": "1.10.4", + "web3-core-subscriptions": "1.10.4", + "web3-eth-abi": "1.10.4", + "web3-eth-accounts": "1.10.4", + "web3-eth-contract": "1.10.4", + "web3-eth-ens": "1.10.4", + "web3-eth-iban": "1.10.4", + "web3-eth-personal": "1.10.4", + "web3-net": "1.10.4", + "web3-utils": "1.10.4" } }, "web3-eth-abi": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.10.3.tgz", - "integrity": "sha512-O8EvV67uhq0OiCMekqYsDtb6FzfYzMXT7VMHowF8HV6qLZXCGTdB/NH4nJrEh2mFtEwVdS6AmLFJAQd2kVyoMQ==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.10.4.tgz", + "integrity": "sha512-cZ0q65eJIkd/jyOlQPDjr8X4fU6CRL1eWgdLwbWEpo++MPU/2P4PFk5ZLAdye9T5Sdp+MomePPJ/gHjLMj2VfQ==", "requires": { "@ethersproject/abi": "^5.6.3", - "web3-utils": "1.10.3" + "web3-utils": "1.10.4" } }, "web3-eth-accounts": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.10.3.tgz", - "integrity": "sha512-8MipGgwusDVgn7NwKOmpeo3gxzzd+SmwcWeBdpXknuyDiZSQy9tXe+E9LeFGrmys/8mLLYP79n3jSbiTyv+6pQ==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.10.4.tgz", + "integrity": "sha512-ysy5sVTg9snYS7tJjxVoQAH6DTOTkRGR8emEVCWNGLGiB9txj+qDvSeT0izjurS/g7D5xlMAgrEHLK1Vi6I3yg==", "requires": { "@ethereumjs/common": "2.6.5", "@ethereumjs/tx": "3.5.2", @@ -30622,10 +30622,10 @@ "eth-lib": "0.2.8", "scrypt-js": "^3.0.1", "uuid": "^9.0.0", - "web3-core": "1.10.3", - "web3-core-helpers": "1.10.3", - "web3-core-method": "1.10.3", - "web3-utils": "1.10.3" + "web3-core": "1.10.4", + "web3-core-helpers": "1.10.4", + "web3-core-method": "1.10.4", + "web3-utils": "1.10.4" }, "dependencies": { "bn.js": { @@ -30651,24 +30651,24 @@ } }, "web3-eth-contract": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.10.3.tgz", - "integrity": "sha512-Y2CW61dCCyY4IoUMD4JsEQWrILX4FJWDWC/Txx/pr3K/+fGsBGvS9kWQN5EsVXOp4g7HoFOfVh9Lf7BmVVSRmg==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.10.4.tgz", + "integrity": "sha512-Q8PfolOJ4eV9TvnTj1TGdZ4RarpSLmHnUnzVxZ/6/NiTfe4maJz99R0ISgwZkntLhLRtw0C7LRJuklzGYCNN3A==", "requires": { "@types/bn.js": "^5.1.1", - "web3-core": "1.10.3", - "web3-core-helpers": "1.10.3", - "web3-core-method": "1.10.3", - "web3-core-promievent": "1.10.3", - "web3-core-subscriptions": "1.10.3", - "web3-eth-abi": "1.10.3", - "web3-utils": "1.10.3" + "web3-core": "1.10.4", + "web3-core-helpers": "1.10.4", + "web3-core-method": "1.10.4", + "web3-core-promievent": "1.10.4", + "web3-core-subscriptions": "1.10.4", + "web3-eth-abi": "1.10.4", + "web3-utils": "1.10.4" }, "dependencies": { "@types/bn.js": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.3.tgz", - "integrity": "sha512-wT1B4iIO82ecXkdN6waCK8Ou7E71WU+mP1osDA5Q8c6Ur+ozU2vIKUIhSpUr6uE5L2YHocKS1Z2jG2fBC1YVeg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", + "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", "requires": { "@types/node": "*" } @@ -30676,40 +30676,40 @@ } }, "web3-eth-ens": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.10.3.tgz", - "integrity": "sha512-hR+odRDXGqKemw1GFniKBEXpjYwLgttTES+bc7BfTeoUyUZXbyDHe5ifC+h+vpzxh4oS0TnfcIoarK0Z9tFSiQ==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.10.4.tgz", + "integrity": "sha512-LLrvxuFeVooRVZ9e5T6OWKVflHPFgrVjJ/jtisRWcmI7KN/b64+D/wJzXqgmp6CNsMQcE7rpmf4CQmJCrTdsgg==", "requires": { "content-hash": "^2.5.2", "eth-ens-namehash": "2.0.8", - "web3-core": "1.10.3", - "web3-core-helpers": "1.10.3", - "web3-core-promievent": "1.10.3", - "web3-eth-abi": "1.10.3", - "web3-eth-contract": "1.10.3", - "web3-utils": "1.10.3" + "web3-core": "1.10.4", + "web3-core-helpers": "1.10.4", + "web3-core-promievent": "1.10.4", + "web3-eth-abi": "1.10.4", + "web3-eth-contract": "1.10.4", + "web3-utils": "1.10.4" } }, "web3-eth-iban": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.10.3.tgz", - "integrity": "sha512-ZCfOjYKAjaX2TGI8uif5ah+J3BYFuo+47JOIV1RIz2l7kD9VfnxvRH5UiQDRyMALQC7KFd2hUqIEtHklapNyKA==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.10.4.tgz", + "integrity": "sha512-0gE5iNmOkmtBmbKH2aTodeompnNE8jEyvwFJ6s/AF6jkw9ky9Op9cqfzS56AYAbrqEFuClsqB/AoRves7LDELw==", "requires": { "bn.js": "^5.2.1", - "web3-utils": "1.10.3" + "web3-utils": "1.10.4" } }, "web3-eth-personal": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.10.3.tgz", - "integrity": "sha512-avrQ6yWdADIvuNQcFZXmGLCEzulQa76hUOuVywN7O3cklB4nFc/Gp3yTvD3bOAaE7DhjLQfhUTCzXL7WMxVTsw==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.10.4.tgz", + "integrity": "sha512-BRa/hs6jU1hKHz+AC/YkM71RP3f0Yci1dPk4paOic53R4ZZG4MgwKRkJhgt3/GPuPliwS46f/i5A7fEGBT4F9w==", "requires": { "@types/node": "^12.12.6", - "web3-core": "1.10.3", - "web3-core-helpers": "1.10.3", - "web3-core-method": "1.10.3", - "web3-net": "1.10.3", - "web3-utils": "1.10.3" + "web3-core": "1.10.4", + "web3-core-helpers": "1.10.4", + "web3-core-method": "1.10.4", + "web3-net": "1.10.4", + "web3-utils": "1.10.4" }, "dependencies": { "@types/node": { @@ -30720,13 +30720,13 @@ } }, "web3-net": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.10.3.tgz", - "integrity": "sha512-IoSr33235qVoI1vtKssPUigJU9Fc/Ph0T9CgRi15sx+itysmvtlmXMNoyd6Xrgm9LuM4CIhxz7yDzH93B79IFg==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.10.4.tgz", + "integrity": "sha512-mKINnhOOnZ4koA+yV2OT5s5ztVjIx7IY9a03w6s+yao/BUn+Luuty0/keNemZxTr1E8Ehvtn28vbOtW7Ids+Ow==", "requires": { - "web3-core": "1.10.3", - "web3-core-method": "1.10.3", - "web3-utils": "1.10.3" + "web3-core": "1.10.4", + "web3-core-method": "1.10.4", + "web3-utils": "1.10.4" } }, "web3-provider-engine": { @@ -30810,14 +30810,14 @@ } }, "web3-providers-http": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.10.3.tgz", - "integrity": "sha512-6dAgsHR3MxJ0Qyu3QLFlQEelTapVfWNTu5F45FYh8t7Y03T1/o+YAkVxsbY5AdmD+y5bXG/XPJ4q8tjL6MgZHw==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.10.4.tgz", + "integrity": "sha512-m2P5Idc8hdiO0l60O6DSCPw0kw64Zgi0pMjbEFRmxKIck2Py57RQMu4bxvkxJwkF06SlGaEQF8rFZBmuX7aagQ==", "requires": { "abortcontroller-polyfill": "^1.7.5", "cross-fetch": "^4.0.0", "es6-promise": "^4.2.8", - "web3-core-helpers": "1.10.3" + "web3-core-helpers": "1.10.4" }, "dependencies": { "cross-fetch": { @@ -30831,39 +30831,39 @@ } }, "web3-providers-ipc": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.10.3.tgz", - "integrity": "sha512-vP5WIGT8FLnGRfswTxNs9rMfS1vCbMezj/zHbBe/zB9GauBRTYVrUo2H/hVrhLg8Ut7AbsKZ+tCJ4mAwpKi2hA==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.10.4.tgz", + "integrity": "sha512-YRF/bpQk9z3WwjT+A6FI/GmWRCASgd+gC0si7f9zbBWLXjwzYAKG73bQBaFRAHex1hl4CVcM5WUMaQXf3Opeuw==", "requires": { "oboe": "2.1.5", - "web3-core-helpers": "1.10.3" + "web3-core-helpers": "1.10.4" } }, "web3-providers-ws": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.10.3.tgz", - "integrity": "sha512-/filBXRl48INxsh6AuCcsy4v5ndnTZ/p6bl67kmO9aK1wffv7CT++DrtclDtVMeDGCgB3van+hEf9xTAVXur7Q==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.10.4.tgz", + "integrity": "sha512-j3FBMifyuFFmUIPVQR4pj+t5ILhAexAui0opgcpu9R5LxQrLRUZxHSnU+YO25UycSOa/NAX8A+qkqZNpcFAlxA==", "requires": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.10.3", + "web3-core-helpers": "1.10.4", "websocket": "^1.0.32" } }, "web3-shh": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.10.3.tgz", - "integrity": "sha512-cAZ60CPvs9azdwMSQ/PSUdyV4PEtaW5edAZhu3rCXf6XxQRliBboic+AvwUvB6j3eswY50VGa5FygfVmJ1JVng==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.10.4.tgz", + "integrity": "sha512-cOH6iFFM71lCNwSQrC3niqDXagMqrdfFW85hC9PFUrAr3PUrIem8TNstTc3xna2bwZeWG6OBy99xSIhBvyIACw==", "requires": { - "web3-core": "1.10.3", - "web3-core-method": "1.10.3", - "web3-core-subscriptions": "1.10.3", - "web3-net": "1.10.3" + "web3-core": "1.10.4", + "web3-core-method": "1.10.4", + "web3-core-subscriptions": "1.10.4", + "web3-net": "1.10.4" } }, "web3-utils": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.3.tgz", - "integrity": "sha512-OqcUrEE16fDBbGoQtZXWdavsPzbGIDc5v3VrRTZ0XrIpefC/viZ1ZU9bGEemazyS0catk/3rkOOxpzTfY+XsyQ==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.4.tgz", + "integrity": "sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A==", "requires": { "@ethereumjs/util": "^8.1.0", "bn.js": "^5.2.1", @@ -30876,14 +30876,14 @@ }, "dependencies": { "ethereum-cryptography": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz", - "integrity": "sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", + "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", "requires": { - "@noble/curves": "1.1.0", - "@noble/hashes": "1.3.1", - "@scure/bip32": "1.3.1", - "@scure/bip39": "1.2.1" + "@noble/curves": "1.3.0", + "@noble/hashes": "1.3.3", + "@scure/bip32": "1.3.3", + "@scure/bip39": "1.2.2" } } } diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index e308946e150f..d8247d3c7614 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -78,7 +78,7 @@ "url": "^0.11.3", "util": "^0.12.5", "viewerjs": "^1.11.6", - "web3": "^1.10.3", + "web3": "^1.10.4", "web3modal": "^1.9.12", "xss": "^1.0.14" }, From f7569f2cf088933ca56d3b45ab048ab5b5c8de46 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 5 Feb 2024 22:54:00 +0300 Subject: [PATCH 379/607] BRIDGED_TOKENS_ENABLED=true for Fuse and Gnosis chain Docker generation workflows --- .github/workflows/publish-docker-image-for-fuse.yml | 1 + .github/workflows/publish-docker-image-for-gnosis-chain.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/publish-docker-image-for-fuse.yml b/.github/workflows/publish-docker-image-for-fuse.yml index 00a0c0be04f4..bb88fc294b50 100644 --- a/.github/workflows/publish-docker-image-for-fuse.yml +++ b/.github/workflows/publish-docker-image-for-fuse.yml @@ -28,6 +28,7 @@ jobs: push: true tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:latest, blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} build-args: | + BRIDGED_TOKENS_ENABLED=true CACHE_EXCHANGE_RATES_PERIOD= API_V1_READ_METHODS_DISABLED=false DISABLE_WEBAPP=false diff --git a/.github/workflows/publish-docker-image-for-gnosis-chain.yml b/.github/workflows/publish-docker-image-for-gnosis-chain.yml index 93706a4d9112..31aa884f38f7 100644 --- a/.github/workflows/publish-docker-image-for-gnosis-chain.yml +++ b/.github/workflows/publish-docker-image-for-gnosis-chain.yml @@ -28,6 +28,7 @@ jobs: push: true tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:latest, blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} build-args: | + BRIDGED_TOKENS_ENABLED=true CACHE_EXCHANGE_RATES_PERIOD= API_V1_READ_METHODS_DISABLED=false DISABLE_WEBAPP=false From e0d0bb60aff95944dd759f3c05bb6949fc087a9c Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 5 Feb 2024 22:58:20 +0300 Subject: [PATCH 380/607] Remove v6.0.0-dev branch from CI --- .github/workflows/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 95cbf4d45cf3..a14ffaea2c26 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -4,7 +4,6 @@ on: push: branches: - master - - v6.0.0-dev - production-core - production-eth-experimental - production-eth-goerli @@ -29,7 +28,6 @@ on: pull_request: branches: - master - - v6.0.0-dev - production-optimism - production-zksync From bfb7cbda30a81b17234242a788f683afee351b10 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 5 Feb 2024 23:01:00 +0300 Subject: [PATCH 381/607] Bridged tokens envs to common-blockscout.env --- docker-compose/envs/common-blockscout.env | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 69816e29db00..9e1e6b268d93 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -282,3 +282,9 @@ TENDERLY_CHAIN_PATH= # NOVES_FI_BASE_API_URL= # NOVES_FI_CHAIN_NAME= # NOVES_FI_API_TOKEN= +# BRIDGED_TOKENS_ENABLED= +# BRIDGED_TOKENS_ETH_OMNI_BRIDGE_MEDIATOR= +# BRIDGED_TOKENS_BSC_OMNI_BRIDGE_MEDIATOR= +# BRIDGED_TOKENS_POA_OMNI_BRIDGE_MEDIATOR= +# BRIDGED_TOKENS_AMB_BRIDGE_MEDIATORS +# BRIDGED_TOKENS_FOREIGN_JSON_RPC \ No newline at end of file From 415da41dd3634edc14b80229ecf435e31464d24d Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 5 Feb 2024 23:04:53 +0300 Subject: [PATCH 382/607] Fuse docker image to release CI --- .github/workflows/release-additional.yml | 25 ++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-additional.yml b/.github/workflows/release-additional.yml index 6e251e1a1864..a16c4511080a 100644 --- a/.github/workflows/release-additional.yml +++ b/.github/workflows/release-additional.yml @@ -105,6 +105,27 @@ jobs: CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= ADMIN_PANEL_ENABLED=false CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= - BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=shibarium \ No newline at end of file + CHAIN_TYPE=shibarium + - name: Build and push Docker image for Fuse + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-fuse:latest, blockscout/blockscout-fuse:${{ env.RELEASE_VERSION }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BRIDGED_TOKENS_ENABLED=true + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} \ No newline at end of file From 7f08e950482af039e6983f28bf4746fbbcbad621 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 20:08:05 +0000 Subject: [PATCH 383/607] Bump postcss-loader from 8.0.0 to 8.1.0 in /apps/block_scout_web/assets Bumps [postcss-loader](https://github.com/webpack-contrib/postcss-loader) from 8.0.0 to 8.1.0. - [Release notes](https://github.com/webpack-contrib/postcss-loader/releases) - [Changelog](https://github.com/webpack-contrib/postcss-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/postcss-loader/compare/v8.0.0...v8.1.0) --- updated-dependencies: - dependency-name: postcss-loader dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 23 +++++++++++++------ apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 725f103be151..710f8adf2084 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -88,7 +88,7 @@ "jest-environment-jsdom": "^29.7.0", "mini-css-extract-plugin": "^2.8.0", "postcss": "^8.4.33", - "postcss-loader": "^8.0.0", + "postcss-loader": "^8.1.0", "sass": "^1.70.0", "sass-loader": "^14.0.0", "style-loader": "^3.3.4", @@ -13882,9 +13882,9 @@ } }, "node_modules/postcss-loader": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.0.0.tgz", - "integrity": "sha512-+RiNlmYd1aXYv6QSBOAu6n9eJYy0ydyXTfjljAJ3vFU6MMo2M552zTVcBpBH+R5aAeKaYVG1K9UEyAVsLL1Qjg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.0.tgz", + "integrity": "sha512-AbperNcX3rlob7Ay7A/HQcrofug1caABBkopoFeOQMspZBqcqj6giYn1Bwey/0uiOPAcR+NQD0I2HC7rXzk91w==", "dev": true, "dependencies": { "cosmiconfig": "^9.0.0", @@ -13899,8 +13899,17 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "postcss": "^7.0.0 || ^8.0.1", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/postcss-loader/node_modules/semver": { @@ -28314,9 +28323,9 @@ "requires": {} }, "postcss-loader": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.0.0.tgz", - "integrity": "sha512-+RiNlmYd1aXYv6QSBOAu6n9eJYy0ydyXTfjljAJ3vFU6MMo2M552zTVcBpBH+R5aAeKaYVG1K9UEyAVsLL1Qjg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.0.tgz", + "integrity": "sha512-AbperNcX3rlob7Ay7A/HQcrofug1caABBkopoFeOQMspZBqcqj6giYn1Bwey/0uiOPAcR+NQD0I2HC7rXzk91w==", "dev": true, "requires": { "cosmiconfig": "^9.0.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index e0ed5234878c..cd66fcc5bede 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -100,7 +100,7 @@ "jest-environment-jsdom": "^29.7.0", "mini-css-extract-plugin": "^2.8.0", "postcss": "^8.4.33", - "postcss-loader": "^8.0.0", + "postcss-loader": "^8.1.0", "sass": "^1.70.0", "sass-loader": "^14.0.0", "style-loader": "^3.3.4", From 92eb25754aeb868203f4edcdb1ccd152e58e28d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 20:08:41 +0000 Subject: [PATCH 384/607] Bump @babel/core from 7.23.7 to 7.23.9 in /apps/block_scout_web/assets Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.23.7 to 7.23.9. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.9/packages/babel-core) --- updated-dependencies: - dependency-name: "@babel/core" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 126 +++++++++--------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 725f103be151..fb9ea3bf9c91 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -71,7 +71,7 @@ "xss": "^1.0.14" }, "devDependencies": { - "@babel/core": "^7.23.7", + "@babel/core": "^7.23.9", "@babel/preset-env": "^7.23.9", "autoprefixer": "^10.4.17", "babel-loader": "^9.1.3", @@ -249,20 +249,20 @@ } }, "node_modules/@babel/core": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", - "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.7", - "@babel/parser": "^7.23.6", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -602,13 +602,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.7.tgz", - "integrity": "sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", + "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6" + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9" }, "engines": { "node": ">=6.9.0" @@ -628,9 +628,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", - "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1946,22 +1946,22 @@ } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", - "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", "dependencies": { "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", @@ -1969,8 +1969,8 @@ "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1979,9 +1979,9 @@ } }, "node_modules/@babel/types": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", - "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -18015,20 +18015,20 @@ "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==" }, "@babel/core": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", - "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.7", - "@babel/parser": "^7.23.6", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -18278,13 +18278,13 @@ } }, "@babel/helpers": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.7.tgz", - "integrity": "sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", + "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", "requires": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6" + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9" } }, "@babel/highlight": { @@ -18298,9 +18298,9 @@ } }, "@babel/parser": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", - "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==" + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.23.3", @@ -19186,19 +19186,19 @@ } }, "@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" } }, "@babel/traverse": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", - "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", "requires": { "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", @@ -19206,16 +19206,16 @@ "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", "debug": "^4.3.1", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", - "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", "requires": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index e0ed5234878c..3bff43e758e9 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -83,7 +83,7 @@ "xss": "^1.0.14" }, "devDependencies": { - "@babel/core": "^7.23.7", + "@babel/core": "^7.23.9", "@babel/preset-env": "^7.23.9", "autoprefixer": "^10.4.17", "babel-loader": "^9.1.3", From ba5086ec1134b0916f7d0251bc5ce2d82a67d83a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 07:07:19 +0000 Subject: [PATCH 385/607] Bump css-loader from 6.9.1 to 6.10.0 in /apps/block_scout_web/assets Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 6.9.1 to 6.10.0. - [Release notes](https://github.com/webpack-contrib/css-loader/releases) - [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/css-loader/compare/v6.9.1...v6.10.0) --- updated-dependencies: - dependency-name: css-loader dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 23 +++++++++++++------ apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 9d2045250d9f..22adc00d82a7 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -76,7 +76,7 @@ "autoprefixer": "^10.4.17", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", - "css-loader": "^6.9.1", + "css-loader": "^6.10.0", "css-minimizer-webpack-plugin": "^6.0.0", "eslint": "^8.56.0", "eslint-config-standard": "^17.1.0", @@ -6237,9 +6237,9 @@ } }, "node_modules/css-loader": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.9.1.tgz", - "integrity": "sha512-OzABOh0+26JKFdMzlK6PY1u5Zx8+Ck7CVRlcGNZoY9qwJjdfu2VWFuprTIpPW+Av5TZTVViYWcFQaEEQURLknQ==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz", + "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", @@ -6259,7 +6259,16 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/css-loader/node_modules/semver": { @@ -22480,9 +22489,9 @@ "requires": {} }, "css-loader": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.9.1.tgz", - "integrity": "sha512-OzABOh0+26JKFdMzlK6PY1u5Zx8+Ck7CVRlcGNZoY9qwJjdfu2VWFuprTIpPW+Av5TZTVViYWcFQaEEQURLknQ==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz", + "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==", "dev": true, "requires": { "icss-utils": "^5.1.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index dcd50b1b1505..b563a289770a 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -88,7 +88,7 @@ "autoprefixer": "^10.4.17", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", - "css-loader": "^6.9.1", + "css-loader": "^6.10.0", "css-minimizer-webpack-plugin": "^6.0.0", "eslint": "^8.56.0", "eslint-config-standard": "^17.1.0", From 4df72137236f7d277fae9d9a383e192efa830c17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 07:07:33 +0000 Subject: [PATCH 386/607] Bump sass-loader from 14.0.0 to 14.1.0 in /apps/block_scout_web/assets Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 14.0.0 to 14.1.0. - [Release notes](https://github.com/webpack-contrib/sass-loader/releases) - [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/sass-loader/compare/v14.0.0...v14.1.0) --- updated-dependencies: - dependency-name: sass-loader dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 21 ++++++++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 9d2045250d9f..4f64b657dd16 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -90,7 +90,7 @@ "postcss": "^8.4.33", "postcss-loader": "^8.1.0", "sass": "^1.70.0", - "sass-loader": "^14.0.0", + "sass-loader": "^14.1.0", "style-loader": "^3.3.4", "webpack": "^5.89.0", "webpack-cli": "^5.1.4" @@ -15232,9 +15232,9 @@ } }, "node_modules/sass-loader": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.0.0.tgz", - "integrity": "sha512-oceP9wWbep/yRJ2+sMbCzk0UsXsDzdNis+N8nu9i5GwPXjy6v3DNB6TqfJLSpPO9k4+B8x8p/CEgjA9ZLkoLug==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.0.tgz", + "integrity": "sha512-LS2mLeFWA+orYxHNu+O18Xe4jR0kyamNOOUsE3NyBP4DvIL+8stHpNX0arYTItdPe80kluIiJ7Wfe/9iHSRO0Q==", "dev": true, "dependencies": { "neo-async": "^2.6.2" @@ -15247,12 +15247,16 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", "sass": "^1.3.0", "sass-embedded": "*", "webpack": "^5.0.0" }, "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, "node-sass": { "optional": true }, @@ -15261,6 +15265,9 @@ }, "sass-embedded": { "optional": true + }, + "webpack": { + "optional": true } } }, @@ -29289,9 +29296,9 @@ } }, "sass-loader": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.0.0.tgz", - "integrity": "sha512-oceP9wWbep/yRJ2+sMbCzk0UsXsDzdNis+N8nu9i5GwPXjy6v3DNB6TqfJLSpPO9k4+B8x8p/CEgjA9ZLkoLug==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.0.tgz", + "integrity": "sha512-LS2mLeFWA+orYxHNu+O18Xe4jR0kyamNOOUsE3NyBP4DvIL+8stHpNX0arYTItdPe80kluIiJ7Wfe/9iHSRO0Q==", "dev": true, "requires": { "neo-async": "^2.6.2" diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index dcd50b1b1505..84c56b63820e 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -102,7 +102,7 @@ "postcss": "^8.4.33", "postcss-loader": "^8.1.0", "sass": "^1.70.0", - "sass-loader": "^14.0.0", + "sass-loader": "^14.1.0", "style-loader": "^3.3.4", "webpack": "^5.89.0", "webpack-cli": "^5.1.4" From 3f9d41596c9a907447340166e8fc9bbd31239651 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 09:01:29 +0000 Subject: [PATCH 387/607] Bump webpack from 5.89.0 to 5.90.1 in /apps/block_scout_web/assets Bumps [webpack](https://github.com/webpack/webpack) from 5.89.0 to 5.90.1. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.89.0...v5.90.1) --- updated-dependencies: - dependency-name: webpack dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 102 +++++++++--------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index b4a1a0db9e51..7344bedebb7f 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -92,7 +92,7 @@ "sass": "^1.70.0", "sass-loader": "^14.1.0", "style-loader": "^3.3.4", - "webpack": "^5.89.0", + "webpack": "^5.90.1", "webpack-cli": "^5.1.4" }, "engines": { @@ -3327,9 +3327,9 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", - "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", @@ -3617,9 +3617,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "node_modules/@types/graceful-fs": { @@ -16203,13 +16203,13 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/terser": { - "version": "5.16.9", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.9.tgz", - "integrity": "sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg==", + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", + "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", "dev": true, "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -16221,16 +16221,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz", - "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", - "terser": "^5.16.5" + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -16255,9 +16255,9 @@ } }, "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.8", @@ -17369,19 +17369,19 @@ } }, "node_modules/webpack": { - "version": "5.89.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", - "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "version": "5.90.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz", + "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", + "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.11.5", "@webassemblyjs/wasm-edit": "^1.11.5", "@webassemblyjs/wasm-parser": "^1.11.5", "acorn": "^8.7.1", "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.15.0", "es-module-lexer": "^1.2.1", @@ -17395,7 +17395,7 @@ "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", + "terser-webpack-plugin": "^5.3.10", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, @@ -20172,9 +20172,9 @@ "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" }, "@jridgewell/source-map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", - "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "dev": true, "requires": { "@jridgewell/gen-mapping": "^0.3.0", @@ -20403,9 +20403,9 @@ } }, "@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "@types/graceful-fs": { @@ -29998,13 +29998,13 @@ } }, "terser": { - "version": "5.16.9", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.9.tgz", - "integrity": "sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg==", + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", + "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", "dev": true, "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -30018,22 +30018,22 @@ } }, "terser-webpack-plugin": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz", - "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, "requires": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", - "terser": "^5.16.5" + "terser": "^5.26.0" }, "dependencies": { "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "requires": { "@types/json-schema": "^7.0.8", @@ -30935,19 +30935,19 @@ "dev": true }, "webpack": { - "version": "5.89.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", - "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "version": "5.90.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz", + "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", + "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.11.5", "@webassemblyjs/wasm-edit": "^1.11.5", "@webassemblyjs/wasm-parser": "^1.11.5", "acorn": "^8.7.1", "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.15.0", "es-module-lexer": "^1.2.1", @@ -30961,7 +30961,7 @@ "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", + "terser-webpack-plugin": "^5.3.10", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 6341e42baa01..94d5a38a4146 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -104,7 +104,7 @@ "sass": "^1.70.0", "sass-loader": "^14.1.0", "style-loader": "^3.3.4", - "webpack": "^5.89.0", + "webpack": "^5.90.1", "webpack-cli": "^5.1.4" }, "jest": { From 135859a89b74d76b7591ecfeb718762ea463a490 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Sat, 3 Feb 2024 13:43:23 +0300 Subject: [PATCH 388/607] Include null gas price txs in fee calculations --- CHANGELOG.md | 2 ++ apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ea300b1190e..6053bdc2ef12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Fixes +- [#9317](https://github.com/blockscout/blockscout/pull/9317) - Include null gas price txs in fee calculations + ### Chore
diff --git a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex index d09ce6d41cb0..e2066201a0c4 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex @@ -98,7 +98,7 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do transaction in Transaction, where: transaction.block_consensus == true, where: transaction.status == ^1, - where: transaction.gas_price > ^0, + where: is_nil(transaction.gas_price) or transaction.gas_price > ^0, where: transaction.block_number > ^from_block, group_by: transaction.block_number, order_by: [desc: transaction.block_number], @@ -170,7 +170,7 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do left_join: transaction in assoc(block, :transactions), where: block.consensus == true, where: transaction.status == ^1, - where: transaction.gas_price > ^0, + where: is_nil(transaction.gas_price) or transaction.gas_price > ^0, where: transaction.block_number > ^from_block, group_by: transaction.block_number, order_by: [desc: transaction.block_number], From b42f8e38677b378d612c464bd5aeb7aff740e0aa Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 6 Feb 2024 16:18:19 +0400 Subject: [PATCH 389/607] fix: more review comments --- .../lib/block_scout_web/chain.ex | 2 +- .../controllers/chain_controller.ex | 22 +++++++++++-------- apps/explorer/lib/explorer/chain/search.ex | 4 ++-- .../lib/explorer/chain/user_operation.ex | 2 +- .../lib/indexer/fetcher/beacon/client.ex | 2 ++ docker-compose/envs/common-blockscout.env | 2 +- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index 489e8c79c476..c1b9743c94d0 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -688,7 +688,7 @@ defmodule BlockScoutWeb.Chain do end defp hash_to_user_operation(hash) do - if UserOperation.user_operations_enabled?() do + if UserOperation.enabled?() do UserOperation.hash_to_user_operation(hash) else {:error, :not_found} diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex index eabc844c7d01..41bd04b4a536 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex @@ -67,19 +67,19 @@ defmodule BlockScoutWeb.ChainController do end def search(conn, %{"q" => query}) do + search_path = + conn + |> search_path(:search_results, q: query) + |> Controller.full_path() + query |> String.trim() |> BlockScoutWeb.Chain.from_param() |> case do {:ok, item} -> - redirect_search_results(conn, item) + redirect_search_results(conn, item, search_path) {:error, :not_found} -> - search_path = - conn - |> search_path(:search_results, q: query) - |> Controller.full_path() - redirect(conn, to: search_path) end end @@ -150,7 +150,7 @@ defmodule BlockScoutWeb.ChainController do end end - defp redirect_search_results(conn, %Address{} = item) do + defp redirect_search_results(conn, %Address{} = item, _search_path) do address_path = conn |> address_path(:show, item) @@ -159,7 +159,7 @@ defmodule BlockScoutWeb.ChainController do redirect(conn, to: address_path) end - defp redirect_search_results(conn, %Block{} = item) do + defp redirect_search_results(conn, %Block{} = item, _search_path) do block_path = conn |> block_path(:show, item) @@ -168,7 +168,7 @@ defmodule BlockScoutWeb.ChainController do redirect(conn, to: block_path) end - defp redirect_search_results(conn, %Transaction{} = item) do + defp redirect_search_results(conn, %Transaction{} = item, _search_path) do transaction_path = conn |> transaction_path(:show, item) @@ -176,4 +176,8 @@ defmodule BlockScoutWeb.ChainController do redirect(conn, to: transaction_path) end + + defp redirect_search_results(conn, _item, search_path) do + redirect(conn, to: search_path) + end end diff --git a/apps/explorer/lib/explorer/chain/search.ex b/apps/explorer/lib/explorer/chain/search.ex index 36189b422a4e..653418299627 100644 --- a/apps/explorer/lib/explorer/chain/search.ex +++ b/apps/explorer/lib/explorer/chain/search.ex @@ -108,7 +108,7 @@ defmodule Explorer.Chain.Search do |> union(^block_query) tx_block_op_query = - if UserOperation.user_operations_enabled?() do + if UserOperation.enabled?() do user_operation_query = search_user_operation_query(string) tx_block_query @@ -194,7 +194,7 @@ defmodule Explorer.Chain.Search do end op_result = - if valid_full_hash?(search_query) && UserOperation.user_operations_enabled?() do + if valid_full_hash?(search_query) && UserOperation.enabled?() do search_query |> search_user_operation_query() |> select_repo(options).all() diff --git a/apps/explorer/lib/explorer/chain/user_operation.ex b/apps/explorer/lib/explorer/chain/user_operation.ex index 75300f6ea5fc..dd4b62bf83b5 100644 --- a/apps/explorer/lib/explorer/chain/user_operation.ex +++ b/apps/explorer/lib/explorer/chain/user_operation.ex @@ -67,7 +67,7 @@ defmodule Explorer.Chain.UserOperation do end end - def user_operations_enabled? do + def enabled? do Microservice.check_enabled(Explorer.MicroserviceInterfaces.AccountAbstraction) == :ok end end diff --git a/apps/indexer/lib/indexer/fetcher/beacon/client.ex b/apps/indexer/lib/indexer/fetcher/beacon/client.ex index 92ac93c60f46..8d5b79b63f11 100644 --- a/apps/indexer/lib/indexer/fetcher/beacon/client.ex +++ b/apps/indexer/lib/indexer/fetcher/beacon/client.ex @@ -38,6 +38,8 @@ defmodule Indexer.Fetcher.Beacon.Client do where `retry_indices_list` is the list of indices from `slots` for which the request failed and should be retried. """ @spec get_blob_sidecars([integer()]) :: {:ok, list(), [integer()]} + def get_blob_sidecars([]), do: {:ok, [], []} + def get_blob_sidecars(slots) when is_list(slots) do {oks, errors_with_retries} = slots diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 820a628655c3..268b58fd3c2f 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -186,7 +186,7 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false # INDEXER_ROOTSTOCK_DATA_FETCHER_BATCH_SIZE= # INDEXER_ROOTSTOCK_DATA_FETCHER_CONCURRENCY= # INDEXER_ROOTSTOCK_DATA_FETCHER_DB_BATCH_SIZE= -# INDEXER_BEACON_RPC_URL= +# INDEXER_BEACON_RPC_URL=http://localhost:5052 # INDEXER_DISABLE_BEACON_BLOB_FETCHER= # INDEXER_BEACON_BLOB_FETCHER_SLOT_DURATION=12 # INDEXER_BEACON_BLOB_FETCHER_REFERENCE_SLOT=8206822 From 1daf62bca9c50fdeda372c210276e8a0e5e26ad6 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 6 Feb 2024 17:09:49 +0400 Subject: [PATCH 390/607] chore: update env defaults --- docker-compose/envs/common-blockscout.env | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 268b58fd3c2f..725491055bf6 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -189,9 +189,9 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false # INDEXER_BEACON_RPC_URL=http://localhost:5052 # INDEXER_DISABLE_BEACON_BLOB_FETCHER= # INDEXER_BEACON_BLOB_FETCHER_SLOT_DURATION=12 -# INDEXER_BEACON_BLOB_FETCHER_REFERENCE_SLOT=8206822 -# INDEXER_BEACON_BLOB_FETCHER_REFERENCE_TIMESTAMP=1705305887 -# INDEXER_BEACON_BLOB_FETCHER_START_BLOCK=8206822 +# INDEXER_BEACON_BLOB_FETCHER_REFERENCE_SLOT=8000000 +# INDEXER_BEACON_BLOB_FETCHER_REFERENCE_TIMESTAMP=1702824023 +# INDEXER_BEACON_BLOB_FETCHER_START_BLOCK=19200000 # INDEXER_BEACON_BLOB_FETCHER_END_BLOCK=0 # TOKEN_ID_MIGRATION_FIRST_BLOCK= # TOKEN_ID_MIGRATION_CONCURRENCY= From 291ffa3e7d86d676cd7f3b8a26a8b6327a73b8fa Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 8 Feb 2024 15:37:48 +0300 Subject: [PATCH 391/607] Define BRIDGED_TOKENS_ENABLED env in Dockerfile --- CHANGELOG.md | 2 ++ docker/Dockerfile | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0f3dcfdb52b..e0906c68edf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Chore +- [#9361](https://github.com/blockscout/blockscout/pull/9361) - Define BRIDGED_TOKENS_ENABLED env in Dockerfile +
Dependencies version bumps diff --git a/docker/Dockerfile b/docker/Dockerfile index bc8bb581b4b8..1990e9ee0ed1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -23,6 +23,8 @@ ARG AMPLITUDE_API_KEY ARG AMPLITUDE_URL ARG CHAIN_TYPE ENV CHAIN_TYPE=${CHAIN_TYPE} +ARG BRIDGED_TOKENS_ENABLED +ENV BRIDGED_TOKENS_ENABLED=${BRIDGED_TOKENS_ENABLED} # Cache elixir deps ADD mix.exs mix.lock ./ @@ -70,6 +72,8 @@ ARG RELEASE_VERSION ENV RELEASE_VERSION=${RELEASE_VERSION} ARG CHAIN_TYPE ENV CHAIN_TYPE=${CHAIN_TYPE} +ARG BRIDGED_TOKENS_ENABLED +ENV BRIDGED_TOKENS_ENABLED=${BRIDGED_TOKENS_ENABLED} ARG BLOCKSCOUT_VERSION ENV BLOCKSCOUT_VERSION=${BLOCKSCOUT_VERSION} From 63266a2427765dbf4b00b2ce7ec47f3f23b346ed Mon Sep 17 00:00:00 2001 From: Nick Zenchik Date: Thu, 8 Feb 2024 18:47:34 +0300 Subject: [PATCH 392/607] Fixing stats DB connection vars --- docker-compose/services/stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose/services/stats.yml b/docker-compose/services/stats.yml index 15b1a8d1ff46..8abcd0bf3d97 100644 --- a/docker-compose/services/stats.yml +++ b/docker-compose/services/stats.yml @@ -50,7 +50,7 @@ services: env_file: - ../envs/common-stats.env environment: - - STATS__DB_URL=postgres://stats:n0uejXPl61ci6ldCuE2gQU5Y@stats-db:5432/stats - - STATS__BLOCKSCOUT_DB_URL=${STATS__BLOCKSCOUT_DB_URL-postgresql://blockscout:ceWb1MeLBEeOIfk65gU8EjF8@db:5432/blockscout} + - STATS__DB_URL=postgres://stats:n0uejXPl61ci6ldCuE2gQU5Y@stats-postgres:5432/stats + - STATS__BLOCKSCOUT_DB_URL=${STATS__BLOCKSCOUT_DB_URL:-postgresql://blockscout:ceWb1MeLBEeOIfk65gU8EjF8@db:5432/blockscout} - STATS__CREATE_DATABASE=true - STATS__RUN_MIGRATIONS=true From 1bc07ab829a1354d44d59541282658147823c5ca Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 8 Feb 2024 19:32:57 +0300 Subject: [PATCH 393/607] uniform namings for DB containers --- .github/workflows/config.yml | 2 +- docker-compose/docker-compose.yml | 6 +++--- docker-compose/envs/common-blockscout.env | 4 ++-- docker-compose/erigon.yml | 6 +++--- docker-compose/external-backend.yml | 4 ++-- docker-compose/external-db.yml | 6 +++--- docker-compose/external-frontend.yml | 6 +++--- docker-compose/ganache.yml | 6 +++--- docker-compose/geth-clique-consensus.yml | 6 +++--- docker-compose/geth.yml | 6 +++--- docker-compose/hardhat-network.yml | 6 +++--- docker-compose/services/redis.yml | 4 ++-- docker-compose/services/stats.yml | 4 ++-- docker/Makefile | 2 +- 14 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index a14ffaea2c26..bdbc610ff3db 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -601,7 +601,7 @@ jobs: - build-and-cache - matrix-builder services: - redis_db: + redis-db: image: "redis:alpine" ports: - 6379:6379 diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index fab930386386..d17409f98fc0 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -1,10 +1,10 @@ version: '3.9' services: - redis_db: + redis-db: extends: file: ./services/redis.yml - service: redis_db + service: redis-db db-init: extends: @@ -19,7 +19,7 @@ services: backend: depends_on: - db - - redis_db + - redis-db extends: file: ./services/backend.yml service: backend diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 9e1e6b268d93..adbac85d562d 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -226,7 +226,7 @@ RE_CAPTCHA_V3_SECRET_KEY= RE_CAPTCHA_V3_CLIENT_KEY= RE_CAPTCHA_DISABLED=false JSON_RPC= -# API_RATE_LIMIT_HAMMER_REDIS_URL=redis://redis_db:6379/1 +# API_RATE_LIMIT_HAMMER_REDIS_URL=redis://redis-db:6379/1 # API_RATE_LIMIT_IS_BLOCKSCOUT_BEHIND_PROXY=false API_RATE_LIMIT_UI_V2_TOKEN_TTL_IN_SECONDS=18000 FETCH_REWARDS_WAY=trace_block @@ -262,7 +262,7 @@ DECODE_NOT_A_CONTRACT_CALLS=true # ACCOUNT_WATCHLIST_ADDRESSES_LIMIT=15 ACCOUNT_CLOAK_KEY= ACCOUNT_ENABLED=false -ACCOUNT_REDIS_URL=redis://redis_db:6379 +ACCOUNT_REDIS_URL=redis://redis-db:6379 EIP_1559_ELASTICITY_MULTIPLIER=2 # MIXPANEL_TOKEN= # MIXPANEL_URL= diff --git a/docker-compose/erigon.yml b/docker-compose/erigon.yml index 0dbcef7f3e0e..dea8047f4666 100644 --- a/docker-compose/erigon.yml +++ b/docker-compose/erigon.yml @@ -1,10 +1,10 @@ version: '3.9' services: - redis_db: + redis-db: extends: file: ./services/redis.yml - service: redis_db + service: redis-db db-init: extends: @@ -19,7 +19,7 @@ services: backend: depends_on: - db - - redis_db + - redis-db extends: file: ./services/backend.yml service: backend diff --git a/docker-compose/external-backend.yml b/docker-compose/external-backend.yml index db8df3758037..37cd5783652b 100644 --- a/docker-compose/external-backend.yml +++ b/docker-compose/external-backend.yml @@ -1,10 +1,10 @@ version: '3.9' services: - redis_db: + redis-db: extends: file: ./services/redis.yml - service: redis_db + service: redis-db db-init: extends: diff --git a/docker-compose/external-db.yml b/docker-compose/external-db.yml index b40151c51dec..bd4ec6f069f0 100644 --- a/docker-compose/external-db.yml +++ b/docker-compose/external-db.yml @@ -1,14 +1,14 @@ version: '3.9' services: - redis_db: + redis-db: extends: file: ./services/redis.yml - service: redis_db + service: redis-db backend: depends_on: - - redis_db + - redis-db extends: file: ./services/backend.yml service: backend diff --git a/docker-compose/external-frontend.yml b/docker-compose/external-frontend.yml index f0d9f9f89298..bad65c4afa8c 100644 --- a/docker-compose/external-frontend.yml +++ b/docker-compose/external-frontend.yml @@ -1,10 +1,10 @@ version: '3.9' services: - redis_db: + redis-db: extends: file: ./services/redis.yml - service: redis_db + service: redis-db db-init: extends: @@ -19,7 +19,7 @@ services: backend: depends_on: - db - - redis_db + - redis-db extends: file: ./services/backend.yml service: backend diff --git a/docker-compose/ganache.yml b/docker-compose/ganache.yml index 6edb596669f4..dce2aed9c1e6 100644 --- a/docker-compose/ganache.yml +++ b/docker-compose/ganache.yml @@ -1,10 +1,10 @@ version: '3.9' services: - redis_db: + redis-db: extends: file: ./services/redis.yml - service: redis_db + service: redis-db db-init: extends: @@ -19,7 +19,7 @@ services: backend: depends_on: - db - - redis_db + - redis-db extends: file: ./services/backend.yml service: backend diff --git a/docker-compose/geth-clique-consensus.yml b/docker-compose/geth-clique-consensus.yml index ac1573849204..27fa83563529 100644 --- a/docker-compose/geth-clique-consensus.yml +++ b/docker-compose/geth-clique-consensus.yml @@ -1,10 +1,10 @@ version: '3.9' services: - redis_db: + redis-db: extends: file: ./services/redis.yml - service: redis_db + service: redis-db db-init: extends: @@ -19,7 +19,7 @@ services: backend: depends_on: - db - - redis_db + - redis-db extends: file: ./services/backend.yml service: backend diff --git a/docker-compose/geth.yml b/docker-compose/geth.yml index 611acec30606..0e55eb33d742 100644 --- a/docker-compose/geth.yml +++ b/docker-compose/geth.yml @@ -1,10 +1,10 @@ version: '3.9' services: - redis_db: + redis-db: extends: file: ./services/redis.yml - service: redis_db + service: redis-db db-init: extends: @@ -19,7 +19,7 @@ services: backend: depends_on: - db - - redis_db + - redis-db extends: file: ./services/backend.yml service: backend diff --git a/docker-compose/hardhat-network.yml b/docker-compose/hardhat-network.yml index b76b254a3e6f..74c29218f720 100644 --- a/docker-compose/hardhat-network.yml +++ b/docker-compose/hardhat-network.yml @@ -1,10 +1,10 @@ version: '3.9' services: - redis_db: + redis-db: extends: file: ./services/redis.yml - service: redis_db + service: redis-db db-init: extends: @@ -19,7 +19,7 @@ services: backend: depends_on: - db - - redis_db + - redis-db extends: file: ./services/backend.yml service: backend diff --git a/docker-compose/services/redis.yml b/docker-compose/services/redis.yml index 9760137f5bb5..93f616686de6 100644 --- a/docker-compose/services/redis.yml +++ b/docker-compose/services/redis.yml @@ -1,9 +1,9 @@ version: '3.9' services: - redis_db: + redis-db: image: 'redis:alpine' - container_name: redis_db + container_name: redis-db command: redis-server volumes: - ./redis-data:/data diff --git a/docker-compose/services/stats.yml b/docker-compose/services/stats.yml index 8abcd0bf3d97..b4c14aac2e1f 100644 --- a/docker-compose/services/stats.yml +++ b/docker-compose/services/stats.yml @@ -19,7 +19,7 @@ services: user: 2000:2000 shm_size: 256m restart: always - container_name: 'stats-postgres' + container_name: 'stats-db' command: postgres -c 'max_connections=200' environment: POSTGRES_DB: 'stats' @@ -50,7 +50,7 @@ services: env_file: - ../envs/common-stats.env environment: - - STATS__DB_URL=postgres://stats:n0uejXPl61ci6ldCuE2gQU5Y@stats-postgres:5432/stats + - STATS__DB_URL=postgres://stats:n0uejXPl61ci6ldCuE2gQU5Y@stats-db:5432/stats - STATS__BLOCKSCOUT_DB_URL=${STATS__BLOCKSCOUT_DB_URL:-postgresql://blockscout:ceWb1MeLBEeOIfk65gU8EjF8@db:5432/blockscout} - STATS__CREATE_DATABASE=true - STATS__RUN_MIGRATIONS=true diff --git a/docker/Makefile b/docker/Makefile index 6a9365e9136a..9d356b7d5512 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -7,7 +7,7 @@ FRONTEND_CONTAINER_NAME := frontend VISUALIZER_CONTAINER_NAME := visualizer SIG_PROVIDER_CONTAINER_NAME := sig-provider STATS_CONTAINER_NAME := stats -STATS_DB_CONTAINER_NAME := stats-postgres +STATS_DB_CONTAINER_NAME := stats-db PROXY_CONTAINER_NAME := proxy PG_CONTAINER_NAME := postgres RELEASE_VERSION ?= '6.1.0' From e7dac2b0fe6ae2b848f8b749f01f5b8e441e2ee9 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Fri, 9 Feb 2024 16:38:04 +0400 Subject: [PATCH 394/607] fix: transaction blobs order in API --- apps/explorer/lib/explorer/chain.ex | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index e3dafc7d8ab0..f425430ccd1a 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -2874,7 +2874,8 @@ defmodule Explorer.Chain do from( blob_transaction in BlobTransaction, select: %{ - hash: fragment("unnest(blob_versioned_hashes)") + hash: fragment("unnest(blob_versioned_hashes)"), + idx: fragment("generate_series(1, array_length(blob_versioned_hashes, 1))") }, where: blob_transaction.hash == ^transaction_hash ) @@ -2886,7 +2887,8 @@ defmodule Explorer.Chain do blob_data: blob.blob_data, kzg_commitment: blob.kzg_commitment, kzg_proof: blob.kzg_proof - } + }, + order_by: transaction_blob.idx ) query From 4d44b79e84d6fee2865489da5ccc14659f32adcc Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Fri, 9 Feb 2024 16:59:06 +0400 Subject: [PATCH 395/607] chore: move blob function out of chain.ex --- .../api/v2/transaction_controller.ex | 3 +- apps/explorer/lib/explorer/chain.ex | 41 ------------------- .../lib/explorer/chain/beacon/reader.ex | 41 +++++++++++++++++++ 3 files changed, 43 insertions(+), 42 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex index e0b0876493d8..8b19b1801108 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -29,6 +29,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do alias BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation, as: TransactionInterpretationService alias BlockScoutWeb.Models.TransactionStateHelper alias Explorer.Chain + alias Explorer.Chain.Beacon.Reader, as: BeaconReader alias Explorer.Chain.{Hash, Transaction} alias Explorer.Chain.Zkevm.Reader alias Indexer.Fetcher.FirstTraceOnDemand @@ -411,7 +412,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do with {:ok, _transaction, transaction_hash} <- validate_transaction(transaction_hash_string, params) do full_options = @api_true - blobs = Chain.transaction_to_blobs(transaction_hash, full_options) + blobs = BeaconReader.transaction_to_blobs(transaction_hash, full_options) conn |> put_status(200) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index f425430ccd1a..dff9886bcd63 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -66,8 +66,6 @@ defmodule Explorer.Chain do Withdrawal } - alias Explorer.Chain.Beacon.{Blob, BlobTransaction} - alias Explorer.Chain.Cache.{ BlockNumber, Blocks, @@ -2856,45 +2854,6 @@ defmodule Explorer.Chain do |> select_repo(options).all() end - @doc """ - Finds all `t:Explorer.Chain.Beacon.Blob.t/0`s for `t:Explorer.Chain.Transaction.t/0`. - - ## Options - - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is - `:required`, and the `t:Explorer.Chain.Log.t/0` has no associated record for that association, then the - `t:Explorer.Chain.Beacon.Blob.t/0` will not be included in the page `entries`. - - """ - @spec transaction_to_blobs(Hash.Full.t(), [necessity_by_association_option | api?]) :: [Blob.t()] - def transaction_to_blobs(transaction_hash, options \\ []) when is_list(options) do - query = - from( - transaction_blob in subquery( - from( - blob_transaction in BlobTransaction, - select: %{ - hash: fragment("unnest(blob_versioned_hashes)"), - idx: fragment("generate_series(1, array_length(blob_versioned_hashes, 1))") - }, - where: blob_transaction.hash == ^transaction_hash - ) - ), - left_join: blob in Blob, - on: blob.hash == transaction_blob.hash, - select: %{ - hash: type(transaction_blob.hash, Hash.Full), - blob_data: blob.blob_data, - kzg_commitment: blob.kzg_commitment, - kzg_proof: blob.kzg_proof - }, - order_by: transaction_blob.idx - ) - - query - |> select_repo(options).all() - end - @doc """ Converts `transaction` to the status of the `t:Explorer.Chain.Transaction.t/0` whether pending or collated. diff --git a/apps/explorer/lib/explorer/chain/beacon/reader.ex b/apps/explorer/lib/explorer/chain/beacon/reader.ex index 19a51ca0be22..9dd3623b35f8 100644 --- a/apps/explorer/lib/explorer/chain/beacon/reader.ex +++ b/apps/explorer/lib/explorer/chain/beacon/reader.ex @@ -60,6 +60,47 @@ defmodule Explorer.Chain.Beacon.Reader do end end + @doc """ + Finds all `t:Explorer.Chain.Beacon.Blob.t/0`s for `t:Explorer.Chain.Transaction.t/0`. + + Returns a list of `%Explorer.Chain.Beacon.Blob{}` belonging to the given `transaction_hash`. + + iex> blob = insert(:blob) + iex> %Explorer.Chain.Beacon.BlobTransaction{hash: transaction_hash} = insert(:blob_transaction, blob_versioned_hashes: [blob.hash]) + iex> blobs = Explorer.Chain.Beacon.Reader.transaction_to_blobs(transaction_hash) + iex> blobs == [%{hash: blob.hash, blob_data: blob.blob_data, kzg_commitment: blob.kzg_commitment, kzg_proof: blob.kzg_proof}] + true + + """ + @spec transaction_to_blobs(Hash.Full.t(), [Chain.api?()]) :: [Blob.t()] + def transaction_to_blobs(transaction_hash, options \\ []) when is_list(options) do + query = + from( + transaction_blob in subquery( + from( + blob_transaction in BlobTransaction, + select: %{ + hash: fragment("unnest(blob_versioned_hashes)"), + idx: fragment("generate_series(1, array_length(blob_versioned_hashes, 1))") + }, + where: blob_transaction.hash == ^transaction_hash + ) + ), + left_join: blob in Blob, + on: blob.hash == transaction_blob.hash, + select: %{ + hash: type(transaction_blob.hash, Hash.Full), + blob_data: blob.blob_data, + kzg_commitment: blob.kzg_commitment, + kzg_proof: blob.kzg_proof + }, + order_by: transaction_blob.idx + ) + + query + |> select_repo(options).all() + end + @doc """ Finds associated transaction hashes for the given blob `hash` identifier. Returns at most 10 matches. From b1f979587c5ee27187f48082e27cf511e174dbf9 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Fri, 17 Nov 2023 01:05:52 +0300 Subject: [PATCH 396/607] Fix dialyzer and add TypedEctoSchema --- .dialyzer-ignore | 8 - CHANGELOG.md | 1 + .../controllers/api/rpc/stats_controller.ex | 2 - .../controllers/api/v2/token_controller.ex | 7 +- .../controllers/api/v2/utils_controller.ex | 7 +- .../views/api/v2/address_view.ex | 14 +- .../views/api/v2/transaction_view.ex | 157 +++--- .../api/v2/token_controller_test.exs | 2 +- apps/explorer/lib/explorer/account/api/key.ex | 8 +- .../explorer/lib/explorer/account/api/plan.ex | 2 +- .../lib/explorer/account/custom_abi.ex | 12 +- .../explorer/lib/explorer/account/identity.ex | 10 +- .../explorer/account/public_tags_request.ex | 34 +- .../lib/explorer/account/tag_address.ex | 10 +- .../lib/explorer/account/tag_transaction.ex | 10 +- .../lib/explorer/account/watchlist.ex | 4 +- .../lib/explorer/account/watchlist_address.ex | 32 +- .../account/watchlist_notification.ex | 26 +- apps/explorer/lib/explorer/accounts/user.ex | 8 +- .../explorer/accounts/user/authenticate.ex | 6 +- .../explorer/accounts/user/registration.ex | 12 +- .../lib/explorer/accounts/user_contact.ex | 15 +- .../lib/explorer/admin/administrator.ex | 13 +- .../lib/explorer/application/constants.ex | 6 +- apps/explorer/lib/explorer/chain.ex | 6 +- apps/explorer/lib/explorer/chain/address.ex | 71 +-- .../explorer/chain/address/coin_balance.ex | 15 +- .../chain/address/coin_balance_daily.ex | 15 +- .../chain/address/current_token_balance.ex | 30 +- .../lib/explorer/chain/address/name.ex | 17 +- .../explorer/chain/address/token_balance.ex | 24 +- apps/explorer/lib/explorer/chain/block.ex | 154 +++--- .../explorer/chain/block/emission_reward.ex | 13 +- .../lib/explorer/chain/block/reward.ex | 21 +- .../chain/block/second_degree_relation.ex | 41 +- .../lib/explorer/chain/bridged_token.ex | 38 +- .../lib/explorer/chain/contract_method.ex | 8 +- .../chain/decompiled_smart_contract.ex | 9 +- .../explorer/chain/internal_transaction.ex | 50 +- apps/explorer/lib/explorer/chain/log.ex | 29 +- .../explorer/chain/pending_block_operation.ex | 16 +- .../explorer/chain/polygon_edge/deposit.ex | 15 +- .../chain/polygon_edge/deposit_execute.ex | 19 +- .../explorer/chain/polygon_edge/withdrawal.ex | 27 +- .../chain/polygon_edge/withdrawal_exit.ex | 17 +- .../lib/explorer/chain/shibarium/bridge.ex | 34 +- .../lib/explorer/chain/smart_contract.ex | 41 +- .../chain/smart_contract/audit_report.ex | 35 +- .../chain/smart_contract/external_library.ex | 4 +- .../proxy/verification_status.ex | 19 +- .../smart_contract/verification_status.ex | 17 +- .../chain/smart_contract_additional_source.ex | 16 +- apps/explorer/lib/explorer/chain/token.ex | 147 +++-- .../lib/explorer/chain/token/instance.ex | 24 +- .../lib/explorer/chain/token_transfer.ex | 65 +-- .../lib/explorer/chain/transaction.ex | 502 +++++++++--------- .../lib/explorer/chain/transaction/fork.ex | 16 +- .../transaction/history/transaction_stats.ex | 19 +- .../lib/explorer/chain/transaction_action.ex | 27 +- .../lib/explorer/chain/user_operation.ex | 15 +- apps/explorer/lib/explorer/chain/validator.ex | 4 +- .../explorer/lib/explorer/chain/withdrawal.ex | 25 +- .../explorer/chain/zkevm/batch_transaction.ex | 22 +- .../chain/zkevm/lifecycle_transaction.ex | 20 +- .../explorer/chain/zkevm/transaction_batch.ex | 21 +- .../explorer/counters/last_fetched_counter.ex | 10 +- .../lib/explorer/encrypted/address_hash.ex | 2 + .../explorer/lib/explorer/encrypted/binary.ex | 2 + .../explorer/encrypted/transaction_hash.ex | 2 + .../lib/explorer/market/market_history.ex | 22 +- .../lib/explorer/migrator/migration_status.ex | 2 +- apps/explorer/lib/explorer/schema.ex | 2 +- .../explorer/lib/explorer/tags/address_tag.ex | 10 +- .../lib/explorer/tags/address_to_tag.ex | 13 +- .../explorer/utility/event_notification.ex | 2 +- .../explorer/utility/missing_block_range.ex | 2 +- apps/explorer/mix.exs | 3 +- mix.lock | 1 + 78 files changed, 905 insertions(+), 1282 deletions(-) diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 8441fdc9d929..27142a9cbbe4 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -1,16 +1,8 @@ -:0: Unknown function 'Elixir.ExUnit.Callbacks':'__merge__'/3 -:0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2 -:0: Unknown type 'Elixir.Map':t/0 -:0: Unknown type 'Elixir.Hash':t/0 -:0: Unknown type 'Elixir.Address':t/0 lib/ethereum_jsonrpc/rolling_window.ex:171 lib/explorer/smart_contract/solidity/publisher_worker.ex:1 lib/explorer/smart_contract/vyper/publisher_worker.ex:1 lib/explorer/smart_contract/solidity/publisher_worker.ex:8 lib/explorer/smart_contract/vyper/publisher_worker.ex:8 -lib/block_scout_web/router.ex:1 -lib/block_scout_web/schema/types.ex:31 -lib/phoenix/router.ex:324 lib/phoenix/router.ex:402 lib/explorer/smart_contract/reader.ex:435 lib/explorer/exchange_rates/source.ex:139 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8daa52723a69..c5cd1366efec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Chore - [#9361](https://github.com/blockscout/blockscout/pull/9361) - Define BRIDGED_TOKENS_ENABLED env in Dockerfile +- [#8851](https://github.com/blockscout/blockscout/pull/8851) - Fix dialyzer and add TypedEctoSchema
Dependencies version bumps diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex index 4ed52cbebe62..8b766c7715b8 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex @@ -1,8 +1,6 @@ defmodule BlockScoutWeb.API.RPC.StatsController do use BlockScoutWeb, :controller - use Explorer.Schema - alias Explorer.{Chain, Etherscan, Market} alias Explorer.Chain.Cache.{AddressSum, AddressSumMinusBurnt} alias Explorer.Chain.Wei diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex index e29a2127d511..3a62f1de48e7 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex @@ -196,7 +196,12 @@ defmodule BlockScoutWeb.API.V2.TokenController do |> Chain.put_owner_to_token_instance(token, @api_true) {:error, :not_found} -> - %Instance{token_id: token_id, metadata: nil, owner: nil} + %Instance{ + token_id: Decimal.new(token_id), + metadata: nil, + owner: nil, + token_contract_address_hash: address_hash + } |> Instance.put_is_unique(token, @api_true) |> Chain.put_owner_to_token_instance(token, @api_true) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/utils_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/utils_controller.ex index 3fd6dd9500bf..90a10cff5900 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/utils_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/utils_controller.ex @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.API.V2.UtilsController do alias BlockScoutWeb.API.V2.TransactionView alias Explorer.Chain - alias Explorer.Chain.{Data, SmartContract, Transaction} + alias Explorer.Chain.{Address, Data, SmartContract, Transaction} @api_true [api?: true] @@ -22,7 +22,10 @@ defmodule BlockScoutWeb.API.V2.UtilsController do {decoded_input, _abi_acc, _methods_acc} = Transaction.decoded_input_data( - %Transaction{input: data, to_address: %{contract_code: "", smart_contract: smart_contract}}, + %Transaction{ + input: data, + to_address: %Address{contract_code: %Data{bytes: ""}, smart_contract: smart_contract} + }, @api_true ) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex index 3c8e52f7b1af..aafe02d9a90b 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex @@ -139,7 +139,8 @@ defmodule BlockScoutWeb.API.V2.AddressView do }) end - def prepare_token_balance(token_balance, fetch_token_instance? \\ false) do + @spec prepare_token_balance(Chain.Address.TokenBalance.t(), boolean()) :: map() + defp prepare_token_balance(token_balance, fetch_token_instance? \\ false) do %{ "value" => token_balance.value, "token" => TokenView.render("token.json", %{token: token_balance.token}), @@ -221,6 +222,12 @@ defmodule BlockScoutWeb.API.V2.AddressView do # TODO think about this approach mb refactor or mark deprecated for example. # Suggested solution: batch preload + @spec fetch_and_render_token_instance( + Decimal.t(), + Ecto.Schema.belongs_to(Chain.Token.t()) | nil, + Chain.Hash.Address.t(), + Chain.Address.TokenBalance.t() + ) :: map() def fetch_and_render_token_instance(token_id, token, address_hash, token_balance) do token_instance = case Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address( @@ -236,8 +243,9 @@ defmodule BlockScoutWeb.API.V2.AddressView do %Instance{ token_id: token_id, metadata: nil, - owner: %{hash: address_hash}, - current_token_balance: token_balance + owner: %Address{hash: address_hash}, + current_token_balance: token_balance, + token_contract_address_hash: token.contract_address_hash } |> Instance.put_is_unique(token, @api_true) end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index 34c340124950..ef55af3ba32b 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -8,7 +8,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do alias BlockScoutWeb.TransactionStateView alias Ecto.Association.NotLoaded alias Explorer.{Chain, Market} - alias Explorer.Chain.{Address, Block, Hash, InternalTransaction, Log, Token, Transaction, Wei} + alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Token, Transaction, Wei} alias Explorer.Chain.Block.Reward alias Explorer.Chain.PolygonEdge.Reader alias Explorer.Chain.Transaction.StateChange @@ -17,10 +17,8 @@ defmodule BlockScoutWeb.API.V2.TransactionView do import BlockScoutWeb.Account.AuthController, only: [current_user: 1] import Explorer.Chain.Transaction, only: [maybe_prepare_stability_fees: 1, bytes_to_address_hash: 1] - import Explorer.Helper, only: [decode_data: 2] @api_true [api?: true] - @suave_bid_event "0x83481d5b04dea534715acad673a8177a46fc93882760f36bdc16ccac439d504e" def render("message.json", assigns) do ApiView.render("message.json", assigns) @@ -482,105 +480,70 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end end - defp suave_fields(transaction, result, single_tx?, conn, watchlist_names) do - if is_nil(transaction.execution_node_hash) do - result - else - {[wrapped_decoded_input], _, _} = - decode_transactions( - [ - %Transaction{ - to_address: transaction.wrapped_to_address, - input: transaction.wrapped_input, - hash: transaction.wrapped_hash - } - ], - false - ) + if Application.compile_env(:explorer, :chain_type) != "suave" do + defp suave_fields(_transaction, result, _single_tx?, _conn, _watchlist_names), do: result + else + defp suave_fields(transaction, result, single_tx?, conn, watchlist_names) do + if is_nil(transaction.execution_node_hash) do + result + else + {[wrapped_decoded_input], _, _} = + decode_transactions( + [ + %Transaction{ + to_address: transaction.wrapped_to_address, + input: transaction.wrapped_input, + hash: transaction.wrapped_hash + } + ], + false + ) - result - |> Map.put("allowed_peekers", suave_parse_allowed_peekers(transaction.logs)) - |> Map.put( - "execution_node", - Helper.address_with_info( - single_tx? && conn, - transaction.execution_node, - transaction.execution_node_hash, - single_tx?, - watchlist_names - ) - ) - |> Map.put("wrapped", %{ - "type" => transaction.wrapped_type, - "nonce" => transaction.wrapped_nonce, - "to" => + result + |> Map.put("allowed_peekers", Transaction.suave_parse_allowed_peekers(transaction.logs)) + |> Map.put( + "execution_node", Helper.address_with_info( single_tx? && conn, - transaction.wrapped_to_address, - transaction.wrapped_to_address_hash, + transaction.execution_node, + transaction.execution_node_hash, single_tx?, watchlist_names - ), - "gas_limit" => transaction.wrapped_gas, - "gas_price" => transaction.wrapped_gas_price, - "fee" => - format_fee( - Chain.fee( - %Transaction{gas: transaction.wrapped_gas, gas_price: transaction.wrapped_gas_price, gas_used: nil}, - :wei - ) - ), - "max_priority_fee_per_gas" => transaction.wrapped_max_priority_fee_per_gas, - "max_fee_per_gas" => transaction.wrapped_max_fee_per_gas, - "value" => transaction.wrapped_value, - "hash" => transaction.wrapped_hash, - "method" => - method_name( - %Transaction{to_address: transaction.wrapped_to_address, input: transaction.wrapped_input}, - wrapped_decoded_input - ), - "decoded_input" => decoded_input(wrapped_decoded_input), - "raw_input" => transaction.wrapped_input - }) - end - end - - defp suave_parse_allowed_peekers(logs) do - suave_bid_contracts = - Application.get_all_env(:explorer)[Transaction][:suave_bid_contracts] - |> String.split(",") - |> Enum.map(fn sbc -> String.downcase(String.trim(sbc)) end) - - bid_event = - Enum.find(logs, fn log -> - sanitize_log_first_topic(log.first_topic) == @suave_bid_event && - Enum.member?(suave_bid_contracts, String.downcase(Hash.to_string(log.address_hash))) - end) - - if is_nil(bid_event) do - [] - else - [_bid_id, _decryption_condition, allowed_peekers] = - decode_data(bid_event.data, [{:bytes, 16}, {:uint, 64}, {:array, :address}]) - - Enum.map(allowed_peekers, fn peeker -> - "0x" <> Base.encode16(peeker, case: :lower) - end) - end - end - - defp sanitize_log_first_topic(first_topic) do - if is_nil(first_topic) do - "" - else - sanitized = - if is_binary(first_topic) do - first_topic - else - Hash.to_string(first_topic) - end - - String.downcase(sanitized) + ) + ) + |> Map.put("wrapped", %{ + "type" => transaction.wrapped_type, + "nonce" => transaction.wrapped_nonce, + "to" => + Helper.address_with_info( + single_tx? && conn, + transaction.wrapped_to_address, + transaction.wrapped_to_address_hash, + single_tx?, + watchlist_names + ), + "gas_limit" => transaction.wrapped_gas, + "gas_price" => transaction.wrapped_gas_price, + "fee" => + format_fee( + Chain.fee( + %Transaction{gas: transaction.wrapped_gas, gas_price: transaction.wrapped_gas_price, gas_used: nil}, + :wei + ) + ), + "max_priority_fee_per_gas" => transaction.wrapped_max_priority_fee_per_gas, + "max_fee_per_gas" => transaction.wrapped_max_fee_per_gas, + "value" => transaction.wrapped_value, + "hash" => transaction.wrapped_hash, + "method" => + method_name( + %Transaction{to_address: transaction.wrapped_to_address, input: transaction.wrapped_input}, + wrapped_decoded_input + ), + "decoded_input" => decoded_input(wrapped_decoded_input), + "raw_input" => transaction.wrapped_input + }) + end end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs index 2dec0632fd3d..0cc36ad595f6 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs @@ -1022,7 +1022,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do assert %{ "animation_url" => nil, "external_app_url" => nil, - "id" => 0, + "id" => "0", "image_url" => nil, "is_unique" => true, "metadata" => nil, diff --git a/apps/explorer/lib/explorer/account/api/key.ex b/apps/explorer/lib/explorer/account/api/key.ex index ab80d41ec234..5330ab98a461 100644 --- a/apps/explorer/lib/explorer/account/api/key.ex +++ b/apps/explorer/lib/explorer/account/api/key.ex @@ -13,10 +13,10 @@ defmodule Explorer.Account.Api.Key do @max_key_per_account 3 @primary_key false - schema "account_api_keys" do - field(:name, :string) - field(:value, UUID, primary_key: true) - belongs_to(:identity, Identity) + typed_schema "account_api_keys" do + field(:name, :string, null: false) + field(:value, UUID, primary_key: true, null: false) + belongs_to(:identity, Identity, null: false) timestamps() end diff --git a/apps/explorer/lib/explorer/account/api/plan.ex b/apps/explorer/lib/explorer/account/api/plan.ex index b89b33bc9cb7..599f133caa2f 100644 --- a/apps/explorer/lib/explorer/account/api/plan.ex +++ b/apps/explorer/lib/explorer/account/api/plan.ex @@ -4,7 +4,7 @@ defmodule Explorer.Account.Api.Plan do """ use Explorer.Schema - schema "account_api_plans" do + typed_schema "account_api_plans" do field(:name, :string) field(:max_req_per_second, :integer) diff --git a/apps/explorer/lib/explorer/account/custom_abi.ex b/apps/explorer/lib/explorer/account/custom_abi.ex index 58da24a50c99..5784c8586a45 100644 --- a/apps/explorer/lib/explorer/account/custom_abi.ex +++ b/apps/explorer/lib/explorer/account/custom_abi.ex @@ -14,15 +14,15 @@ defmodule Explorer.Account.CustomABI do @max_abis_per_account 15 - schema "account_custom_abis" do - field(:abi, {:array, :map}) + typed_schema "account_custom_abis" do + field(:abi, {:array, :map}, null: false) field(:given_abi, :string, virtual: true) field(:abi_validating_error, :string, virtual: true) - field(:address_hash_hash, Cloak.Ecto.SHA256) - field(:address_hash, Explorer.Encrypted.AddressHash) - field(:name, Explorer.Encrypted.Binary) + field(:address_hash_hash, Cloak.Ecto.SHA256) :: binary() | nil + field(:address_hash, Explorer.Encrypted.AddressHash, null: false) + field(:name, Explorer.Encrypted.Binary, null: false) - belongs_to(:identity, Identity) + belongs_to(:identity, Identity, null: false) timestamps() end diff --git a/apps/explorer/lib/explorer/account/identity.ex b/apps/explorer/lib/explorer/account/identity.ex index 05171bc9f79d..1fd3300db5c1 100644 --- a/apps/explorer/lib/explorer/account/identity.ex +++ b/apps/explorer/lib/explorer/account/identity.ex @@ -10,11 +10,11 @@ defmodule Explorer.Account.Identity do alias Explorer.Account.Api.Plan alias Explorer.Account.{TagAddress, Watchlist} - schema "account_identities" do - field(:uid_hash, Cloak.Ecto.SHA256) - field(:uid, Explorer.Encrypted.Binary) - field(:email, Explorer.Encrypted.Binary) - field(:name, Explorer.Encrypted.Binary) + typed_schema "account_identities" do + field(:uid_hash, Cloak.Ecto.SHA256) :: binary() | nil + field(:uid, Explorer.Encrypted.Binary, null: false) + field(:email, Explorer.Encrypted.Binary, null: false) + field(:name, Explorer.Encrypted.Binary, null: false) field(:nickname, Explorer.Encrypted.Binary) field(:avatar, Explorer.Encrypted.Binary) field(:verification_email_sent_at, :utc_datetime_usec) diff --git a/apps/explorer/lib/explorer/account/public_tags_request.ex b/apps/explorer/lib/explorer/account/public_tags_request.ex index d892d15340b4..8cdf5e56fbda 100644 --- a/apps/explorer/lib/explorer/account/public_tags_request.ex +++ b/apps/explorer/lib/explorer/account/public_tags_request.ex @@ -19,37 +19,21 @@ defmodule Explorer.Account.PublicTagsRequest do @max_tags_per_request 2 @max_tag_length 35 - @type t :: %__MODULE__{ - company: String.t(), - website: String.t(), - tags: String.t(), - addresses: [Hash.Address.t()], - description: String.t(), - additional_comment: String.t(), - request_type: String.t(), - is_owner: boolean(), - remove_reason: String.t(), - request_id: String.t(), - full_name: String.t(), - email: String.t(), - identity_id: integer() - } - - schema("account_public_tags_requests") do + typed_schema "account_public_tags_requests" do field(:company, :string) field(:website, :string) - field(:tags, :string) - field(:addresses, {:array, Hash.Address}) + field(:tags, :string, null: false) + field(:addresses, {:array, Hash.Address}, null: false) field(:description, :string) - field(:additional_comment, :string) - field(:request_type, :string) - field(:is_owner, :boolean, default: true) + field(:additional_comment, :string, null: false) + field(:request_type, :string, null: false) + field(:is_owner, :boolean, default: true, null: false) field(:remove_reason, :string) field(:request_id, :string) - field(:full_name, Explorer.Encrypted.Binary) - field(:email, Explorer.Encrypted.Binary) + field(:full_name, Explorer.Encrypted.Binary, null: false) + field(:email, Explorer.Encrypted.Binary, null: false) - belongs_to(:identity, Identity) + belongs_to(:identity, Identity, null: false) timestamps() end diff --git a/apps/explorer/lib/explorer/account/tag_address.ex b/apps/explorer/lib/explorer/account/tag_address.ex index 5d38db529a6d..6750e8a47f21 100644 --- a/apps/explorer/lib/explorer/account/tag_address.ex +++ b/apps/explorer/lib/explorer/account/tag_address.ex @@ -14,12 +14,12 @@ defmodule Explorer.Account.TagAddress do import Explorer.Chain, only: [hash_to_lower_case_string: 1] - schema "account_tag_addresses" do - field(:address_hash_hash, Cloak.Ecto.SHA256) - field(:name, Explorer.Encrypted.Binary) - field(:address_hash, Explorer.Encrypted.AddressHash) + typed_schema "account_tag_addresses" do + field(:address_hash_hash, Cloak.Ecto.SHA256) :: binary() | nil + field(:name, Explorer.Encrypted.Binary, null: false) + field(:address_hash, Explorer.Encrypted.AddressHash, null: false) - belongs_to(:identity, Identity) + belongs_to(:identity, Identity, null: false) timestamps() end diff --git a/apps/explorer/lib/explorer/account/tag_transaction.ex b/apps/explorer/lib/explorer/account/tag_transaction.ex index b821fffcf9a0..f2c51672710d 100644 --- a/apps/explorer/lib/explorer/account/tag_transaction.ex +++ b/apps/explorer/lib/explorer/account/tag_transaction.ex @@ -12,12 +12,12 @@ defmodule Explorer.Account.TagTransaction do alias Explorer.{Chain, PagingOptions, Repo} import Explorer.Chain, only: [hash_to_lower_case_string: 1] - schema "account_tag_transactions" do - field(:tx_hash_hash, Cloak.Ecto.SHA256) - field(:name, Explorer.Encrypted.Binary) - field(:tx_hash, Explorer.Encrypted.TransactionHash) + typed_schema "account_tag_transactions" do + field(:tx_hash_hash, Cloak.Ecto.SHA256) :: binary() | nil + field(:name, Explorer.Encrypted.Binary, null: false) + field(:tx_hash, Explorer.Encrypted.TransactionHash, null: false) - belongs_to(:identity, Identity) + belongs_to(:identity, Identity, null: false) timestamps() end diff --git a/apps/explorer/lib/explorer/account/watchlist.ex b/apps/explorer/lib/explorer/account/watchlist.ex index cd6998b83f77..0130ac6fcf6d 100644 --- a/apps/explorer/lib/explorer/account/watchlist.ex +++ b/apps/explorer/lib/explorer/account/watchlist.ex @@ -10,8 +10,8 @@ defmodule Explorer.Account.Watchlist do alias Explorer.Account.{Identity, WatchlistAddress} @derive {Jason.Encoder, only: [:name, :watchlist_addresses]} - schema "account_watchlists" do - field(:name, :string) + typed_schema "account_watchlists" do + field(:name, :string, null: false) belongs_to(:identity, Identity) has_many(:watchlist_addresses, WatchlistAddress) diff --git a/apps/explorer/lib/explorer/account/watchlist_address.ex b/apps/explorer/lib/explorer/account/watchlist_address.ex index e488ac758e13..86c0923704b4 100644 --- a/apps/explorer/lib/explorer/account/watchlist_address.ex +++ b/apps/explorer/lib/explorer/account/watchlist_address.ex @@ -15,22 +15,22 @@ defmodule Explorer.Account.WatchlistAddress do import Explorer.Chain, only: [hash_to_lower_case_string: 1] - schema "account_watchlist_addresses" do - field(:address_hash_hash, Cloak.Ecto.SHA256) - field(:name, Explorer.Encrypted.Binary) - field(:address_hash, Explorer.Encrypted.AddressHash) - - belongs_to(:watchlist, Watchlist) - - field(:watch_coin_input, :boolean, default: true) - field(:watch_coin_output, :boolean, default: true) - field(:watch_erc_20_input, :boolean, default: true) - field(:watch_erc_20_output, :boolean, default: true) - field(:watch_erc_721_input, :boolean, default: true) - field(:watch_erc_721_output, :boolean, default: true) - field(:watch_erc_1155_input, :boolean, default: true) - field(:watch_erc_1155_output, :boolean, default: true) - field(:notify_email, :boolean, default: true) + typed_schema "account_watchlist_addresses" do + field(:address_hash_hash, Cloak.Ecto.SHA256) :: binary() | nil + field(:name, Explorer.Encrypted.Binary, null: false) + field(:address_hash, Explorer.Encrypted.AddressHash, null: false) + + belongs_to(:watchlist, Watchlist, null: false) + + field(:watch_coin_input, :boolean, default: true, null: false) + field(:watch_coin_output, :boolean, default: true, null: false) + field(:watch_erc_20_input, :boolean, default: true, null: false) + field(:watch_erc_20_output, :boolean, default: true, null: false) + field(:watch_erc_721_input, :boolean, default: true, null: false) + field(:watch_erc_721_output, :boolean, default: true, null: false) + field(:watch_erc_1155_input, :boolean, default: true, null: false) + field(:watch_erc_1155_output, :boolean, default: true, null: false) + field(:notify_email, :boolean, default: true, null: false) field(:notify_epns, :boolean) field(:notify_feed, :boolean) field(:notify_inapp, :boolean) diff --git a/apps/explorer/lib/explorer/account/watchlist_notification.ex b/apps/explorer/lib/explorer/account/watchlist_notification.ex index cc45561073ef..fe28c7efac9c 100644 --- a/apps/explorer/lib/explorer/account/watchlist_notification.ex +++ b/apps/explorer/lib/explorer/account/watchlist_notification.ex @@ -12,17 +12,17 @@ defmodule Explorer.Account.WatchlistNotification do alias Explorer.Repo alias Explorer.Account.{Watchlist, WatchlistAddress} - schema "account_watchlist_notifications" do - field(:amount, :decimal) - field(:block_number, :integer) - field(:direction, :string) - field(:method, :string) - field(:tx_fee, :decimal) - field(:type, :string) - field(:viewed_at, :integer) - field(:name, Explorer.Encrypted.Binary) + typed_schema "account_watchlist_notifications" do + field(:amount, :decimal, null: false) + field(:block_number, :integer, null: false) + field(:direction, :string, null: false) + field(:method, :string, null: false) + field(:tx_fee, :decimal, null: false) + field(:type, :string, null: false) + field(:viewed_at, :integer, null: false) + field(:name, Explorer.Encrypted.Binary, null: false) field(:subject, Explorer.Encrypted.Binary) - field(:subject_hash, Cloak.Ecto.SHA256) + field(:subject_hash, Cloak.Ecto.SHA256) :: binary() | nil belongs_to(:watchlist_address, WatchlistAddress) belongs_to(:watchlist, Watchlist) @@ -31,9 +31,9 @@ defmodule Explorer.Account.WatchlistNotification do field(:to_address_hash, Explorer.Encrypted.AddressHash) field(:transaction_hash, Explorer.Encrypted.TransactionHash) - field(:from_address_hash_hash, Cloak.Ecto.SHA256) - field(:to_address_hash_hash, Cloak.Ecto.SHA256) - field(:transaction_hash_hash, Cloak.Ecto.SHA256) + field(:from_address_hash_hash, Cloak.Ecto.SHA256) :: binary() | nil + field(:to_address_hash_hash, Cloak.Ecto.SHA256) :: binary() | nil + field(:transaction_hash_hash, Cloak.Ecto.SHA256) :: binary() | nil timestamps() end diff --git a/apps/explorer/lib/explorer/accounts/user.ex b/apps/explorer/lib/explorer/accounts/user.ex index fd797d0b9532..20291b95c651 100644 --- a/apps/explorer/lib/explorer/accounts/user.ex +++ b/apps/explorer/lib/explorer/accounts/user.ex @@ -16,13 +16,7 @@ defmodule Explorer.Accounts.User do * `:password_hash` - Encrypted password * `:contacts` - List of `t:UserContact.t/0` """ - @type t :: %User{ - username: String.t(), - password_hash: String.t(), - contacts: [UserContact.t()] - } - - schema "users" do + typed_schema "users" do field(:username, :string) field(:password, :string, virtual: true) field(:password_hash, :string) diff --git a/apps/explorer/lib/explorer/accounts/user/authenticate.ex b/apps/explorer/lib/explorer/accounts/user/authenticate.ex index 08626c18b0cb..ea127d1a05ab 100644 --- a/apps/explorer/lib/explorer/accounts/user/authenticate.ex +++ b/apps/explorer/lib/explorer/accounts/user/authenticate.ex @@ -7,9 +7,9 @@ defmodule Explorer.Accounts.User.Authenticate do import Ecto.Changeset - embedded_schema do - field(:username, :string) - field(:password, :string) + typed_embedded_schema do + field(:username, :string, null: false) + field(:password, :string, null: false) end @required_attrs ~w(password username)a diff --git a/apps/explorer/lib/explorer/accounts/user/registration.ex b/apps/explorer/lib/explorer/accounts/user/registration.ex index 360e6bbb5e00..b62dd1c25e40 100644 --- a/apps/explorer/lib/explorer/accounts/user/registration.ex +++ b/apps/explorer/lib/explorer/accounts/user/registration.ex @@ -9,13 +9,11 @@ defmodule Explorer.Accounts.User.Registration do alias Explorer.Accounts.User.Registration - @type t :: %__MODULE__{} - - embedded_schema do - field(:username, :string) - field(:email, :string) - field(:password, :string) - field(:password_confirmation, :string) + typed_embedded_schema do + field(:username, :string, null: false) + field(:email, :string, null: false) + field(:password, :string, null: false) + field(:password_confirmation, :string, null: false) end @fields ~w(email password password_confirmation username)a diff --git a/apps/explorer/lib/explorer/accounts/user_contact.ex b/apps/explorer/lib/explorer/accounts/user_contact.ex index 5d6117ef998b..5b53692f38e1 100644 --- a/apps/explorer/lib/explorer/accounts/user_contact.ex +++ b/apps/explorer/lib/explorer/accounts/user_contact.ex @@ -19,17 +19,10 @@ defmodule Explorer.Accounts.UserContact do * `:verified` - Flag indicating if email contact has been verified * `:user` - owning `t:User.t/0` """ - @type t :: %UserContact{ - email: String.t(), - primary: boolean(), - verified: boolean(), - user: User.t() - } - - schema "user_contacts" do - field(:email, :string) - field(:primary, :boolean, default: false) - field(:verified, :boolean, default: false) + typed_schema "user_contacts" do + field(:email, :string, null: false) + field(:primary, :boolean, default: false, null: false) + field(:verified, :boolean, default: false, null: false) belongs_to(:user, User) diff --git a/apps/explorer/lib/explorer/admin/administrator.ex b/apps/explorer/lib/explorer/admin/administrator.ex index 20ec8d92b320..2f3109c10bc4 100644 --- a/apps/explorer/lib/explorer/admin/administrator.ex +++ b/apps/explorer/lib/explorer/admin/administrator.ex @@ -8,22 +8,15 @@ defmodule Explorer.Admin.Administrator do import Ecto.Changeset alias Explorer.Accounts.User - alias Explorer.Admin.Administrator - alias Explorer.Admin.Administrator.Role @typedoc """ * `:role` - Administrator's role determining permission level * `:user` - The `t:User.t/0` that is an admin * `:user_id` - User foreign key """ - @type t :: %Administrator{ - role: Role.t(), - user: User.t() | %Ecto.Association.NotLoaded{} - } - - schema "administrators" do - field(:role, :string) - belongs_to(:user, User) + typed_schema "administrators" do + field(:role, :string, null: false) + belongs_to(:user, User, null: false) timestamps() end diff --git a/apps/explorer/lib/explorer/application/constants.ex b/apps/explorer/lib/explorer/application/constants.ex index a7dea3535756..dc90158dd3be 100644 --- a/apps/explorer/lib/explorer/application/constants.ex +++ b/apps/explorer/lib/explorer/application/constants.ex @@ -9,9 +9,9 @@ defmodule Explorer.Application.Constants do @keys_manager_contract_address_key "keys_manager_contract_address" @primary_key false - schema "constants" do - field(:key, :string, primary_key: true) - field(:value, :string) + typed_schema "constants" do + field(:key, :string, primary_key: true, null: false) + field(:value, :string, null: false) timestamps() end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index a985998e5485..91f91386cb55 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -3833,7 +3833,11 @@ defmodule Explorer.Chain do |> select_repo(options).all() end - @spec erc721_or_erc1155_token_instance_from_token_id_and_token_address(non_neg_integer(), Hash.Address.t(), [api?]) :: + @spec erc721_or_erc1155_token_instance_from_token_id_and_token_address( + Decimal.t() | non_neg_integer(), + Hash.Address.t(), + [api?] + ) :: {:ok, Instance.t()} | {:error, :not_found} def erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, token_contract_address, options \\ []) do query = Instance.token_instance_query(token_id, token_contract_address) diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index 83458445cd12..a240fb8552d8 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -36,38 +36,6 @@ defmodule Explorer.Chain.Address do """ @type hash :: Hash.t() - @typedoc """ - * `fetched_coin_balance` - The last fetched balance from Nethermind - * `fetched_coin_balance_block_number` - the `t:Explorer.Chain.Block.t/0` `t:Explorer.Chain.Block.block_number/0` for - which `fetched_coin_balance` was fetched - * `hash` - the hash of the address's public key - * `contract_code` - the binary code of the contract when an Address is a contract. The human-readable - Solidity source code is in `smart_contract` `t:Explorer.Chain.SmartContract.t/0` `contract_source_code` *if* the - contract has been verified - * `names` - names known for the address - * `inserted_at` - when this address was inserted - * `updated_at` - when this address was last updated - * `ens_domain_name` - virtual field for ENS domain name passing - - `fetched_coin_balance` and `fetched_coin_balance_block_number` may be updated when a new coin_balance row is fetched. - They may also be updated when the balance is fetched via the on demand fetcher. - """ - @type t :: %__MODULE__{ - fetched_coin_balance: Wei.t(), - fetched_coin_balance_block_number: Block.block_number(), - hash: Hash.Address.t(), - contract_code: Data.t() | nil, - names: %Ecto.Association.NotLoaded{} | [Address.Name.t()], - contracts_creation_transaction: %Ecto.Association.NotLoaded{} | Transaction.t(), - inserted_at: DateTime.t(), - updated_at: DateTime.t(), - nonce: non_neg_integer() | nil, - transactions_count: non_neg_integer() | nil, - token_transfers_count: non_neg_integer() | nil, - gas_used: non_neg_integer() | nil, - ens_domain_name: String.t() | nil - } - @derive {Poison.Encoder, except: [ :__meta__, @@ -90,10 +58,27 @@ defmodule Explorer.Chain.Address do :names ]} - @primary_key {:hash, Hash.Address, autogenerate: false} - schema "addresses" do + @typedoc """ + * `fetched_coin_balance` - The last fetched balance from Nethermind + * `fetched_coin_balance_block_number` - the `t:Explorer.Chain.Block.t/0` `t:Explorer.Chain.Block.block_number/0` for + which `fetched_coin_balance` was fetched + * `hash` - the hash of the address's public key + * `contract_code` - the binary code of the contract when an Address is a contract. The human-readable + Solidity source code is in `smart_contract` `t:Explorer.Chain.SmartContract.t/0` `contract_source_code` *if* the + contract has been verified + * `names` - names known for the address + * `inserted_at` - when this address was inserted + * `updated_at` - when this address was last updated + * `ens_domain_name` - virtual field for ENS domain name passing + + `fetched_coin_balance` and `fetched_coin_balance_block_number` may be updated when a new coin_balance row is fetched. + They may also be updated when the balance is fetched via the on demand fetcher. + """ + @primary_key false + typed_schema "addresses" do + field(:hash, Hash.Address, primary_key: true) field(:fetched_coin_balance, Wei) - field(:fetched_coin_balance_block_number, :integer) + field(:fetched_coin_balance_block_number, :integer) :: Block.block_number() | nil field(:contract_code, Data) field(:nonce, :integer) field(:decompiled, :boolean, default: false) @@ -105,24 +90,26 @@ defmodule Explorer.Chain.Address do field(:gas_used, :integer) field(:ens_domain_name, :string, virtual: true) - has_one(:smart_contract, SmartContract) - has_one(:token, Token, foreign_key: :contract_address_hash) + has_one(:smart_contract, SmartContract, references: :hash) + has_one(:token, Token, foreign_key: :contract_address_hash, references: :hash) has_one( :contracts_creation_internal_transaction, InternalTransaction, - foreign_key: :created_contract_address_hash + foreign_key: :created_contract_address_hash, + references: :hash ) has_one( :contracts_creation_transaction, Transaction, - foreign_key: :created_contract_address_hash + foreign_key: :created_contract_address_hash, + references: :hash ) - has_many(:names, Address.Name, foreign_key: :address_hash) - has_many(:decompiled_smart_contracts, DecompiledSmartContract, foreign_key: :address_hash) - has_many(:withdrawals, Withdrawal, foreign_key: :address_hash) + has_many(:names, Address.Name, foreign_key: :address_hash, references: :hash) + has_many(:decompiled_smart_contracts, DecompiledSmartContract, foreign_key: :address_hash, references: :hash) + has_many(:withdrawals, Withdrawal, foreign_key: :address_hash, references: :hash) timestamps() end diff --git a/apps/explorer/lib/explorer/chain/address/coin_balance.ex b/apps/explorer/lib/explorer/chain/address/coin_balance.ex index b2e5a46f263e..de246c1ba4a3 100644 --- a/apps/explorer/lib/explorer/chain/address/coin_balance.ex +++ b/apps/explorer/lib/explorer/chain/address/coin_balance.ex @@ -27,18 +27,9 @@ defmodule Explorer.Chain.Address.CoinBalance do given `address`, the `t:Explorer.Chain.Address.t/0` `fetched_coin_balance` will match this value. * `value_fetched_at` - when `value` was fetched. """ - @type t :: %__MODULE__{ - address: %Ecto.Association.NotLoaded{} | Address.t(), - address_hash: Hash.Address.t(), - block_number: Block.block_number(), - inserted_at: DateTime.t(), - updated_at: DateTime.t(), - value: Wei.t() | nil - } - @primary_key false - schema "address_coin_balances" do - field(:block_number, :integer) + typed_schema "address_coin_balances" do + field(:block_number, :integer) :: Block.block_number() field(:value, Wei) field(:value_fetched_at, :utc_datetime_usec) field(:delta, Wei, virtual: true) @@ -47,7 +38,7 @@ defmodule Explorer.Chain.Address.CoinBalance do timestamps() - belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address) + belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address, null: false) end @doc """ diff --git a/apps/explorer/lib/explorer/chain/address/coin_balance_daily.ex b/apps/explorer/lib/explorer/chain/address/coin_balance_daily.ex index cc881d9c471d..fdd7b9e9fc60 100644 --- a/apps/explorer/lib/explorer/chain/address/coin_balance_daily.ex +++ b/apps/explorer/lib/explorer/chain/address/coin_balance_daily.ex @@ -21,23 +21,14 @@ defmodule Explorer.Chain.Address.CoinBalanceDaily do * `updated_at` - When the balance was last updated. * `value` - the max balance (`value`) of `address` during the `day`. """ - @type t :: %__MODULE__{ - address: %Ecto.Association.NotLoaded{} | Address.t(), - address_hash: Hash.Address.t(), - day: Date.t(), - inserted_at: DateTime.t(), - updated_at: DateTime.t(), - value: Wei.t() | nil - } - @primary_key false - schema "address_coin_balances_daily" do - field(:day, :date) + typed_schema "address_coin_balances_daily" do + field(:day, :date, null: false) field(:value, Wei) timestamps() - belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address) + belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address, null: false) end @doc """ diff --git a/apps/explorer/lib/explorer/chain/address/current_token_balance.ex b/apps/explorer/lib/explorer/chain/address/current_token_balance.ex index fe7f3070122c..30dbe76f56f8 100644 --- a/apps/explorer/lib/explorer/chain/address/current_token_balance.ex +++ b/apps/explorer/lib/explorer/chain/address/current_token_balance.ex @@ -27,30 +27,13 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do * `token_id` - The token_id of the transferred token (applicable for ERC-1155) * `token_type` - The type of the token """ - @type t :: %__MODULE__{ - address: %Ecto.Association.NotLoaded{} | Address.t(), - address_hash: Hash.Address.t(), - token: %Ecto.Association.NotLoaded{} | Token.t(), - token_contract_address_hash: Hash.Address, - block_number: Block.block_number(), - max_block_number: Block.block_number(), - inserted_at: DateTime.t(), - updated_at: DateTime.t(), - value: Decimal.t() | nil, - token_id: non_neg_integer() | nil, - token_type: String.t(), - distinct_token_instances_count: non_neg_integer(), - token_ids: list(Decimal.t()), - preloaded_token_instances: list() - } - - schema "address_current_token_balances" do + typed_schema "address_current_token_balances" do field(:value, :decimal) - field(:block_number, :integer) - field(:max_block_number, :integer, virtual: true) + field(:block_number, :integer) :: Block.block_number() + field(:max_block_number, :integer, virtual: true) :: Block.block_number() field(:value_fetched_at, :utc_datetime_usec) field(:token_id, :decimal) - field(:token_type, :string) + field(:token_type, :string, null: false) field(:fiat_value, :decimal, virtual: true) field(:distinct_token_instances_count, :integer, virtual: true) field(:token_ids, {:array, :decimal}, virtual: true) @@ -59,14 +42,15 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do # A transient field for deriving token holder count deltas during address_current_token_balances upserts field(:old_value, :decimal) - belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address) + belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address, null: false) belongs_to( :token, Token, foreign_key: :token_contract_address_hash, references: :contract_address_hash, - type: Hash.Address + type: Hash.Address, + null: false ) timestamps() diff --git a/apps/explorer/lib/explorer/chain/address/name.ex b/apps/explorer/lib/explorer/chain/address/name.ex index 533ef2878911..d05e04ae509c 100644 --- a/apps/explorer/lib/explorer/chain/address/name.ex +++ b/apps/explorer/lib/explorer/chain/address/name.ex @@ -18,20 +18,13 @@ defmodule Explorer.Chain.Address.Name do * `name` - name for the address * `primary` - flag for if the name is the primary name for the address """ - @type t :: %__MODULE__{ - address: %Ecto.Association.NotLoaded{} | Address.t(), - address_hash: Hash.Address.t(), - name: String.t(), - primary: boolean(), - metadata: map() - } - - @primary_key {:id, :integer, autogenerate: false} - schema "address_names" do - field(:name, :string) + @primary_key false + typed_schema "address_names" do + field(:id, :integer, autogenerate: false, primary_key: true, null: false) + field(:name, :string, null: false) field(:primary, :boolean) field(:metadata, :map) - belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address) + belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address, null: false) timestamps() end diff --git a/apps/explorer/lib/explorer/chain/address/token_balance.ex b/apps/explorer/lib/explorer/chain/address/token_balance.ex index 5b647bae1ed2..99eac8779f29 100644 --- a/apps/explorer/lib/explorer/chain/address/token_balance.ex +++ b/apps/explorer/lib/explorer/chain/address/token_balance.ex @@ -26,34 +26,22 @@ defmodule Explorer.Chain.Address.TokenBalance do * `token_id` - The token_id of the transferred token (applicable for ERC-1155 and ERC-721 tokens) * `token_type` - The type of the token """ - @type t :: %__MODULE__{ - address: %Ecto.Association.NotLoaded{} | Address.t(), - address_hash: Hash.Address.t(), - token: %Ecto.Association.NotLoaded{} | Token.t(), - token_contract_address_hash: Hash.Address, - block_number: Block.block_number(), - inserted_at: DateTime.t(), - updated_at: DateTime.t(), - value: Decimal.t() | nil, - token_id: non_neg_integer() | nil, - token_type: String.t() - } - - schema "address_token_balances" do + typed_schema "address_token_balances" do field(:value, :decimal) - field(:block_number, :integer) + field(:block_number, :integer) :: Block.block_number() field(:value_fetched_at, :utc_datetime_usec) field(:token_id, :decimal) - field(:token_type, :string) + field(:token_type, :string, null: false) - belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address) + belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address, null: false) belongs_to( :token, Token, foreign_key: :token_contract_address_hash, references: :contract_address_hash, - type: Hash.Address + type: Hash.Address, + null: false ) timestamps() diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index cdf6381b9772..db08bd8a7b85 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -1,3 +1,71 @@ +defmodule Explorer.Chain.Block.Schema do + @moduledoc false + + alias Explorer.Chain.{Address, Block, Hash, PendingBlockOperation, Transaction, Wei, Withdrawal} + alias Explorer.Chain.Block.{Reward, SecondDegreeRelation} + + @chain_type_fields (case Application.compile_env(:explorer, :chain_type) do + "rsk" -> + elem( + quote do + field(:bitcoin_merged_mining_header, :binary) + field(:bitcoin_merged_mining_coinbase_transaction, :binary) + field(:bitcoin_merged_mining_merkle_proof, :binary) + field(:hash_for_merged_mining, :binary) + field(:minimum_gas_price, :decimal) + end, + 2 + ) + + _ -> + [] + end) + + defmacro generate do + quote do + @primary_key false + typed_schema "blocks" do + field(:hash, Hash.Full, primary_key: true, null: false) + field(:consensus, :boolean, null: false) + field(:difficulty, :decimal) + field(:gas_limit, :decimal, null: false) + field(:gas_used, :decimal, null: false) + field(:nonce, Hash.Nonce, null: false) + field(:number, :integer, null: false) + field(:size, :integer) + field(:timestamp, :utc_datetime_usec, null: false) + field(:total_difficulty, :decimal) + field(:refetch_needed, :boolean) + field(:base_fee_per_gas, Wei) + field(:is_empty, :boolean) + + timestamps() + + belongs_to(:miner, Address, foreign_key: :miner_hash, references: :hash, type: Hash.Address, null: false) + + has_many(:nephew_relations, SecondDegreeRelation, foreign_key: :uncle_hash, references: :hash) + has_many(:nephews, through: [:nephew_relations, :nephew], references: :hash) + + belongs_to(:parent, Block, foreign_key: :parent_hash, references: :hash, type: Hash.Full, null: false) + + has_many(:uncle_relations, SecondDegreeRelation, foreign_key: :nephew_hash, references: :hash) + has_many(:uncles, through: [:uncle_relations, :uncle], references: :hash) + + has_many(:transactions, Transaction, references: :hash) + has_many(:transaction_forks, Transaction.Fork, foreign_key: :uncle_hash, references: :hash) + + has_many(:rewards, Reward, foreign_key: :block_hash, references: :hash) + + has_many(:withdrawals, Withdrawal, foreign_key: :block_hash, references: :hash) + + has_one(:pending_operations, PendingBlockOperation, foreign_key: :block_hash, references: :hash) + + unquote_splicing(@chain_type_fields) + end + end + end +end + defmodule Explorer.Chain.Block do @moduledoc """ A package of data that contains zero or more transactions, the hash of the previous block ("parent"), and optionally @@ -5,10 +73,12 @@ defmodule Explorer.Chain.Block do structure that they form is called a "blockchain". """ + require Explorer.Chain.Block.Schema + use Explorer.Schema - alias Explorer.Chain.{Address, Block, Gas, Hash, PendingBlockOperation, Transaction, Wei, Withdrawal} - alias Explorer.Chain.Block.{EmissionReward, Reward, SecondDegreeRelation} + alias Explorer.Chain.{Block, Hash, Transaction, Wei} + alias Explorer.Chain.Block.{EmissionReward, Reward} alias Explorer.Repo @optional_attrs ~w(size refetch_needed total_difficulty difficulty base_fee_per_gas)a @@ -35,20 +105,6 @@ defmodule Explorer.Chain.Block do """ @type block_number :: non_neg_integer() - if Application.compile_env(:explorer, :chain_type) == "rsk" do - @rootstock_fields quote( - do: [ - bitcoin_merged_mining_header: binary(), - bitcoin_merged_mining_coinbase_transaction: binary(), - bitcoin_merged_mining_merkle_proof: binary(), - hash_for_merged_mining: binary(), - minimum_gas_price: Decimal.t() - ] - ) - else - @rootstock_fields quote(do: []) - end - @typedoc """ * `consensus` * `true` - this is a block on the longest consensus agreed upon chain. @@ -80,71 +136,7 @@ defmodule Explorer.Chain.Block do """ end} """ - @type t :: %__MODULE__{ - unquote_splicing(@rootstock_fields), - consensus: boolean(), - difficulty: difficulty(), - gas_limit: Gas.t(), - gas_used: Gas.t(), - hash: Hash.Full.t(), - miner: %Ecto.Association.NotLoaded{} | Address.t(), - miner_hash: Hash.Address.t(), - nonce: Hash.Nonce.t(), - number: block_number(), - parent_hash: Hash.t(), - size: non_neg_integer(), - timestamp: DateTime.t(), - total_difficulty: difficulty(), - transactions: %Ecto.Association.NotLoaded{} | [Transaction.t()], - refetch_needed: boolean(), - base_fee_per_gas: Wei.t(), - is_empty: boolean() - } - - @primary_key {:hash, Hash.Full, autogenerate: false} - schema "blocks" do - field(:consensus, :boolean) - field(:difficulty, :decimal) - field(:gas_limit, :decimal) - field(:gas_used, :decimal) - field(:nonce, Hash.Nonce) - field(:number, :integer) - field(:size, :integer) - field(:timestamp, :utc_datetime_usec) - field(:total_difficulty, :decimal) - field(:refetch_needed, :boolean) - field(:base_fee_per_gas, Wei) - field(:is_empty, :boolean) - - if Application.compile_env(:explorer, :chain_type) == "rsk" do - field(:bitcoin_merged_mining_header, :binary) - field(:bitcoin_merged_mining_coinbase_transaction, :binary) - field(:bitcoin_merged_mining_merkle_proof, :binary) - field(:hash_for_merged_mining, :binary) - field(:minimum_gas_price, :decimal) - end - - timestamps() - - belongs_to(:miner, Address, foreign_key: :miner_hash, references: :hash, type: Hash.Address) - - has_many(:nephew_relations, SecondDegreeRelation, foreign_key: :uncle_hash) - has_many(:nephews, through: [:nephew_relations, :nephew]) - - belongs_to(:parent, __MODULE__, foreign_key: :parent_hash, references: :hash, type: Hash.Full) - - has_many(:uncle_relations, SecondDegreeRelation, foreign_key: :nephew_hash) - has_many(:uncles, through: [:uncle_relations, :uncle]) - - has_many(:transactions, Transaction) - has_many(:transaction_forks, Transaction.Fork, foreign_key: :uncle_hash) - - has_many(:rewards, Reward, foreign_key: :block_hash) - - has_many(:withdrawals, Withdrawal, foreign_key: :block_hash) - - has_one(:pending_operations, PendingBlockOperation, foreign_key: :block_hash) - end + Explorer.Chain.Block.Schema.generate() def changeset(%__MODULE__{} = block, attrs) do block diff --git a/apps/explorer/lib/explorer/chain/block/emission_reward.ex b/apps/explorer/lib/explorer/chain/block/emission_reward.ex index a6c3713fcce0..8be68584b56e 100644 --- a/apps/explorer/lib/explorer/chain/block/emission_reward.ex +++ b/apps/explorer/lib/explorer/chain/block/emission_reward.ex @@ -5,7 +5,7 @@ defmodule Explorer.Chain.Block.EmissionReward do use Explorer.Schema - alias Explorer.Chain.Block.{EmissionReward, Range} + alias Explorer.Chain.Block.Range alias Explorer.Chain.Wei @typedoc """ @@ -14,15 +14,10 @@ defmodule Explorer.Chain.Block.EmissionReward do * `:block_range` - Range of block numbers * `:reward` - Reward given in Wei """ - @type t :: %EmissionReward{ - block_range: Range.t(), - reward: Wei.t() - } - @primary_key false - schema "emission_rewards" do - field(:block_range, Range) - field(:reward, Wei) + typed_schema "emission_rewards" do + field(:block_range, Range, null: false) + field(:reward, Wei, null: false) end def changeset(%__MODULE__{} = emission_reward, attrs) do diff --git a/apps/explorer/lib/explorer/chain/block/reward.ex b/apps/explorer/lib/explorer/chain/block/reward.ex index a05b1cf1b7cd..f558c0eced42 100644 --- a/apps/explorer/lib/explorer/chain/block/reward.ex +++ b/apps/explorer/lib/explorer/chain/block/reward.ex @@ -43,26 +43,18 @@ defmodule Explorer.Chain.Block.Reward do * `:block_hash` - Hash of the validated block * `:reward` - Total block reward """ - @type t :: %__MODULE__{ - address: %Ecto.Association.NotLoaded{} | Address.t() | nil, - address_hash: Hash.Address.t(), - address_type: AddressType.t(), - block: %Ecto.Association.NotLoaded{} | Block.t() | nil, - block_hash: Hash.Full.t(), - reward: Wei.t() - } - @primary_key false - schema "block_rewards" do - field(:address_type, AddressType) - field(:reward, Wei) + typed_schema "block_rewards" do + field(:address_type, AddressType, null: false) + field(:reward, Wei, null: false) belongs_to( :address, Address, foreign_key: :address_hash, references: :hash, - type: Hash.Address + type: Hash.Address, + null: false ) belongs_to( @@ -70,7 +62,8 @@ defmodule Explorer.Chain.Block.Reward do Block, foreign_key: :block_hash, references: :hash, - type: Hash.Full + type: Hash.Full, + null: false ) timestamps() diff --git a/apps/explorer/lib/explorer/chain/block/second_degree_relation.ex b/apps/explorer/lib/explorer/chain/block/second_degree_relation.ex index b9a2af6de16a..2f6af19fbb72 100644 --- a/apps/explorer/lib/explorer/chain/block/second_degree_relation.ex +++ b/apps/explorer/lib/explorer/chain/block/second_degree_relation.ex @@ -29,31 +29,26 @@ defmodule Explorer.Chain.Block.SecondDegreeRelation do * `uncle_hash` - foreign key for `uncle`. * `index` - index of the uncle within its nephew. Can be `nil` for blocks fetched before this field was added. """ - @type t :: - %__MODULE__{ - nephew: %Ecto.Association.NotLoaded{} | Block.t(), - nephew_hash: Hash.Full.t(), - uncle: %Ecto.Association.NotLoaded{} | Block.t() | nil, - uncle_fetched_at: nil, - uncle_hash: Hash.Full.t(), - index: non_neg_integer() | nil - } - | %__MODULE__{ - nephew: %Ecto.Association.NotLoaded{} | Block.t(), - nephew_hash: Hash.Full.t(), - uncle: %Ecto.Association.NotLoaded{} | Block.t(), - uncle_fetched_at: DateTime.t(), - uncle_hash: Hash.Full.t(), - index: non_neg_integer() | nil - } - @primary_key false - schema "block_second_degree_relations" do + typed_schema "block_second_degree_relations" do field(:uncle_fetched_at, :utc_datetime_usec) - field(:index, :integer) - - belongs_to(:nephew, Block, foreign_key: :nephew_hash, primary_key: true, references: :hash, type: Hash.Full) - belongs_to(:uncle, Block, foreign_key: :uncle_hash, primary_key: true, references: :hash, type: Hash.Full) + field(:index, :integer, null: true) + + belongs_to(:nephew, Block, + foreign_key: :nephew_hash, + primary_key: true, + references: :hash, + type: Hash.Full, + null: false + ) + + belongs_to(:uncle, Block, + foreign_key: :uncle_hash, + primary_key: true, + references: :hash, + type: Hash.Full, + null: false + ) end def changeset(%__MODULE__{} = uncle, params) do diff --git a/apps/explorer/lib/explorer/chain/bridged_token.ex b/apps/explorer/lib/explorer/chain/bridged_token.ex index ca4b0074104e..e53b0e1e9887 100644 --- a/apps/explorer/lib/explorer/chain/bridged_token.ex +++ b/apps/explorer/lib/explorer/chain/bridged_token.ex @@ -20,7 +20,6 @@ defmodule Explorer.Chain.BridgedToken do alias Explorer.{Chain, PagingOptions, Repo, SortingHelper} alias Explorer.Chain.{ - Address, BridgedToken, Hash, InternalTransaction, @@ -33,28 +32,6 @@ defmodule Explorer.Chain.BridgedToken do @default_paging_options %PagingOptions{page_size: 50} - @typedoc """ - * `foreign_chain_id` - chain ID of a foreign token - * `foreign_token_contract_address_hash` - Foreign token's contract hash - * `home_token_contract_address` - The `t:Address.t/0` of the home token's contract - * `home_token_contract_address_hash` - Home token's contract hash foreign key - * `custom_metadata` - Arbitrary string with custom metadata. For instance, tokens/weights for Balance tokens - * `custom_cap` - Custom capitalization for this token - * `lp_token` - Boolean flag: LP token or not - * `type` - omni/amb - """ - @type t :: %BridgedToken{ - foreign_chain_id: Decimal.t(), - foreign_token_contract_address_hash: Hash.Address.t(), - home_token_contract_address: %Ecto.Association.NotLoaded{} | Address.t(), - home_token_contract_address_hash: Hash.Address.t(), - custom_metadata: String.t(), - custom_cap: Decimal.t(), - lp_token: boolean(), - type: String.t(), - exchange_rate: Decimal.t() - } - @derive {Poison.Encoder, except: [ :__meta__, @@ -71,8 +48,18 @@ defmodule Explorer.Chain.BridgedToken do :updated_at ]} + @typedoc """ + * `foreign_chain_id` - chain ID of a foreign token + * `foreign_token_contract_address_hash` - Foreign token's contract hash + * `home_token_contract_address` - The `t:Address.t/0` of the home token's contract + * `home_token_contract_address_hash` - Home token's contract hash foreign key + * `custom_metadata` - Arbitrary string with custom metadata. For instance, tokens/weights for Balance tokens + * `custom_cap` - Custom capitalization for this token + * `lp_token` - Boolean flag: LP token or not + * `type` - omni/amb + """ @primary_key false - schema "bridged_tokens" do + typed_schema "bridged_tokens" do field(:foreign_chain_id, :decimal) field(:foreign_token_contract_address_hash, Hash.Address) field(:custom_metadata, :string) @@ -87,7 +74,8 @@ defmodule Explorer.Chain.BridgedToken do foreign_key: :home_token_contract_address_hash, primary_key: true, references: :contract_address_hash, - type: Hash.Address + type: Hash.Address, + null: false ) timestamps() diff --git a/apps/explorer/lib/explorer/chain/contract_method.ex b/apps/explorer/lib/explorer/chain/contract_method.ex index dd1cf0a80923..e17e233ff4d6 100644 --- a/apps/explorer/lib/explorer/chain/contract_method.ex +++ b/apps/explorer/lib/explorer/chain/contract_method.ex @@ -11,13 +11,7 @@ defmodule Explorer.Chain.ContractMethod do alias Explorer.Chain.{Hash, MethodIdentifier, SmartContract} alias Explorer.Repo - @type t :: %__MODULE__{ - identifier: MethodIdentifier.t(), - abi: map(), - type: String.t() - } - - schema "contract_methods" do + typed_schema "contract_methods" do field(:identifier, MethodIdentifier) field(:abi, :map) field(:type, :string) diff --git a/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex b/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex index 795214604482..31e92d38bacc 100644 --- a/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex @@ -9,16 +9,17 @@ defmodule Explorer.Chain.DecompiledSmartContract do @derive {Jason.Encoder, only: [:address_hash, :decompiler_version, :decompiled_source_code]} - schema "decompiled_smart_contracts" do - field(:decompiler_version, :string) - field(:decompiled_source_code, :string) + typed_schema "decompiled_smart_contracts" do + field(:decompiler_version, :string, null: false) + field(:decompiled_source_code, :string, null: false) belongs_to( :address, Address, foreign_key: :address_hash, references: :hash, - type: Hash.Address + type: Hash.Address, + null: false ) timestamps() diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index 82ce66e8c3dc..b082a9a76c33 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.InternalTransaction do use Explorer.Schema - alias Explorer.Chain.{Address, Block, Data, Gas, Hash, PendingBlockOperation, Transaction, Wei} + alias Explorer.Chain.{Address, Block, Data, Hash, PendingBlockOperation, Transaction, Wei} alias Explorer.Chain.InternalTransaction.{Action, CallType, Result, Type} @typedoc """ @@ -32,50 +32,23 @@ defmodule Explorer.Chain.InternalTransaction do * `block_index` - the index of this internal transaction inside the `block` * `pending_block` - `nil` if `block` has all its internal transactions fetched """ - @type t :: %__MODULE__{ - block_number: Explorer.Chain.Block.block_number() | nil, - type: Type.t(), - call_type: CallType.t() | nil, - created_contract_address: %Ecto.Association.NotLoaded{} | Address.t() | nil, - created_contract_address_hash: Hash.t() | nil, - created_contract_code: Data.t() | nil, - error: String.t(), - from_address: %Ecto.Association.NotLoaded{} | Address.t(), - from_address_hash: Hash.Address.t(), - gas: Gas.t() | nil, - gas_used: Gas.t() | nil, - index: non_neg_integer(), - init: Data.t() | nil, - input: Data.t() | nil, - output: Data.t() | nil, - to_address: %Ecto.Association.NotLoaded{} | Address.t() | nil, - to_address_hash: Hash.Address.t() | nil, - trace_address: [non_neg_integer()], - transaction: %Ecto.Association.NotLoaded{} | Transaction.t(), - transaction_hash: Hash.t(), - transaction_index: Transaction.transaction_index() | nil, - value: Wei.t(), - block_hash: Hash.Full.t(), - block_index: non_neg_integer() - } - @primary_key false - schema "internal_transactions" do + typed_schema "internal_transactions" do field(:call_type, CallType) field(:created_contract_code, Data) field(:error, :string) field(:gas, :decimal) field(:gas_used, :decimal) - field(:index, :integer, primary_key: true) + field(:index, :integer, primary_key: true, null: false) field(:init, Data) field(:input, Data) field(:output, Data) - field(:trace_address, {:array, :integer}) - field(:type, Type) - field(:value, Wei) + field(:trace_address, {:array, :integer}, null: false) + field(:type, Type, null: false) + field(:value, Wei, null: false) field(:block_number, :integer) field(:transaction_index, :integer) - field(:block_index, :integer) + field(:block_index, :integer, null: false) timestamps() @@ -92,7 +65,8 @@ defmodule Explorer.Chain.InternalTransaction do Address, foreign_key: :from_address_hash, references: :hash, - type: Hash.Address + type: Hash.Address, + null: false ) belongs_to( @@ -107,13 +81,15 @@ defmodule Explorer.Chain.InternalTransaction do foreign_key: :transaction_hash, primary_key: true, references: :hash, - type: Hash.Full + type: Hash.Full, + null: false ) belongs_to(:block, Block, foreign_key: :block_hash, references: :hash, - type: Hash.Full + type: Hash.Full, + null: false ) belongs_to(:pending_block, PendingBlockOperation, diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 57909184a04b..0ea5b9313ffd 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -28,47 +28,34 @@ defmodule Explorer.Chain.Log do * `transaction_hash` - foreign key for `transaction`. * `index` - index of the log entry in all logs for the `transaction` """ - @type t :: %__MODULE__{ - address: %Ecto.Association.NotLoaded{} | Address.t(), - address_hash: Hash.Address.t(), - block_hash: Hash.Full.t(), - block_number: non_neg_integer() | nil, - data: Data.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() - } - @primary_key false - schema "logs" do - field(:data, Data) + typed_schema "logs" do + field(:data, Data, null: false) 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(:index, :integer, primary_key: true, null: false) field(:block_number, :integer) timestamps() - belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address) + belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address, null: false) belongs_to(:transaction, Transaction, foreign_key: :transaction_hash, primary_key: true, references: :hash, - type: Hash.Full + type: Hash.Full, + null: false ) belongs_to(:block, Block, foreign_key: :block_hash, primary_key: true, references: :hash, - type: Hash.Full + type: Hash.Full, + null: false ) end diff --git a/apps/explorer/lib/explorer/chain/pending_block_operation.ex b/apps/explorer/lib/explorer/chain/pending_block_operation.ex index 8c4a99a5b4e1..0852e317b8d3 100644 --- a/apps/explorer/lib/explorer/chain/pending_block_operation.ex +++ b/apps/explorer/lib/explorer/chain/pending_block_operation.ex @@ -12,17 +12,19 @@ defmodule Explorer.Chain.PendingBlockOperation do @typedoc """ * `block_hash` - the hash of the block that has pending operations. """ - @type t :: %__MODULE__{ - block_hash: Hash.Full.t() - } - @primary_key false - schema "pending_block_operations" do + typed_schema "pending_block_operations" do timestamps() - field(:block_number, :integer) + field(:block_number, :integer, null: false) - belongs_to(:block, Block, foreign_key: :block_hash, primary_key: true, references: :hash, type: Hash.Full) + belongs_to(:block, Block, + foreign_key: :block_hash, + primary_key: true, + references: :hash, + type: Hash.Full, + null: false + ) end def changeset(%__MODULE__{} = pending_ops, attrs) do diff --git a/apps/explorer/lib/explorer/chain/polygon_edge/deposit.ex b/apps/explorer/lib/explorer/chain/polygon_edge/deposit.ex index b9ad75bc3a5b..a18900868876 100644 --- a/apps/explorer/lib/explorer/chain/polygon_edge/deposit.ex +++ b/apps/explorer/lib/explorer/chain/polygon_edge/deposit.ex @@ -22,23 +22,14 @@ defmodule Explorer.Chain.PolygonEdge.Deposit do * `l1_timestamp` - timestamp of the L1 transaction block * `l1_block_number` - block number of the L1 transaction """ - @type t :: %__MODULE__{ - msg_id: non_neg_integer(), - from: Hash.Address.t() | nil, - to: Hash.Address.t() | nil, - l1_transaction_hash: Hash.t() | nil, - l1_timestamp: DateTime.t() | nil, - l1_block_number: Block.block_number() - } - @primary_key false - schema "polygon_edge_deposits" do - field(:msg_id, :integer, primary_key: true) + typed_schema "polygon_edge_deposits" do + field(:msg_id, :integer, primary_key: true, null: false) field(:from, Hash.Address) field(:to, Hash.Address) field(:l1_transaction_hash, Hash.Full) field(:l1_timestamp, :utc_datetime_usec) - field(:l1_block_number, :integer) + field(:l1_block_number, :integer) :: Block.block_number() timestamps() end diff --git a/apps/explorer/lib/explorer/chain/polygon_edge/deposit_execute.ex b/apps/explorer/lib/explorer/chain/polygon_edge/deposit_execute.ex index e3e7617d579f..7e2ed2c64e5e 100644 --- a/apps/explorer/lib/explorer/chain/polygon_edge/deposit_execute.ex +++ b/apps/explorer/lib/explorer/chain/polygon_edge/deposit_execute.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.PolygonEdge.DepositExecute do use Explorer.Schema - alias Explorer.Chain.{Block, Hash} + alias Explorer.Chain.Hash @required_attrs ~w(msg_id l2_transaction_hash l2_block_number success)a @@ -13,19 +13,12 @@ defmodule Explorer.Chain.PolygonEdge.DepositExecute do * `l2_block_number` - block number of the L2 transaction * `success` - a status of onStateReceive internal call (namely internal deposit transaction) """ - @type t :: %__MODULE__{ - msg_id: non_neg_integer(), - l2_transaction_hash: Hash.t(), - l2_block_number: Block.block_number(), - success: boolean() - } - @primary_key false - schema "polygon_edge_deposit_executes" do - field(:msg_id, :integer, primary_key: true) - field(:l2_transaction_hash, Hash.Full) - field(:l2_block_number, :integer) - field(:success, :boolean) + typed_schema "polygon_edge_deposit_executes" do + field(:msg_id, :integer, primary_key: true, null: false) + field(:l2_transaction_hash, Hash.Full, null: false) + field(:l2_block_number, :integer, null: false) + field(:success, :boolean, null: false) timestamps() end diff --git a/apps/explorer/lib/explorer/chain/polygon_edge/withdrawal.ex b/apps/explorer/lib/explorer/chain/polygon_edge/withdrawal.ex index 9cfc109b0bcb..22b50b1ffa13 100644 --- a/apps/explorer/lib/explorer/chain/polygon_edge/withdrawal.ex +++ b/apps/explorer/lib/explorer/chain/polygon_edge/withdrawal.ex @@ -23,26 +23,21 @@ defmodule Explorer.Chain.PolygonEdge.Withdrawal do * `l2_transaction_hash` - hash of the L2 transaction containing the corresponding L2StateSynced event * `l2_block_number` - block number of the L2 transaction """ - @type t :: %__MODULE__{ - msg_id: non_neg_integer(), - from: Hash.Address.t() | nil, - from_address: %Ecto.Association.NotLoaded{} | Address.t() | nil, - to: Hash.Address.t() | nil, - to_address: %Ecto.Association.NotLoaded{} | Address.t() | nil, - l2_transaction_hash: Hash.t(), - l2_transaction: %Ecto.Association.NotLoaded{} | Transaction.t(), - l2_block_number: Block.block_number(), - l2_block: %Ecto.Association.NotLoaded{} | Block.t() - } - @primary_key false - schema "polygon_edge_withdrawals" do - field(:msg_id, :integer, primary_key: true) + typed_schema "polygon_edge_withdrawals" do + field(:msg_id, :integer, primary_key: true, null: false) belongs_to(:from_address, Address, foreign_key: :from, references: :hash, type: Hash.Address) belongs_to(:to_address, Address, foreign_key: :to, references: :hash, type: Hash.Address) - belongs_to(:l2_transaction, Transaction, foreign_key: :l2_transaction_hash, references: :hash, type: Hash.Full) - belongs_to(:l2_block, Block, foreign_key: :l2_block_number, references: :number, type: :integer) + + belongs_to(:l2_transaction, Transaction, + foreign_key: :l2_transaction_hash, + references: :hash, + type: Hash.Full, + null: false + ) + + belongs_to(:l2_block, Block, foreign_key: :l2_block_number, references: :number, type: :integer, null: false) timestamps() end diff --git a/apps/explorer/lib/explorer/chain/polygon_edge/withdrawal_exit.ex b/apps/explorer/lib/explorer/chain/polygon_edge/withdrawal_exit.ex index 27ee5583af66..959d3bfc68a3 100644 --- a/apps/explorer/lib/explorer/chain/polygon_edge/withdrawal_exit.ex +++ b/apps/explorer/lib/explorer/chain/polygon_edge/withdrawal_exit.ex @@ -13,19 +13,12 @@ defmodule Explorer.Chain.PolygonEdge.WithdrawalExit do * `l1_block_number` - block number of the L1 transaction * `success` - a status of onL2StateReceive internal call (namely internal withdrawal transaction) """ - @type t :: %__MODULE__{ - msg_id: non_neg_integer(), - l1_transaction_hash: Hash.t(), - l1_block_number: Block.block_number(), - success: boolean() - } - @primary_key false - schema "polygon_edge_withdrawal_exits" do - field(:msg_id, :integer, primary_key: true) - field(:l1_transaction_hash, Hash.Full) - field(:l1_block_number, :integer) - field(:success, :boolean) + typed_schema "polygon_edge_withdrawal_exits" do + field(:msg_id, :integer, primary_key: true, null: false) + field(:l1_transaction_hash, Hash.Full, null: false) + field(:l1_block_number, :integer) :: Block.block_number() + field(:success, :boolean, null: false) timestamps() end diff --git a/apps/explorer/lib/explorer/chain/shibarium/bridge.ex b/apps/explorer/lib/explorer/chain/shibarium/bridge.ex index 9cc123bfc2db..ee03ebcd2503 100644 --- a/apps/explorer/lib/explorer/chain/shibarium/bridge.ex +++ b/apps/explorer/lib/explorer/chain/shibarium/bridge.ex @@ -5,6 +5,7 @@ defmodule Explorer.Chain.Shibarium.Bridge do alias Explorer.Chain.{ Address, + Block, Hash, Transaction } @@ -31,35 +32,16 @@ defmodule Explorer.Chain.Shibarium.Bridge do * `token_type` - `bone` or `eth` or `other` * `timestamp` - timestamp of the operation block (L1 block for deposit, L2 block - for withdrawal) """ - @type t :: %__MODULE__{ - user_address: %Ecto.Association.NotLoaded{} | Address.t(), - user: Hash.Address.t(), - amount_or_id: Decimal.t() | nil, - erc1155_ids: [non_neg_integer()] | nil, - erc1155_amounts: [Decimal.t()] | nil, - l1_transaction_hash: Hash.t(), - l1_block_number: non_neg_integer() | nil, - l2_transaction: %Ecto.Association.NotLoaded{} | Transaction.t() | nil, - l2_transaction_hash: Hash.t(), - l2_block_number: non_neg_integer() | nil, - operation_hash: Hash.t(), - operation_type: String.t(), - token_type: String.t(), - timestamp: DateTime.t(), - inserted_at: DateTime.t(), - updated_at: DateTime.t() - } - @primary_key false - schema "shibarium_bridge" do - belongs_to(:user_address, Address, foreign_key: :user, references: :hash, type: Hash.Address) + typed_schema "shibarium_bridge" do + belongs_to(:user_address, Address, foreign_key: :user, references: :hash, type: Hash.Address, null: false) field(:amount_or_id, :decimal) field(:erc1155_ids, {:array, :decimal}) field(:erc1155_amounts, {:array, :decimal}) - field(:operation_hash, Hash.Full, primary_key: true) - field(:operation_type, Ecto.Enum, values: [:deposit, :withdrawal]) + field(:operation_hash, Hash.Full, primary_key: true, null: false) + field(:operation_type, Ecto.Enum, values: [:deposit, :withdrawal], null: false) field(:l1_transaction_hash, Hash.Full, primary_key: true) - field(:l1_block_number, :integer) + field(:l1_block_number, :integer) :: Block.block_number() | nil belongs_to(:l2_transaction, Transaction, foreign_key: :l2_transaction_hash, @@ -68,8 +50,8 @@ defmodule Explorer.Chain.Shibarium.Bridge do primary_key: true ) - field(:l2_block_number, :integer) - field(:token_type, Ecto.Enum, values: [:bone, :eth, :other]) + field(:l2_block_number, :integer) :: Block.block_number() | nil + field(:token_type, Ecto.Enum, values: [:bone, :eth, :other], null: false) field(:timestamp, :utc_datetime_usec) timestamps() diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index d8d9eb79b355..2fa61939e1c4 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -246,37 +246,11 @@ defmodule Explorer.Chain.SmartContract do * `is_yul` - field was added for storing user's choice * `verified_via_eth_bytecode_db` - whether contract automatically verified via eth-bytecode-db or not. """ - - @type t :: %SmartContract{ - name: String.t(), - compiler_version: String.t(), - optimization: boolean, - contract_source_code: String.t(), - constructor_arguments: String.t() | nil, - evm_version: String.t() | nil, - optimization_runs: non_neg_integer() | nil, - abi: [function_description], - verified_via_sourcify: boolean | nil, - partially_verified: boolean | nil, - file_path: String.t(), - is_vyper_contract: boolean | nil, - is_changed_bytecode: boolean, - bytecode_checked_at: DateTime.t(), - contract_code_md5: String.t(), - implementation_name: String.t() | nil, - compiler_settings: map() | nil, - implementation_fetched_at: DateTime.t(), - implementation_address_hash: Hash.Address.t(), - autodetect_constructor_args: boolean | nil, - is_yul: boolean | nil, - verified_via_eth_bytecode_db: boolean | nil - } - - schema "smart_contracts" do - field(:name, :string) - field(:compiler_version, :string) - field(:optimization, :boolean) - field(:contract_source_code, :string) + typed_schema "smart_contracts" do + field(:name, :string, null: false) + field(:compiler_version, :string, null: false) + field(:optimization, :boolean, null: false) + field(:contract_source_code, :string, null: false) field(:constructor_arguments, :string) field(:evm_version, :string) field(:optimization_runs, :integer) @@ -288,7 +262,7 @@ defmodule Explorer.Chain.SmartContract do field(:is_vyper_contract, :boolean) field(:is_changed_bytecode, :boolean, default: false) field(:bytecode_checked_at, :utc_datetime_usec, default: DateTime.add(DateTime.utc_now(), -86400, :second)) - field(:contract_code_md5, :string) + field(:contract_code_md5, :string, null: false) field(:implementation_name, :string) field(:compiler_settings, :map) field(:implementation_fetched_at, :utc_datetime_usec, default: nil) @@ -309,7 +283,8 @@ defmodule Explorer.Chain.SmartContract do Address, foreign_key: :address_hash, references: :hash, - type: Hash.Address + type: Hash.Address, + null: false ) has_many(:smart_contract_additional_sources, SmartContractAdditionalSource, diff --git a/apps/explorer/lib/explorer/chain/smart_contract/audit_report.ex b/apps/explorer/lib/explorer/chain/smart_contract/audit_report.ex index fafb74f25bbc..76a6cf63785a 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/audit_report.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/audit_report.ex @@ -11,32 +11,17 @@ defmodule Explorer.Chain.SmartContract.AuditReport do @max_reports_per_day_for_contract 5 - @type t :: %__MODULE__{ - address_hash: Hash.Address.t(), - is_approved: boolean(), - submitter_name: String.t(), - submitter_email: String.t(), - is_project_owner: boolean(), - project_name: String.t(), - project_url: String.t(), - audit_company_name: String.t(), - audit_report_url: String.t(), - audit_publish_date: Date.t(), - request_id: String.t(), - comment: String.t() - } - - schema "smart_contract_audit_reports" do - field(:address_hash, Hash.Address) + typed_schema "smart_contract_audit_reports" do + field(:address_hash, Hash.Address, null: false) field(:is_approved, :boolean) - field(:submitter_name, :string) - field(:submitter_email, :string) - field(:is_project_owner, :boolean) - field(:project_name, :string) - field(:project_url, :string) - field(:audit_company_name, :string) - field(:audit_report_url, :string) - field(:audit_publish_date, :date) + field(:submitter_name, :string, null: false) + field(:submitter_email, :string, null: false) + field(:is_project_owner, :boolean, null: false) + field(:project_name, :string, null: false) + field(:project_url, :string, null: false) + field(:audit_company_name, :string, null: false) + field(:audit_report_url, :string, null: false) + field(:audit_publish_date, :date, null: false) field(:request_id, :string) field(:comment, :string) diff --git a/apps/explorer/lib/explorer/chain/smart_contract/external_library.ex b/apps/explorer/lib/explorer/chain/smart_contract/external_library.ex index ac52388de5bd..62aeb99b7115 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/external_library.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/external_library.ex @@ -3,9 +3,9 @@ defmodule Explorer.Chain.SmartContract.ExternalLibrary do The representation of an external library that was used for a smart contract. """ - use Ecto.Schema + use Explorer.Schema - embedded_schema do + typed_embedded_schema do field(:name) field(:address_hash) end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/verification_status.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/verification_status.ex index 403d040fb1b6..b952ee1373aa 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/verification_status.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/verification_status.ex @@ -10,25 +10,18 @@ defmodule Explorer.Chain.SmartContract.Proxy.VerificationStatus do alias Explorer.Chain.Hash alias Explorer.{Chain, Repo} + @typep status :: integer() | atom() + @typedoc """ * `contract_address_hash` - address of the contract which was tried to verify * `status` - submission status: :pending | :pass | :fail * `uid` - unique verification identifier """ - - @type t :: %__MODULE__{ - uid: String.t(), - contract_address_hash: Hash.Address.t(), - status: non_neg_integer() | atom() - } - - @typep status :: integer() | atom() - @primary_key false - schema "proxy_smart_contract_verification_statuses" do - field(:uid, :string, primary_key: true) - field(:status, Ecto.Enum, values: [pending: 0, pass: 1, fail: 2]) - field(:contract_address_hash, Hash.Address) + typed_schema "proxy_smart_contract_verification_statuses" do + field(:uid, :string, primary_key: true, null: false) + field(:status, Ecto.Enum, values: [pending: 0, pass: 1, fail: 2], null: false) + field(:contract_address_hash, Hash.Address, null: false) timestamps() end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/verification_status.ex b/apps/explorer/lib/explorer/chain/smart_contract/verification_status.ex index 0571238c872e..83037584c1a4 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/verification_status.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/verification_status.ex @@ -12,21 +12,14 @@ defmodule Explorer.Chain.SmartContract.VerificationStatus do @typedoc """ * `address_hash` - address of the contract which was tried to verify - * `status` - try status: :pending | :pass | :fail + * `status` - try status: :pending | :pass | :fail * `uid` - unique verification try identifier """ - - @type t :: %__MODULE__{ - uid: String.t(), - address_hash: Hash.Address.t(), - status: non_neg_integer() - } - @primary_key false - schema "contract_verification_status" do - field(:uid, :string, primary_key: true) - field(:status, :integer) - field(:address_hash, Hash.Address) + typed_schema "contract_verification_status" do + field(:uid, :string, primary_key: true, null: false) + field(:status, :integer, null: false) + field(:address_hash, Hash.Address, null: false) timestamps() end diff --git a/apps/explorer/lib/explorer/chain/smart_contract_additional_source.ex b/apps/explorer/lib/explorer/chain/smart_contract_additional_source.ex index 87429d183354..cf3b4487ccdd 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract_additional_source.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract_additional_source.ex @@ -17,23 +17,17 @@ defmodule Explorer.Chain.SmartContractAdditionalSource do * `contract_source_code` - the Solidity source code from the file with `file_name`. * `address_hash` - foreign key for `smart_contract`. """ - - @type t :: %Explorer.Chain.SmartContractAdditionalSource{ - file_name: String.t(), - contract_source_code: String.t(), - address_hash: Hash.Address.t() - } - - schema "smart_contracts_additional_sources" do - field(:file_name, :string) - field(:contract_source_code, :string) + typed_schema "smart_contracts_additional_sources" do + field(:file_name, :string, null: false) + field(:contract_source_code, :string, null: false) belongs_to( :smart_contract, SmartContract, foreign_key: :address_hash, references: :address_hash, - type: Hash.Address + type: Hash.Address, + null: false ) timestamps() diff --git a/apps/explorer/lib/explorer/chain/token.ex b/apps/explorer/lib/explorer/chain/token.ex index b3606466242a..65a3f8985d5d 100644 --- a/apps/explorer/lib/explorer/chain/token.ex +++ b/apps/explorer/lib/explorer/chain/token.ex @@ -1,3 +1,54 @@ +defmodule Explorer.Chain.Token.Schema do + @moduledoc false + + alias Explorer.Chain.{Address, Hash} + + if Application.compile_env(:explorer, Explorer.Chain.BridgedToken)[:enabled] do + @bridged_field [ + quote do + field(:bridged, :boolean) + end + ] + else + @bridged_field [] + end + + defmacro generate do + quote do + @primary_key false + typed_schema "tokens" do + field(:name, :string) + field(:symbol, :string) + field(:total_supply, :decimal) + field(:decimals, :decimal) + field(:type, :string, null: false) + field(:cataloged, :boolean) + field(:holder_count, :integer) + field(:skip_metadata, :boolean) + field(:total_supply_updated_at_block, :integer) + field(:fiat_value, :decimal) + field(:circulating_market_cap, :decimal) + field(:icon_url, :string) + field(:is_verified_via_admin_panel, :boolean) + + belongs_to( + :contract_address, + Address, + foreign_key: :contract_address_hash, + primary_key: true, + references: :hash, + type: Hash.Address, + null: false + ) + + unquote_splicing(@bridged_field) + + timestamps() + end + end + end +end + defmodule Explorer.Chain.Token do @moduledoc """ Represents a token. @@ -20,11 +71,13 @@ defmodule Explorer.Chain.Token do use Explorer.Schema + require Explorer.Chain.Token.Schema + import Ecto.{Changeset, Query} alias Ecto.Changeset alias Explorer.{Chain, SortingHelper} - alias Explorer.Chain.{Address, BridgedToken, Hash, Search, Token} + alias Explorer.Chain.{BridgedToken, Search, Token} alias Explorer.SmartContract.Helper @default_sorting [ @@ -35,15 +88,21 @@ defmodule Explorer.Chain.Token do asc: :contract_address_hash ] - if Application.compile_env(:explorer, Explorer.Chain.BridgedToken)[:enabled] do - @bridged_field quote( - do: [ - bridged: boolean() - ] - ) - else - @bridged_field quote(do: []) - end + @derive {Poison.Encoder, + except: [ + :__meta__, + :contract_address, + :inserted_at, + :updated_at + ]} + + @derive {Jason.Encoder, + except: [ + :__meta__, + :contract_address, + :inserted_at, + :updated_at + ]} @typedoc """ * `name` - Name of the token @@ -61,73 +120,7 @@ defmodule Explorer.Chain.Token do * `icon_url` - URL of the token's icon. * `is_verified_via_admin_panel` - is token verified via admin panel. """ - @type t :: - %Token{ - unquote_splicing(@bridged_field), - name: String.t(), - symbol: String.t(), - total_supply: Decimal.t() | nil, - decimals: non_neg_integer(), - type: String.t(), - cataloged: boolean(), - contract_address: %Ecto.Association.NotLoaded{} | Address.t(), - contract_address_hash: Hash.Address.t(), - holder_count: non_neg_integer() | nil, - skip_metadata: boolean(), - total_supply_updated_at_block: non_neg_integer() | nil, - fiat_value: Decimal.t() | nil, - circulating_market_cap: Decimal.t() | nil, - icon_url: String.t(), - is_verified_via_admin_panel: boolean() - } - - @derive {Poison.Encoder, - except: [ - :__meta__, - :contract_address, - :inserted_at, - :updated_at - ]} - - @derive {Jason.Encoder, - except: [ - :__meta__, - :contract_address, - :inserted_at, - :updated_at - ]} - - @primary_key false - schema "tokens" do - field(:name, :string) - field(:symbol, :string) - field(:total_supply, :decimal) - field(:decimals, :decimal) - field(:type, :string) - field(:cataloged, :boolean) - field(:holder_count, :integer) - field(:skip_metadata, :boolean) - field(:total_supply_updated_at_block, :integer) - field(:fiat_value, :decimal) - field(:circulating_market_cap, :decimal) - field(:icon_url, :string) - field(:is_verified_via_admin_panel, :boolean) - - belongs_to( - :contract_address, - Address, - foreign_key: :contract_address_hash, - primary_key: true, - references: :hash, - type: Hash.Address - ) - - if Application.compile_env(:explorer, BridgedToken)[:enabled] do - field(:bridged, :boolean) - end - - timestamps() - end + Explorer.Chain.Token.Schema.generate() @required_attrs ~w(contract_address_hash type)a @optional_attrs ~w(cataloged decimals name symbol total_supply skip_metadata total_supply_updated_at_block updated_at fiat_value circulating_market_cap icon_url is_verified_via_admin_panel)a diff --git a/apps/explorer/lib/explorer/chain/token/instance.ex b/apps/explorer/lib/explorer/chain/token/instance.ex index 8ea0e03483d2..05d1ba594c52 100644 --- a/apps/explorer/lib/explorer/chain/token/instance.ex +++ b/apps/explorer/lib/explorer/chain/token/instance.ex @@ -6,7 +6,7 @@ defmodule Explorer.Chain.Token.Instance do use Explorer.Schema alias Explorer.{Chain, Helper} - alias Explorer.Chain.{Address, Block, Hash, Token, TokenTransfer} + alias Explorer.Chain.{Address, Hash, Token, TokenTransfer} alias Explorer.Chain.Address.CurrentTokenBalance alias Explorer.Chain.Token.Instance alias Explorer.PagingOptions @@ -17,22 +17,9 @@ defmodule Explorer.Chain.Token.Instance do * `metadata` - Token instance metadata * `error` - error fetching token instance """ - - @type t :: %Instance{ - token_id: non_neg_integer(), - token_contract_address_hash: Hash.Address.t() | nil, - metadata: map() | nil, - error: String.t() | nil, - owner_address_hash: Hash.Address.t() | nil, - owner_updated_at_block: Block.block_number() | nil, - owner_updated_at_log_index: non_neg_integer() | nil, - current_token_balance: any(), - is_unique: bool() | nil - } - @primary_key false - schema "token_instances" do - field(:token_id, :decimal, primary_key: true) + typed_schema "token_instances" do + field(:token_id, :decimal, primary_key: true, null: false) field(:metadata, :map) field(:error, :string) field(:owner_updated_at_block, :integer) @@ -48,7 +35,8 @@ defmodule Explorer.Chain.Token.Instance do foreign_key: :token_contract_address_hash, references: :contract_address_hash, type: Hash.Address, - primary_key: true + primary_key: true, + null: false ) timestamps() @@ -106,7 +94,7 @@ defmodule Explorer.Chain.Token.Instance do |> select([ctb], ctb.address_hash) end - @spec token_instance_query(non_neg_integer(), Hash.Address.t()) :: Ecto.Query.t() + @spec token_instance_query(Decimal.t() | non_neg_integer(), Hash.Address.t()) :: Ecto.Query.t() def token_instance_query(token_id, token_contract_address), do: from(i in Instance, where: i.token_contract_address_hash == ^token_contract_address and i.token_id == ^token_id) diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index e83f87d703c7..13e656365798 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -33,6 +33,17 @@ defmodule Explorer.Chain.TokenTransfer do @default_paging_options %PagingOptions{page_size: 50} + @typep paging_options :: {:paging_options, PagingOptions.t()} + @typep api? :: {:api?, true | false} + + @constant "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + @weth_deposit_signature "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c" + @weth_withdrawal_signature "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" + @erc1155_single_transfer_signature "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62" + @erc1155_batch_transfer_signature "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb" + + @transfer_function_signature "0xa9059cbb" + @typedoc """ * `:amount` - The token transferred amount * `:block_hash` - hash of the block @@ -49,67 +60,47 @@ defmodule Explorer.Chain.TokenTransfer do * `:amounts` - Tokens transferred amounts in case of batched transfer in ERC-1155 * `:token_ids` - IDs of the tokens (applicable to ERC-1155 tokens) """ - @type t :: %TokenTransfer{ - amount: Decimal.t() | nil, - block_number: non_neg_integer() | nil, - block_hash: Hash.Full.t(), - from_address: %Ecto.Association.NotLoaded{} | Address.t(), - from_address_hash: Hash.Address.t(), - to_address: %Ecto.Association.NotLoaded{} | Address.t(), - to_address_hash: Hash.Address.t(), - token_contract_address: %Ecto.Association.NotLoaded{} | Address.t(), - token_contract_address_hash: Hash.Address.t(), - transaction: %Ecto.Association.NotLoaded{} | Transaction.t(), - transaction_hash: Hash.Full.t(), - log_index: non_neg_integer(), - amounts: [Decimal.t()] | nil, - token_ids: [non_neg_integer()] | nil, - index_in_batch: non_neg_integer() | nil - } - - @typep paging_options :: {:paging_options, PagingOptions.t()} - @typep api? :: {:api?, true | false} - - @constant "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" - @weth_deposit_signature "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c" - @weth_withdrawal_signature "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" - @erc1155_single_transfer_signature "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62" - @erc1155_batch_transfer_signature "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb" - - @transfer_function_signature "0xa9059cbb" - @primary_key false - schema "token_transfers" do + typed_schema "token_transfers" do field(:amount, :decimal) - field(:block_number, :integer) - field(:log_index, :integer, primary_key: true) + field(:block_number, :integer) :: Block.block_number() + field(:log_index, :integer, primary_key: true, null: false) field(:amounts, {:array, :decimal}) field(:token_ids, {:array, :decimal}) field(:index_in_batch, :integer, virtual: true) - belongs_to(:from_address, Address, foreign_key: :from_address_hash, references: :hash, type: Hash.Address) - belongs_to(:to_address, Address, foreign_key: :to_address_hash, references: :hash, type: Hash.Address) + belongs_to(:from_address, Address, + foreign_key: :from_address_hash, + references: :hash, + type: Hash.Address, + null: false + ) + + belongs_to(:to_address, Address, foreign_key: :to_address_hash, references: :hash, type: Hash.Address, null: false) belongs_to( :token_contract_address, Address, foreign_key: :token_contract_address_hash, references: :hash, - type: Hash.Address + type: Hash.Address, + null: false ) belongs_to(:transaction, Transaction, foreign_key: :transaction_hash, primary_key: true, references: :hash, - type: Hash.Full + type: Hash.Full, + null: false ) belongs_to(:block, Block, foreign_key: :block_hash, primary_key: true, references: :hash, - type: Hash.Full + type: Hash.Full, + null: false ) has_many( diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 18cdfee76877..10ca56af6a11 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -1,40 +1,195 @@ +defmodule Explorer.Chain.Transaction.Schema do + @moduledoc false + + alias Explorer.Chain.{ + Address, + Block, + Data, + Hash, + InternalTransaction, + Log, + TokenTransfer, + TransactionAction, + Wei + } + + alias Explorer.Chain.Transaction.{Fork, Status} + alias Explorer.Chain.Zkevm.BatchTransaction + + @chain_type_fields (case Application.compile_env(:explorer, :chain_type) do + "suave" -> + elem( + quote do + belongs_to( + :execution_node, + Address, + foreign_key: :execution_node_hash, + references: :hash, + type: Hash.Address + ) + + field(:wrapped_type, :integer) + field(:wrapped_nonce, :integer) + field(:wrapped_gas, :decimal) + field(:wrapped_gas_price, Wei) + field(:wrapped_max_priority_fee_per_gas, Wei) + field(:wrapped_max_fee_per_gas, Wei) + field(:wrapped_value, Wei) + field(:wrapped_input, Data) + field(:wrapped_v, :decimal) + field(:wrapped_r, :decimal) + field(:wrapped_s, :decimal) + field(:wrapped_hash, Hash.Full) + + belongs_to( + :wrapped_to_address, + Address, + foreign_key: :wrapped_to_address_hash, + references: :hash, + type: Hash.Address + ) + end, + 2 + ) + + "polygon_zkevm" -> + elem( + quote do + has_one(:zkevm_batch_transaction, BatchTransaction, foreign_key: :hash, references: :hash) + has_one(:zkevm_batch, through: [:zkevm_batch_transaction, :batch], references: :hash) + + has_one(:zkevm_sequence_transaction, + through: [:zkevm_batch, :sequence_transaction], + references: :hash + ) + + has_one(:zkevm_verify_transaction, + through: [:zkevm_batch, :verify_transaction], + references: :hash + ) + end, + 2 + ) + + _ -> + [] + end) + + defmacro generate do + quote do + @primary_key false + typed_schema "transactions" do + field(:hash, Hash.Full, primary_key: true) + 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) + field(:gas, :decimal) + field(:gas_price, Wei) + field(:gas_used, :decimal) + field(:index, :integer) + field(:created_contract_code_indexed_at, :utc_datetime_usec) + field(:input, Data) + field(:nonce, :integer) :: non_neg_integer() | nil + field(:r, :decimal) + field(:s, :decimal) + field(:status, Status) + field(:v, :decimal) + field(:value, Wei) + field(:revert_reason, :string) + field(:max_priority_fee_per_gas, Wei) + field(:max_fee_per_gas, Wei) + field(:type, :integer) + field(:has_error_in_internal_txs, :boolean) + field(:has_token_transfers, :boolean, virtual: true) + + # stability virtual fields + field(:transaction_fee_log, :any, virtual: true) + field(:transaction_fee_token, :any, virtual: true) + + # A transient field for deriving old block hash during transaction upserts. + # Used to force refetch of a block in case a transaction is re-collated + # in a different block. See: https://github.com/blockscout/blockscout/issues/1911 + field(:old_block_hash, Hash.Full) + + timestamps() + + belongs_to(:block, Block, foreign_key: :block_hash, references: :hash, type: Hash.Full) + has_many(:forks, Fork, foreign_key: :hash, references: :hash) + + belongs_to( + :from_address, + Address, + foreign_key: :from_address_hash, + references: :hash, + type: Hash.Address + ) + + has_many(:internal_transactions, InternalTransaction, foreign_key: :transaction_hash, references: :hash) + has_many(:logs, Log, foreign_key: :transaction_hash, references: :hash) + + has_many(:token_transfers, TokenTransfer, foreign_key: :transaction_hash, references: :hash) + + has_many(:transaction_actions, TransactionAction, + foreign_key: :hash, + preload_order: [asc: :log_index], + references: :hash + ) + + belongs_to( + :to_address, + Address, + foreign_key: :to_address_hash, + references: :hash, + type: Hash.Address + ) + + has_many(:uncles, through: [:forks, :uncle], references: :hash) + + belongs_to( + :created_contract_address, + Address, + foreign_key: :created_contract_address_hash, + references: :hash, + type: Hash.Address + ) + + unquote_splicing(@chain_type_fields) + end + end + end +end + defmodule Explorer.Chain.Transaction do @moduledoc "Models a Web3 transaction." use Explorer.Schema require Logger + require Explorer.Chain.Transaction.Schema alias ABI.FunctionSelector - alias Ecto.Association.NotLoaded alias Ecto.Changeset - - alias Explorer.{Chain, Repo} + alias Explorer.{Chain, Helper, PagingOptions, Repo, SortingHelper} alias Explorer.Chain.{ - Address, - Block, + Block.Reward, ContractMethod, Data, DenormalizationHelper, - Gas, Hash, - InternalTransaction, Log, SmartContract, + SmartContract.Proxy, Token, TokenTransfer, Transaction, - TransactionAction, Wei } - alias Explorer.Chain.Block.Reward - alias Explorer.Chain.SmartContract.Proxy - alias Explorer.Chain.Transaction.{Fork, Status} - alias Explorer.Chain.Zkevm.BatchTransaction - alias Explorer.{PagingOptions, SortingHelper} alias Explorer.SmartContract.SigProviderInterface @optional_attrs ~w(max_priority_fee_per_gas max_fee_per_gas block_hash block_number block_consensus block_timestamp created_contract_address_hash cumulative_gas_used earliest_processing_start @@ -85,6 +240,48 @@ defmodule Explorer.Chain.Transaction do """ @type wei_per_gas :: Wei.t() + @derive {Poison.Encoder, + only: [ + :block_number, + :block_timestamp, + :cumulative_gas_used, + :error, + :gas, + :gas_price, + :gas_used, + :index, + :created_contract_code_indexed_at, + :input, + :nonce, + :r, + :s, + :v, + :status, + :value, + :revert_reason + ]} + + @derive {Jason.Encoder, + only: [ + :block_number, + :block_timestamp, + :cumulative_gas_used, + :error, + :gas, + :gas_price, + :gas_used, + :index, + :created_contract_code_indexed_at, + :input, + :nonce, + :r, + :s, + :v, + :status, + :value, + :revert_reason + ]} + @typedoc """ * `block` - the block in which this transaction was mined/validated. `nil` when transaction is pending or has only been collated into one of the `uncles` in one of the `forks`. @@ -166,224 +363,7 @@ defmodule Explorer.Chain.Transaction do * `wrapped_s` - S field of the signature from the `wrapped` field (used by Suave) * `wrapped_hash` - hash from the `wrapped` field (used by Suave) """ - @type t :: - Map.merge( - %__MODULE__{ - 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, - cumulative_gas_used: Gas.t() | nil, - earliest_processing_start: DateTime.t() | nil, - error: String.t() | nil, - forks: %Ecto.Association.NotLoaded{} | [Fork.t()], - from_address: %Ecto.Association.NotLoaded{} | Address.t(), - from_address_hash: Hash.Address.t(), - gas: Gas.t(), - gas_price: wei_per_gas | nil, - gas_used: Gas.t() | nil, - hash: Hash.t(), - index: transaction_index | nil, - input: Data.t(), - internal_transactions: %Ecto.Association.NotLoaded{} | [InternalTransaction.t()], - logs: %Ecto.Association.NotLoaded{} | [Log.t()], - nonce: non_neg_integer(), - r: r(), - s: s(), - status: Status.t() | nil, - to_address: %Ecto.Association.NotLoaded{} | Address.t() | nil, - to_address_hash: Hash.Address.t() | nil, - uncles: %Ecto.Association.NotLoaded{} | [Block.t()], - v: v(), - value: Wei.t(), - revert_reason: String.t() | nil, - max_priority_fee_per_gas: wei_per_gas | nil, - max_fee_per_gas: wei_per_gas | nil, - type: non_neg_integer() | nil, - has_error_in_internal_txs: boolean(), - transaction_fee_log: any(), - transaction_fee_token: any() - }, - suave - ) - - if Application.compile_env(:explorer, :chain_type) == "suave" do - @type suave :: %{ - execution_node: %Ecto.Association.NotLoaded{} | Address.t() | nil, - execution_node_hash: Hash.Address.t() | nil, - wrapped_type: non_neg_integer() | nil, - wrapped_nonce: non_neg_integer() | nil, - wrapped_to_address: %Ecto.Association.NotLoaded{} | Address.t() | nil, - wrapped_to_address_hash: Hash.Address.t() | nil, - wrapped_gas: Gas.t() | nil, - wrapped_gas_price: wei_per_gas | nil, - wrapped_max_priority_fee_per_gas: wei_per_gas | nil, - wrapped_max_fee_per_gas: wei_per_gas | nil, - wrapped_value: Wei.t() | nil, - wrapped_input: Data.t() | nil, - wrapped_v: v() | nil, - wrapped_r: r() | nil, - wrapped_s: s() | nil, - wrapped_hash: Hash.t() | nil - } - else - @type suave :: %{} - end - - @derive {Poison.Encoder, - only: [ - :block_number, - :block_timestamp, - :cumulative_gas_used, - :error, - :gas, - :gas_price, - :gas_used, - :index, - :created_contract_code_indexed_at, - :input, - :nonce, - :r, - :s, - :v, - :status, - :value, - :revert_reason - ]} - - @derive {Jason.Encoder, - only: [ - :block_number, - :block_timestamp, - :cumulative_gas_used, - :error, - :gas, - :gas_price, - :gas_used, - :index, - :created_contract_code_indexed_at, - :input, - :nonce, - :r, - :s, - :v, - :status, - :value, - :revert_reason - ]} - - @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) - field(:gas, :decimal) - field(:gas_price, Wei) - field(:gas_used, :decimal) - field(:index, :integer) - field(:created_contract_code_indexed_at, :utc_datetime_usec) - field(:input, Data) - field(:nonce, :integer) - field(:r, :decimal) - field(:s, :decimal) - field(:status, Status) - field(:v, :decimal) - field(:value, Wei) - field(:revert_reason, :string) - field(:max_priority_fee_per_gas, Wei) - field(:max_fee_per_gas, Wei) - field(:type, :integer) - field(:has_error_in_internal_txs, :boolean) - field(:has_token_transfers, :boolean, virtual: true) - - # stability virtual fields - field(:transaction_fee_log, :any, virtual: true) - field(:transaction_fee_token, :any, virtual: true) - - # A transient field for deriving old block hash during transaction upserts. - # Used to force refetch of a block in case a transaction is re-collated - # in a different block. See: https://github.com/blockscout/blockscout/issues/1911 - field(:old_block_hash, Hash.Full) - - timestamps() - - belongs_to(:block, Block, foreign_key: :block_hash, references: :hash, type: Hash.Full) - has_many(:forks, Fork, foreign_key: :hash) - - belongs_to( - :from_address, - Address, - foreign_key: :from_address_hash, - references: :hash, - type: Hash.Address - ) - - has_many(:internal_transactions, InternalTransaction, foreign_key: :transaction_hash) - has_many(:logs, Log, foreign_key: :transaction_hash) - has_many(:token_transfers, TokenTransfer, foreign_key: :transaction_hash) - has_many(:transaction_actions, TransactionAction, foreign_key: :hash, preload_order: [asc: :log_index]) - - belongs_to( - :to_address, - Address, - foreign_key: :to_address_hash, - references: :hash, - type: Hash.Address - ) - - has_many(:uncles, through: [:forks, :uncle]) - - has_one(:zkevm_batch_transaction, BatchTransaction, foreign_key: :hash) - has_one(:zkevm_batch, through: [:zkevm_batch_transaction, :batch]) - has_one(:zkevm_sequence_transaction, through: [:zkevm_batch, :sequence_transaction]) - has_one(:zkevm_verify_transaction, through: [:zkevm_batch, :verify_transaction]) - - belongs_to( - :created_contract_address, - Address, - foreign_key: :created_contract_address_hash, - references: :hash, - type: Hash.Address - ) - - if System.get_env("CHAIN_TYPE") == "suave" do - belongs_to( - :execution_node, - Address, - foreign_key: :execution_node_hash, - references: :hash, - type: Hash.Address - ) - - field(:wrapped_type, :integer) - field(:wrapped_nonce, :integer) - field(:wrapped_gas, :decimal) - field(:wrapped_gas_price, Wei) - field(:wrapped_max_priority_fee_per_gas, Wei) - field(:wrapped_max_fee_per_gas, Wei) - field(:wrapped_value, Wei) - field(:wrapped_input, Data) - field(:wrapped_v, :decimal) - field(:wrapped_r, :decimal) - field(:wrapped_s, :decimal) - field(:wrapped_hash, Hash.Full) - - belongs_to( - :wrapped_to_address, - Address, - foreign_key: :wrapped_to_address_hash, - references: :hash, - type: Hash.Address - ) - end - end + Explorer.Chain.Transaction.Schema.generate() @doc """ A pending transaction does not have a `block_hash` @@ -639,7 +619,7 @@ defmodule Explorer.Chain.Transaction do def decoded_input_data( %__MODULE__{ - to_address: %NotLoaded{}, + to_address: %{smart_contract: nil}, input: input, hash: hash }, @@ -650,7 +630,7 @@ defmodule Explorer.Chain.Transaction do ) do decoded_input_data( %__MODULE__{ - to_address: %{smart_contract: nil}, + to_address: %NotLoaded{}, input: input, hash: hash }, @@ -674,7 +654,7 @@ defmodule Explorer.Chain.Transaction do ) do decoded_input_data( %__MODULE__{ - to_address: %{smart_contract: nil}, + to_address: %NotLoaded{}, input: input, hash: hash }, @@ -687,7 +667,7 @@ defmodule Explorer.Chain.Transaction do def decoded_input_data( %__MODULE__{ - to_address: %{smart_contract: nil}, + to_address: %NotLoaded{}, input: %{bytes: <> = data} = input, hash: hash }, @@ -714,7 +694,7 @@ defmodule Explorer.Chain.Transaction do full_abi_acc, methods_acc} end - def decoded_input_data(%__MODULE__{to_address: %{smart_contract: nil}}, _, _, full_abi_acc, methods_acc) do + def decoded_input_data(%__MODULE__{to_address: %NotLoaded{}}, _, _, full_abi_acc, methods_acc) do {{:error, :contract_not_verified, []}, full_abi_acc, methods_acc} end @@ -734,7 +714,7 @@ defmodule Explorer.Chain.Transaction do {{:error, :could_not_decode}, full_abi_acc} -> case decoded_input_data( %__MODULE__{ - to_address: %{smart_contract: nil}, + to_address: %NotLoaded{}, input: input, hash: hash }, @@ -830,7 +810,7 @@ defmodule Explorer.Chain.Transaction do else case decoded_input_data( %__MODULE__{ - to_address: %{smart_contract: nil}, + to_address: %NotLoaded{}, input: transaction.input, hash: transaction.hash }, @@ -1653,7 +1633,7 @@ defmodule Explorer.Chain.Transaction do @doc """ Return the dynamic that calculates the fee for transactions. """ - @spec dynamic_fee :: struct() + @spec dynamic_fee :: Ecto.Query.dynamic_expr() def dynamic_fee do dynamic([tx], tx.gas_price * fragment("COALESCE(?, ?)", tx.gas_used, tx.gas)) end @@ -1676,4 +1656,48 @@ defmodule Explorer.Chain.Transaction do "hash" => hash } end + + @suave_bid_event "0x83481d5b04dea534715acad673a8177a46fc93882760f36bdc16ccac439d504e" + + @spec suave_parse_allowed_peekers(Ecto.Schema.has_many(Log.t())) :: [String.t()] + def suave_parse_allowed_peekers(%NotLoaded{}), do: [] + + def suave_parse_allowed_peekers(logs) do + suave_bid_contracts = + Application.get_all_env(:explorer)[Transaction][:suave_bid_contracts] + |> String.split(",") + |> Enum.map(fn sbc -> String.downcase(String.trim(sbc)) end) + + bid_event = + Enum.find(logs, fn log -> + sanitize_log_first_topic(log.first_topic) == @suave_bid_event && + Enum.member?(suave_bid_contracts, String.downcase(Hash.to_string(log.address_hash))) + end) + + if is_nil(bid_event) do + [] + else + [_bid_id, _decryption_condition, allowed_peekers] = + Helper.decode_data(bid_event.data, [{:bytes, 16}, {:uint, 64}, {:array, :address}]) + + Enum.map(allowed_peekers, fn peeker -> + "0x" <> Base.encode16(peeker, case: :lower) + end) + end + end + + defp sanitize_log_first_topic(first_topic) do + if is_nil(first_topic) do + "" + else + sanitized = + if is_binary(first_topic) do + first_topic + else + Hash.to_string(first_topic) + end + + String.downcase(sanitized) + end + end end diff --git a/apps/explorer/lib/explorer/chain/transaction/fork.ex b/apps/explorer/lib/explorer/chain/transaction/fork.ex index 74e2fc921d35..794d64c17360 100644 --- a/apps/explorer/lib/explorer/chain/transaction/fork.ex +++ b/apps/explorer/lib/explorer/chain/transaction/fork.ex @@ -20,22 +20,14 @@ defmodule Explorer.Chain.Transaction.Fork do * `uncle` - the block in which this transaction was mined/validated. * `uncle_hash` - `uncle` foreign key. """ - @type t :: %__MODULE__{ - hash: Hash.t(), - index: Transaction.transaction_index(), - transaction: %Ecto.Association.NotLoaded{} | Transaction.t(), - uncle: %Ecto.Association.NotLoaded{} | Block.t(), - uncle_hash: Hash.t() - } - @primary_key false - schema "transaction_forks" do - field(:index, :integer) + typed_schema "transaction_forks" do + field(:index, :integer, null: false) timestamps() - belongs_to(:transaction, Transaction, foreign_key: :hash, references: :hash, type: Hash.Full) - belongs_to(:uncle, Block, foreign_key: :uncle_hash, references: :hash, type: Hash.Full) + belongs_to(:transaction, Transaction, foreign_key: :hash, references: :hash, type: Hash.Full, null: false) + belongs_to(:uncle, Block, foreign_key: :uncle_hash, references: :hash, type: Hash.Full, null: false) end @doc """ diff --git a/apps/explorer/lib/explorer/chain/transaction/history/transaction_stats.ex b/apps/explorer/lib/explorer/chain/transaction/history/transaction_stats.ex index 9d8363ae676e..594d66bbae4a 100644 --- a/apps/explorer/lib/explorer/chain/transaction/history/transaction_stats.ex +++ b/apps/explorer/lib/explorer/chain/transaction/history/transaction_stats.ex @@ -14,13 +14,6 @@ defmodule Explorer.Chain.Transaction.History.TransactionStats do :__meta__ ]} - schema "transaction_stats" do - field(:date, :date) - field(:number_of_transactions, :integer) - field(:gas_used, :decimal) - field(:total_fee, :decimal) - end - @typedoc """ The recorded values of the number of transactions for a single day. * `:date` - The date in UTC. @@ -28,12 +21,12 @@ defmodule Explorer.Chain.Transaction.History.TransactionStats do * `:gas_used` - Gas used in transactions per single day * `:total_fee` - Total fee paid to validators from success transactions per single day """ - @type t :: %__MODULE__{ - date: Date.t(), - number_of_transactions: integer(), - gas_used: non_neg_integer(), - total_fee: non_neg_integer() - } + typed_schema "transaction_stats" do + field(:date, :date) + field(:number_of_transactions, :integer) + field(:gas_used, :decimal) + field(:total_fee, :decimal) + end @spec by_date_range(Date.t(), Date.t()) :: [__MODULE__] def by_date_range(earliest, latest, options \\ []) do diff --git a/apps/explorer/lib/explorer/chain/transaction_action.ex b/apps/explorer/lib/explorer/chain/transaction_action.ex index a280d85fefa5..a8aba0bc5a59 100644 --- a/apps/explorer/lib/explorer/chain/transaction_action.ex +++ b/apps/explorer/lib/explorer/chain/transaction_action.ex @@ -18,18 +18,10 @@ defmodule Explorer.Chain.TransactionAction do * `type` - type of the action protocol (see possible values for Enum of the db table field) * `log_index` - index of the action for sorting (taken from log.index) """ - @type t :: %__MODULE__{ - hash: Hash.t(), - protocol: String.t(), - data: map(), - type: String.t(), - log_index: non_neg_integer() - } - @primary_key false - schema "transaction_actions" do - field(:protocol, Ecto.Enum, values: @supported_protocols) - field(:data, :map) + typed_schema "transaction_actions" do + field(:protocol, Ecto.Enum, values: @supported_protocols, null: false) + field(:data, :map, null: false) field(:type, Ecto.Enum, values: [ @@ -54,12 +46,19 @@ defmodule Explorer.Chain.TransactionAction do :enable_collateral, :disable_collateral, :liquidation_call - ] + ], + null: false ) - field(:log_index, :integer, primary_key: true) + field(:log_index, :integer, primary_key: true, null: false) - belongs_to(:transaction, Transaction, foreign_key: :hash, primary_key: true, references: :hash, type: Hash.Full) + belongs_to(:transaction, Transaction, + foreign_key: :hash, + primary_key: true, + references: :hash, + type: Hash.Full, + null: false + ) timestamps() end diff --git a/apps/explorer/lib/explorer/chain/user_operation.ex b/apps/explorer/lib/explorer/chain/user_operation.ex index 75300f6ea5fc..6bcb4d2638f7 100644 --- a/apps/explorer/lib/explorer/chain/user_operation.ex +++ b/apps/explorer/lib/explorer/chain/user_operation.ex @@ -22,18 +22,11 @@ defmodule Explorer.Chain.UserOperation do * `block_number` - the block number, where user operation happened. * `block_hash` - the block hash, where user operation happened. """ - - @type t :: %Explorer.Chain.UserOperation{ - hash: Hash.Full.t(), - block_number: Explorer.Chain.Block.block_number() | nil, - block_hash: Hash.Full.t() - } - @primary_key false - schema "user_operations" do - field(:hash, Hash.Full, primary_key: true) - field(:block_number, :integer) - field(:block_hash, Hash.Full) + typed_schema "user_operations" do + field(:hash, Hash.Full, primary_key: true, null: false) + field(:block_number, :integer, null: false) + field(:block_hash, Hash.Full, null: false) timestamps() end diff --git a/apps/explorer/lib/explorer/chain/validator.ex b/apps/explorer/lib/explorer/chain/validator.ex index 14c416a1f8f2..76447d851921 100644 --- a/apps/explorer/lib/explorer/chain/validator.ex +++ b/apps/explorer/lib/explorer/chain/validator.ex @@ -8,8 +8,8 @@ defmodule Explorer.Chain.Validator do alias Explorer.{Chain, Repo} @primary_key false - schema "validators" do - field(:address_hash, Address, primary_key: true) + typed_schema "validators" do + field(:address_hash, Address, primary_key: true, null: false) field(:is_validator, :boolean) field(:payout_key_hash, Address) field(:info_updated_at_block, :integer) diff --git a/apps/explorer/lib/explorer/chain/withdrawal.ex b/apps/explorer/lib/explorer/chain/withdrawal.ex index 250307c5670c..ecba16f92ffd 100644 --- a/apps/explorer/lib/explorer/chain/withdrawal.ex +++ b/apps/explorer/lib/explorer/chain/withdrawal.ex @@ -8,33 +8,26 @@ defmodule Explorer.Chain.Withdrawal do alias Explorer.Chain.{Address, Block, Hash, Wei} alias Explorer.PagingOptions - @type t :: %__MODULE__{ - index: non_neg_integer(), - validator_index: non_neg_integer(), - amount: Wei.t(), - block: %Ecto.Association.NotLoaded{} | Block.t(), - block_hash: Hash.Full.t(), - address: %Ecto.Association.NotLoaded{} | Address.t(), - address_hash: Hash.Address.t() - } - @required_attrs ~w(index validator_index amount address_hash block_hash)a - @primary_key {:index, :integer, autogenerate: false} - schema "withdrawals" do - field(:validator_index, :integer) - field(:amount, Wei) + @primary_key false + typed_schema "withdrawals" do + field(:index, :integer, primary_key: true, null: false) + field(:validator_index, :integer, null: false) + field(:amount, Wei, null: false) belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, - type: Hash.Address + type: Hash.Address, + null: false ) belongs_to(:block, Block, foreign_key: :block_hash, references: :hash, - type: Hash.Full + type: Hash.Full, + null: false ) timestamps() diff --git a/apps/explorer/lib/explorer/chain/zkevm/batch_transaction.ex b/apps/explorer/lib/explorer/chain/zkevm/batch_transaction.ex index 74f5fb9b9f86..c60c7ab07ba8 100644 --- a/apps/explorer/lib/explorer/chain/zkevm/batch_transaction.ex +++ b/apps/explorer/lib/explorer/chain/zkevm/batch_transaction.ex @@ -8,19 +8,19 @@ defmodule Explorer.Chain.Zkevm.BatchTransaction do @required_attrs ~w(batch_number hash)a - @type t :: %__MODULE__{ - batch_number: non_neg_integer(), - batch: %Ecto.Association.NotLoaded{} | TransactionBatch.t() | nil, - hash: Hash.t(), - l2_transaction: %Ecto.Association.NotLoaded{} | Transaction.t() | nil - } - @primary_key false - schema "zkevm_batch_l2_transactions" do - belongs_to(:batch, TransactionBatch, foreign_key: :batch_number, references: :number, type: :integer) - belongs_to(:l2_transaction, Transaction, foreign_key: :hash, primary_key: true, references: :hash, type: Hash.Full) + typed_schema "zkevm_batch_l2_transactions" do + belongs_to(:batch, TransactionBatch, foreign_key: :batch_number, references: :number, type: :integer, null: false) + + belongs_to(:l2_transaction, Transaction, + foreign_key: :hash, + primary_key: true, + references: :hash, + type: Hash.Full, + null: false + ) - timestamps() + timestamps(null: false) end @doc """ diff --git a/apps/explorer/lib/explorer/chain/zkevm/lifecycle_transaction.ex b/apps/explorer/lib/explorer/chain/zkevm/lifecycle_transaction.ex index 480b0c0c80fa..4e238dba9670 100644 --- a/apps/explorer/lib/explorer/chain/zkevm/lifecycle_transaction.ex +++ b/apps/explorer/lib/explorer/chain/zkevm/lifecycle_transaction.ex @@ -8,18 +8,14 @@ defmodule Explorer.Chain.Zkevm.LifecycleTransaction do @required_attrs ~w(id hash is_verify)a - @type t :: %__MODULE__{ - hash: Hash.t(), - is_verify: boolean() - } - - @primary_key {:id, :integer, autogenerate: false} - schema "zkevm_lifecycle_l1_transactions" do - field(:hash, Hash.Full) - field(:is_verify, :boolean) - - has_many(:sequenced_batches, TransactionBatch, foreign_key: :sequence_id) - has_many(:verified_batches, TransactionBatch, foreign_key: :verify_id) + @primary_key false + typed_schema "zkevm_lifecycle_l1_transactions" do + field(:id, :integer, primary_key: true, null: false) + field(:hash, Hash.Full, null: false) + field(:is_verify, :boolean, null: false) + + has_many(:sequenced_batches, TransactionBatch, foreign_key: :sequence_id, references: :id) + has_many(:verified_batches, TransactionBatch, foreign_key: :verify_id, references: :id) timestamps() end diff --git a/apps/explorer/lib/explorer/chain/zkevm/transaction_batch.ex b/apps/explorer/lib/explorer/chain/zkevm/transaction_batch.ex index eda97e1d403c..34c2bdbbab96 100644 --- a/apps/explorer/lib/explorer/chain/zkevm/transaction_batch.ex +++ b/apps/explorer/lib/explorer/chain/zkevm/transaction_batch.ex @@ -10,22 +10,9 @@ defmodule Explorer.Chain.Zkevm.TransactionBatch do @required_attrs ~w(number timestamp l2_transactions_count global_exit_root acc_input_hash state_root)a - @type t :: %__MODULE__{ - number: non_neg_integer(), - timestamp: DateTime.t(), - l2_transactions_count: non_neg_integer(), - global_exit_root: Hash.t(), - acc_input_hash: Hash.t(), - state_root: Hash.t(), - sequence_id: non_neg_integer() | nil, - sequence_transaction: %Ecto.Association.NotLoaded{} | LifecycleTransaction.t() | nil, - verify_id: non_neg_integer() | nil, - verify_transaction: %Ecto.Association.NotLoaded{} | LifecycleTransaction.t() | nil, - l2_transactions: %Ecto.Association.NotLoaded{} | [BatchTransaction.t()] - } - - @primary_key {:number, :integer, autogenerate: false} - schema "zkevm_transaction_batches" do + @primary_key false + typed_schema "zkevm_transaction_batches" do + field(:number, :integer, primary_key: true, null: false) field(:timestamp, :utc_datetime_usec) field(:l2_transactions_count, :integer) field(:global_exit_root, Hash.Full) @@ -40,7 +27,7 @@ defmodule Explorer.Chain.Zkevm.TransactionBatch do belongs_to(:verify_transaction, LifecycleTransaction, foreign_key: :verify_id, references: :id, type: :integer) - has_many(:l2_transactions, BatchTransaction, foreign_key: :batch_number) + has_many(:l2_transactions, BatchTransaction, foreign_key: :batch_number, references: :number) timestamps() end diff --git a/apps/explorer/lib/explorer/counters/last_fetched_counter.ex b/apps/explorer/lib/explorer/counters/last_fetched_counter.ex index e4d684c2f167..0acc752e9538 100644 --- a/apps/explorer/lib/explorer/counters/last_fetched_counter.ex +++ b/apps/explorer/lib/explorer/counters/last_fetched_counter.ex @@ -3,19 +3,13 @@ defmodule Explorer.Counters.LastFetchedCounter do Stores last fetched counters. """ - alias Explorer.Counters.LastFetchedCounter use Explorer.Schema import Ecto.Changeset - @type t :: %LastFetchedCounter{ - counter_type: String.t(), - value: Decimal.t() - } - @primary_key false - schema "last_fetched_counters" do - field(:counter_type, :string) + typed_schema "last_fetched_counters" do + field(:counter_type, :string, null: false) field(:value, :decimal) timestamps() diff --git a/apps/explorer/lib/explorer/encrypted/address_hash.ex b/apps/explorer/lib/explorer/encrypted/address_hash.ex index 4518951298ee..fb90251f1e60 100644 --- a/apps/explorer/lib/explorer/encrypted/address_hash.ex +++ b/apps/explorer/lib/explorer/encrypted/address_hash.ex @@ -2,4 +2,6 @@ defmodule Explorer.Encrypted.AddressHash do @moduledoc false use Explorer.Encrypted.Types.AddressHash, vault: Explorer.Vault + + @type t :: Explorer.Chain.Hash.Address.t() end diff --git a/apps/explorer/lib/explorer/encrypted/binary.ex b/apps/explorer/lib/explorer/encrypted/binary.ex index 6de296ded69d..7a5e62bd39af 100644 --- a/apps/explorer/lib/explorer/encrypted/binary.ex +++ b/apps/explorer/lib/explorer/encrypted/binary.ex @@ -2,4 +2,6 @@ defmodule Explorer.Encrypted.Binary do @moduledoc false use Cloak.Ecto.Binary, vault: Explorer.Vault + + @type t :: binary() end diff --git a/apps/explorer/lib/explorer/encrypted/transaction_hash.ex b/apps/explorer/lib/explorer/encrypted/transaction_hash.ex index a783cb899b2b..fa240c27e3b1 100644 --- a/apps/explorer/lib/explorer/encrypted/transaction_hash.ex +++ b/apps/explorer/lib/explorer/encrypted/transaction_hash.ex @@ -2,4 +2,6 @@ defmodule Explorer.Encrypted.TransactionHash do @moduledoc false use Explorer.Encrypted.Types.TransactionHash, vault: Explorer.Vault + + @type t :: Explorer.Chain.Hash.Full.t() end diff --git a/apps/explorer/lib/explorer/market/market_history.ex b/apps/explorer/lib/explorer/market/market_history.ex index ea27a083aa48..6aa24f2f6253 100644 --- a/apps/explorer/lib/explorer/market/market_history.ex +++ b/apps/explorer/lib/explorer/market/market_history.ex @@ -5,14 +5,6 @@ defmodule Explorer.Market.MarketHistory do use Explorer.Schema - schema "market_history" do - field(:closing_price, :decimal) - field(:date, :date) - field(:opening_price, :decimal) - field(:market_cap, :decimal) - field(:tvl, :decimal) - end - @typedoc """ The recorded values of the configured coin to USD for a single day. @@ -22,11 +14,11 @@ defmodule Explorer.Market.MarketHistory do * `:market_cap` - Market cap in USD. * `:tvl` - TVL in USD. """ - @type t :: %__MODULE__{ - closing_price: Decimal.t() | nil, - date: Date.t(), - opening_price: Decimal.t() | nil, - market_cap: Decimal.t() | nil, - tvl: Decimal.t() | nil - } + typed_schema "market_history" do + field(:closing_price, :decimal) + field(:date, :date, null: false) + field(:opening_price, :decimal) + field(:market_cap, :decimal) + field(:tvl, :decimal) + end end diff --git a/apps/explorer/lib/explorer/migrator/migration_status.ex b/apps/explorer/lib/explorer/migrator/migration_status.ex index 01a7bbd54084..e59011e64a89 100644 --- a/apps/explorer/lib/explorer/migrator/migration_status.ex +++ b/apps/explorer/lib/explorer/migrator/migration_status.ex @@ -7,7 +7,7 @@ defmodule Explorer.Migrator.MigrationStatus do alias Explorer.Repo @primary_key false - schema "migrations_status" do + typed_schema "migrations_status" do field(:migration_name, :string) # ["started", "completed"] field(:status, :string) diff --git a/apps/explorer/lib/explorer/schema.ex b/apps/explorer/lib/explorer/schema.ex index b59631550f56..ef88e49fa60d 100644 --- a/apps/explorer/lib/explorer/schema.ex +++ b/apps/explorer/lib/explorer/schema.ex @@ -3,7 +3,7 @@ defmodule Explorer.Schema do defmacro __using__(_opts) do quote do - use Ecto.Schema + use TypedEctoSchema import Ecto.{Changeset, Query} diff --git a/apps/explorer/lib/explorer/tags/address_tag.ex b/apps/explorer/lib/explorer/tags/address_tag.ex index cfb61d3324b7..65d7cc008baf 100644 --- a/apps/explorer/lib/explorer/tags/address_tag.ex +++ b/apps/explorer/lib/explorer/tags/address_tag.ex @@ -20,13 +20,9 @@ defmodule Explorer.Tags.AddressTag do * `:label` - Tag's label * `:display_name` - Label's display name """ - @type t :: %AddressTag{ - label: String.t() - } - - schema "address_tags" do - field(:label, :string) - field(:display_name, :string) + typed_schema "address_tags" do + field(:label, :string, null: false) + field(:display_name, :string, null: false) has_many(:tag_id, AddressToTag, foreign_key: :id) timestamps() diff --git a/apps/explorer/lib/explorer/tags/address_to_tag.ex b/apps/explorer/lib/explorer/tags/address_to_tag.ex index a08e21290ac3..9c2f8d410ca1 100644 --- a/apps/explorer/lib/explorer/tags/address_to_tag.ex +++ b/apps/explorer/lib/explorer/tags/address_to_tag.ex @@ -17,18 +17,14 @@ defmodule Explorer.Tags.AddressToTag do * `:tag_id` - id of Tag * `:address_hash` - hash of Address """ - @type t :: %AddressToTag{ - tag_id: Decimal.t(), - address_hash: Hash.Address.t() - } - - schema "address_to_tags" do + typed_schema "address_to_tags" do belongs_to( :tag, AddressTag, foreign_key: :tag_id, references: :id, - type: :integer + type: :integer, + null: false ) belongs_to( @@ -36,7 +32,8 @@ defmodule Explorer.Tags.AddressToTag do Address, foreign_key: :address_hash, references: :hash, - type: Hash.Address + type: Hash.Address, + null: false ) timestamps() diff --git a/apps/explorer/lib/explorer/utility/event_notification.ex b/apps/explorer/lib/explorer/utility/event_notification.ex index 8ae9dd0a2f5f..ccb52ea5e9b0 100644 --- a/apps/explorer/lib/explorer/utility/event_notification.ex +++ b/apps/explorer/lib/explorer/utility/event_notification.ex @@ -5,7 +5,7 @@ defmodule Explorer.Utility.EventNotification do use Explorer.Schema - schema "event_notifications" do + typed_schema "event_notifications" do field(:data, :string) end diff --git a/apps/explorer/lib/explorer/utility/missing_block_range.ex b/apps/explorer/lib/explorer/utility/missing_block_range.ex index 4892334d5fd5..2a193c7108b1 100644 --- a/apps/explorer/lib/explorer/utility/missing_block_range.ex +++ b/apps/explorer/lib/explorer/utility/missing_block_range.ex @@ -8,7 +8,7 @@ defmodule Explorer.Utility.MissingBlockRange do @default_returning_batch_size 10 - schema "missing_block_ranges" do + typed_schema "missing_block_ranges" do field(:from_number, :integer) field(:to_number, :integer) end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index ff106dc4e1d3..b00731d3e74c 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -118,7 +118,8 @@ defmodule Explorer.Mixfile do {:cloak_ecto, "~> 1.2.0"}, {:redix, "~> 1.1"}, {:hammer_backend_redis, "~> 6.1"}, - {:logger_json, "~> 5.1"} + {:logger_json, "~> 5.1"}, + {:typed_ecto_schema, "~> 0.4.1", runtime: false} ] end diff --git a/mix.lock b/mix.lock index 7c8fd9c13ebd..2db02bb5cf22 100644 --- a/mix.lock +++ b/mix.lock @@ -135,6 +135,7 @@ "tesla": {:hex, :tesla, "1.8.0", "d511a4f5c5e42538d97eef7c40ec4f3e44effdc5068206f42ed859e09e51d1fd", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "10501f360cd926a309501287470372af1a6e1cbed0f43949203a4c13300bc79f"}, "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, "toml": {:hex, :toml, "0.6.2", "38f445df384a17e5d382befe30e3489112a48d3ba4c459e543f748c2f25dd4d1", [:mix], [], "hexpm", "d013e45126d74c0c26a38d31f5e8e9b83ea19fc752470feb9a86071ca5a672fa"}, + "typed_ecto_schema": {:hex, :typed_ecto_schema, "0.4.1", "a373ca6f693f4de84cde474a67467a9cb9051a8a7f3f615f1e23dc74b75237fa", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "85c6962f79d35bf543dd5659c6adc340fd2480cacc6f25d2cc2933ea6e8fcb3b"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, "ueberauth": {:hex, :ueberauth, "0.10.7", "5a31cbe11e7ce5c7484d745dc9e1f11948e89662f8510d03c616de03df581ebd", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "0bccf73e2ffd6337971340832947ba232877aa8122dba4c95be9f729c8987377"}, "ueberauth_auth0": {:hex, :ueberauth_auth0, "2.1.0", "0632d5844049fa2f26823f15e1120aa32f27df6f27ce515a4b04641736594bf4", [:mix], [{:oauth2, "~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "8d3b30fa27c95c9e82c30c4afb016251405706d2e9627e603c3c9787fd1314fc"}, From e1871b0ff376ca81c237a8d3ae206aa0969e80a6 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Sat, 20 Jan 2024 00:04:02 +0300 Subject: [PATCH 397/607] Add base and priority fee to gas oracle response --- CHANGELOG.md | 2 + .../views/api/v2/block_view.ex | 52 +++++------- apps/explorer/lib/explorer/chain/block.ex | 79 ++++++++++++++++++ .../explorer/chain/cache/gas_price_oracle.ex | 42 ++++++---- .../test/explorer/chain/block_test.exs | 40 +++++++++ .../chain/cache/gas_price_oracle_test.exs | 81 +++++-------------- config/runtime.exs | 3 +- 7 files changed, 187 insertions(+), 112 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5cd1366efec..688703d61ce1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Features +- [#9202](https://github.com/blockscout/blockscout/pull/9202) - Add base and priority fee to gas oracle response + ### Fixes - [#9317](https://github.com/blockscout/blockscout/pull/9317) - Include null gas price txs in fee calculations diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex index 9c4b40a3516f..5c2c09cd5fa2 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex @@ -53,8 +53,8 @@ defmodule BlockScoutWeb.API.V2.BlockView do "uncles_hashes" => prepare_uncles(block.uncle_relations), # "state_root" => "TODO", "rewards" => prepare_rewards(block.rewards, block, single_block?), - "gas_target_percentage" => gas_target(block), - "gas_used_percentage" => gas_used_percentage(block), + "gas_target_percentage" => Block.gas_target(block), + "gas_used_percentage" => Block.gas_used_percentage(block), "burnt_fees_percentage" => burnt_fees_percentage(burnt_fees, transaction_fees), "type" => block |> BlockView.block_type() |> String.downcase(), "tx_fees" => transaction_fees, @@ -84,24 +84,6 @@ defmodule BlockScoutWeb.API.V2.BlockView do %{"hash" => uncle_relation.uncle_hash} end - def gas_target(block) do - if Decimal.compare(block.gas_limit, 0) == :gt do - elasticity_multiplier = Application.get_env(:explorer, :elasticity_multiplier) - ratio = Decimal.div(block.gas_used, Decimal.div(block.gas_limit, elasticity_multiplier)) - ratio |> Decimal.sub(1) |> Decimal.mult(100) |> Decimal.to_float() - else - Decimal.new(0) - end - end - - def gas_used_percentage(block) do - if Decimal.compare(block.gas_limit, 0) == :gt do - block.gas_used |> Decimal.div(block.gas_limit) |> Decimal.mult(100) |> Decimal.to_float() - else - Decimal.new(0) - end - end - def burnt_fees_percentage(_, %Decimal{coef: 0}), do: nil def burnt_fees_percentage(burnt_fees, transaction_fees) @@ -117,18 +99,24 @@ defmodule BlockScoutWeb.API.V2.BlockView do def count_withdrawals(%Block{withdrawals: withdrawals}) when is_list(withdrawals), do: Enum.count(withdrawals) def count_withdrawals(_), do: nil - defp chain_type_fields(result, block, single_block?) do - case single_block? && Application.get_env(:explorer, :chain_type) do - "rsk" -> - result - |> Map.put("minimum_gas_price", block.minimum_gas_price) - |> Map.put("bitcoin_merged_mining_header", block.bitcoin_merged_mining_header) - |> Map.put("bitcoin_merged_mining_coinbase_transaction", block.bitcoin_merged_mining_coinbase_transaction) - |> Map.put("bitcoin_merged_mining_merkle_proof", block.bitcoin_merged_mining_merkle_proof) - |> Map.put("hash_for_merged_mining", block.hash_for_merged_mining) - - _ -> + case Application.compile_env(:explorer, :chain_type) do + "rsk" -> + defp chain_type_fields(result, block, single_block?) do + if single_block? do + result + |> Map.put("minimum_gas_price", block.minimum_gas_price) + |> Map.put("bitcoin_merged_mining_header", block.bitcoin_merged_mining_header) + |> Map.put("bitcoin_merged_mining_coinbase_transaction", block.bitcoin_merged_mining_coinbase_transaction) + |> Map.put("bitcoin_merged_mining_merkle_proof", block.bitcoin_merged_mining_merkle_proof) + |> Map.put("hash_for_merged_mining", block.hash_for_merged_mining) + else + result + end + end + + _ -> + defp chain_type_fields(result, _block, _single_block?) do result - end + end end end diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index db08bd8a7b85..c170ac04fa67 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -299,4 +299,83 @@ defmodule Explorer.Chain.Block do end def uncle_reward_coef, do: @uncle_reward_coef + + @doc """ + Calculates the gas target for a given block. + + The gas target represents the percentage by which the actual gas used is above or below the gas target for the block, adjusted by the elasticity multiplier. + If the `gas_limit` is greater than 0, it calculates the ratio of `gas_used` to `gas_limit` adjusted by this multiplier. + """ + @spec gas_target(t()) :: float() + def gas_target(block) do + if Decimal.compare(block.gas_limit, 0) == :gt do + elasticity_multiplier = Application.get_env(:explorer, :elasticity_multiplier) + ratio = Decimal.div(block.gas_used, Decimal.div(block.gas_limit, elasticity_multiplier)) + ratio |> Decimal.sub(1) |> Decimal.mult(100) |> Decimal.to_float() + else + 0.0 + end + end + + @doc """ + Calculates the percentage of gas used for a given block relative to its gas limit. + + This function determines what percentage of the block's gas limit was actually used by the transactions in the block. + """ + @spec gas_used_percentage(t()) :: float() + def gas_used_percentage(block) do + if Decimal.compare(block.gas_limit, 0) == :gt do + block.gas_used |> Decimal.div(block.gas_limit) |> Decimal.mult(100) |> Decimal.to_float() + else + 0.0 + end + end + + @doc """ + Calculates the base fee for the next block based on the current block's gas usage. + + The base fee calculation uses the following [formula](https://eips.ethereum.org/EIPS/eip-1559): + + gas_target = gas_limit / elasticity_multiplier + base_fee_for_next_block = base_fee_per_gas + (base_fee_per_gas * gas_used_delta / gas_target / base_fee_max_change_denominator) + + where elasticity_multiplier is an env variable `EIP_1559_ELASTICITY_MULTIPLIER`, + `gas_used_delta` is the difference between the actual gas used and the target gas + and `base_fee_max_change_denominator` is an env variable `EIP_1559_BASE_FEE_MAX_CHANGE_DENOMINATOR` that limits the maximum change of the base fee from one block to the next. + + + """ + @spec next_block_base_fee :: Decimal.t() | nil + def next_block_base_fee do + query = + from(block in Block, + where: block.consensus == true, + order_by: [desc: block.number], + limit: 1 + ) + + case Repo.one(query) do + nil -> nil + block -> next_block_base_fee(block) + end + end + + @spec next_block_base_fee(t()) :: Decimal.t() | nil + def next_block_base_fee(block) do + elasticity_multiplier = Application.get_env(:explorer, :elasticity_multiplier) + base_fee_max_change_denominator = Application.get_env(:explorer, :base_fee_max_change_denominator) + + gas_target = Decimal.div(block.gas_limit, elasticity_multiplier) + + gas_used_delta = Decimal.sub(block.gas_used, gas_target) + + base_fee_per_gas_decimal = block.base_fee_per_gas |> Wei.to(:wei) + + base_fee_per_gas_decimal && + base_fee_per_gas_decimal + |> Decimal.mult(gas_used_delta) + |> Decimal.div(gas_target) + |> Decimal.div(base_fee_max_change_denominator) + |> Decimal.add(base_fee_per_gas_decimal) + end end diff --git a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex index e2066201a0c4..28cb60e11982 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex @@ -10,8 +10,6 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do from: 2 ] - alias EthereumJSONRPC.Blocks - alias Explorer.Chain.{ Block, DenormalizationHelper, @@ -72,7 +70,15 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do fast_time: nil | Decimal.t() } ]} - when gas_price: nil | %{price: float(), time: float(), fiat_price: Decimal.t()} + when gas_price: + nil + | %{ + base_fee: Decimal.t() | nil, + priority_fee: Decimal.t() | nil, + price: float(), + time: float(), + fiat_price: Decimal.t() + } def get_average_gas_price(num_of_blocks, safelow_percentile, average_percentile, fast_percentile) do safelow_percentile_fraction = safelow_percentile / 100 average_percentile_fraction = average_percentile / 100 @@ -272,30 +278,30 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do fast_time: fast_time } = merge_fees(fees) - json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) - - {slow_fee, average_fee, fast_fee} = + {slow_fee, average_fee, fast_fee, base_fee_wei} = case nil not in [slow_priority_fee_per_gas, average_priority_fee_per_gas, fast_priority_fee_per_gas] && - EthereumJSONRPC.fetch_block_by_tag("pending", json_rpc_named_arguments) do - {:ok, %Blocks{blocks_params: [%{base_fee_per_gas: base_fee}]}} when not is_nil(base_fee) -> - base_fee_wei = base_fee |> Decimal.new() |> Wei.from(:wei) + Block.next_block_base_fee() do + %Decimal{} = base_fee -> + base_fee_wei = base_fee |> Wei.from(:wei) { priority_with_base_fee(slow_priority_fee_per_gas, base_fee_wei), priority_with_base_fee(average_priority_fee_per_gas, base_fee_wei), - priority_with_base_fee(fast_priority_fee_per_gas, base_fee_wei) + priority_with_base_fee(fast_priority_fee_per_gas, base_fee_wei), + base_fee_wei } _ -> - {gas_price(slow_gas_price), gas_price(average_gas_price), gas_price(fast_gas_price)} + {gas_price(slow_gas_price), gas_price(average_gas_price), gas_price(fast_gas_price), nil} end exchange_rate_from_db = Market.get_coin_exchange_rate() %{ - slow: compose_gas_price(slow_fee, slow_time, exchange_rate_from_db), - average: compose_gas_price(average_fee, average_time, exchange_rate_from_db), - fast: compose_gas_price(fast_fee, fast_time, exchange_rate_from_db) + slow: compose_gas_price(slow_fee, slow_time, exchange_rate_from_db, base_fee_wei, slow_priority_fee_per_gas), + average: + compose_gas_price(average_fee, average_time, exchange_rate_from_db, base_fee_wei, average_priority_fee_per_gas), + fast: compose_gas_price(fast_fee, fast_time, exchange_rate_from_db, base_fee_wei, fast_priority_fee_per_gas) } end @@ -321,11 +327,13 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do end) end - defp compose_gas_price(fee, time, exchange_rate_from_db) do + defp compose_gas_price(fee, time, exchange_rate_from_db, base_fee, priority_fee) do %{ price: fee |> format_wei(), time: time && time |> Decimal.to_float(), - fiat_price: fiat_fee(fee, exchange_rate_from_db) + fiat_price: fiat_fee(fee, exchange_rate_from_db), + base_fee: base_fee |> format_wei(), + priority_fee: base_fee && priority_fee && priority_fee |> Decimal.new() |> Wei.from(:wei) |> format_wei() } end @@ -346,6 +354,8 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do value |> Wei.from(:wei) end + defp format_wei(nil), do: nil + defp format_wei(wei), do: wei |> Wei.to(:gwei) |> Decimal.to_float() |> Float.ceil(2) defp global_ttl, do: Application.get_env(:explorer, __MODULE__)[:global_ttl] diff --git a/apps/explorer/test/explorer/chain/block_test.exs b/apps/explorer/test/explorer/chain/block_test.exs index 703cd8609a7b..b7034316c040 100644 --- a/apps/explorer/test/explorer/chain/block_test.exs +++ b/apps/explorer/test/explorer/chain/block_test.exs @@ -100,4 +100,44 @@ defmodule Explorer.Chain.BlockTest do assert %{uncle_reward: ^expected_uncle_reward} = Block.block_reward_by_parts(block, []) end end + + describe "next_block_base_fee" do + test "with no blocks in the database returns nil" do + assert Block.next_block_base_fee() == nil + end + + test "ignores non consensus blocks" do + insert(:block, consensus: false, base_fee_per_gas: Wei.from(Decimal.new(1), :wei)) + assert Block.next_block_base_fee() == nil + end + + test "returns the next block base fee" do + insert(:block, + consensus: true, + base_fee_per_gas: Wei.from(Decimal.new(1000), :wei), + gas_limit: Decimal.new(30_000_000), + gas_used: Decimal.new(15_000_000) + ) + + assert Block.next_block_base_fee() == Decimal.new(1000) + + insert(:block, + consensus: true, + base_fee_per_gas: Wei.from(Decimal.new(1000), :wei), + gas_limit: Decimal.new(30_000_000), + gas_used: Decimal.new(3_000_000) + ) + + assert Block.next_block_base_fee() == Decimal.new(900) + + insert(:block, + consensus: true, + base_fee_per_gas: Wei.from(Decimal.new(1000), :wei), + gas_limit: Decimal.new(30_000_000), + gas_used: Decimal.new(27_000_000) + ) + + assert Block.next_block_base_fee() == Decimal.new(1100) + end + end end diff --git a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs index a30d4070f66f..1fdccd0a1bc6 100644 --- a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs +++ b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs @@ -1,54 +1,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do use Explorer.DataCase - import Mox - alias Explorer.Chain.Cache.GasPriceOracle + alias Explorer.Chain.Wei alias Explorer.Counters.AverageBlockTime - @block %{ - "difficulty" => "0x0", - "gasLimit" => "0x0", - "gasUsed" => "0x0", - "hash" => "0x29c850324e357f3c0c836d79860c5af55f7b651e5d7ee253c1af1b14908af49c", - "extraData" => "0x0", - "logsBloom" => "0x0", - "miner" => "0x0", - "number" => "0x1", - "parentHash" => "0x0", - "receiptsRoot" => "0x0", - "size" => "0x0", - "sha3Uncles" => "0x0", - "stateRoot" => "0x0", - "timestamp" => "0x0", - "baseFeePerGas" => "0x1DCD6500", - "totalDifficulty" => "0x0", - "transactions" => [ - %{ - "blockHash" => "0x29c850324e357f3c0c836d79860c5af55f7b651e5d7ee253c1af1b14908af49c", - "blockNumber" => "0x1", - "from" => "0x0", - "gas" => "0x0", - "gasPrice" => "0x0", - "hash" => "0xa2e81bb56b55ba3dab2daf76501b50dfaad240cccb905dbf89d65c7a84a4a48e", - "input" => "0x", - "nonce" => "0x0", - "r" => "0x0", - "s" => "0x0", - "to" => "0x0", - "transactionIndex" => "0x0", - "v" => "0x0", - "value" => "0x0" - } - ], - "transactionsRoot" => "0x0", - "uncles" => [] - } - describe "get_average_gas_price/4" do test "returns nil percentile values if no blocks in the DB" do - expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) - assert {{:ok, %{ slow: nil, @@ -58,8 +16,6 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do end test "returns nil percentile values if blocks are empty in the DB" do - expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) - insert(:block) insert(:block) insert(:block) @@ -73,8 +29,6 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do end test "returns nil percentile values for blocks with failed txs in the DB" do - expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) - block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") :transaction @@ -99,8 +53,6 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do end test "returns nil percentile values for transactions with 0 gas price aka 'whitelisted transactions' in the DB" do - expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) - block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729") @@ -137,8 +89,6 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do end test "returns the same percentile values if gas price is the same over transactions" do - expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) - block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729") @@ -175,8 +125,6 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do end test "returns correct min gas price from the block" do - expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) - block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729") @@ -225,8 +173,6 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do end test "returns correct average percentile" do - expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) - block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729") block3 = insert(:block, number: 102, hash: "0x659b2a1cc4dd1a5729900cf0c81c471d1c7891b2517bf9466f7fba56ead2fca0") @@ -274,10 +220,16 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do end test "returns correct gas price for EIP-1559 transactions" do - expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) - block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") - block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729") + + block2 = + insert(:block, + number: 101, + hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729", + gas_limit: Decimal.new(10_000_000), + gas_used: Decimal.new(5_000_000), + base_fee_per_gas: Wei.from(Decimal.new(500_000_000), :wei) + ) :transaction |> insert( @@ -330,8 +282,6 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do end test "return gas prices with time if available" do - expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) - block1 = insert(:block, number: 100, @@ -343,7 +293,10 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729", - timestamp: ~U[2023-12-12 12:13:00.000000Z] + timestamp: ~U[2023-12-12 12:13:00.000000Z], + gas_limit: Decimal.new(10_000_000), + gas_used: Decimal.new(5_000_000), + base_fee_per_gas: Wei.from(Decimal.new(500_000_000), :wei) ) :transaction @@ -405,7 +358,6 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do end test "return gas prices with average block time if earliest_processing_start is not available" do - expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) old_env = Application.get_env(:explorer, AverageBlockTime) Application.put_env(:explorer, AverageBlockTime, enabled: true, cache_period: 1_800_000) start_supervised!(AverageBlockTime) @@ -432,7 +384,10 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do insert(:block, number: block_number + 103, consensus: true, - timestamp: Timex.shift(first_timestamp, seconds: -7) + timestamp: Timex.shift(first_timestamp, seconds: -7), + gas_limit: Decimal.new(10_000_000), + gas_used: Decimal.new(5_000_000), + base_fee_per_gas: Wei.from(Decimal.new(500_000_000), :wei) ) AverageBlockTime.refresh() diff --git a/config/runtime.exs b/config/runtime.exs index 9a3f329acb92..d684fda96e31 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -206,7 +206,8 @@ config :explorer, restricted_list: System.get_env("RESTRICTED_LIST"), restricted_list_key: System.get_env("RESTRICTED_LIST_KEY"), checksum_function: checksum_function && String.to_atom(checksum_function), - elasticity_multiplier: ConfigHelper.parse_integer_env_var("EIP_1559_ELASTICITY_MULTIPLIER", 2) + elasticity_multiplier: ConfigHelper.parse_integer_env_var("EIP_1559_ELASTICITY_MULTIPLIER", 2), + base_fee_max_change_denominator: ConfigHelper.parse_integer_env_var("EIP_1559_BASE_FEE_MAX_CHANGE_DENOMINATOR", 8) config :explorer, :proxy, caching_implementation_data_enabled: true, From 9bfcc27fb18859f0534a10933971637b502cccf2 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Fri, 9 Feb 2024 16:41:09 +0300 Subject: [PATCH 398/607] Process nikitosing4 review --- apps/explorer/lib/explorer/chain/block.ex | 10 +++++----- .../lib/explorer/chain/cache/gas_price_oracle.ex | 2 +- apps/explorer/test/explorer/chain/block_test.exs | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index c170ac04fa67..bfd97598795a 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -345,8 +345,8 @@ defmodule Explorer.Chain.Block do """ - @spec next_block_base_fee :: Decimal.t() | nil - def next_block_base_fee do + @spec next_block_base_fee_per_gas :: Decimal.t() | nil + def next_block_base_fee_per_gas do query = from(block in Block, where: block.consensus == true, @@ -356,12 +356,12 @@ defmodule Explorer.Chain.Block do case Repo.one(query) do nil -> nil - block -> next_block_base_fee(block) + block -> next_block_base_fee_per_gas(block) end end - @spec next_block_base_fee(t()) :: Decimal.t() | nil - def next_block_base_fee(block) do + @spec next_block_base_fee_per_gas(t()) :: Decimal.t() | nil + def next_block_base_fee_per_gas(block) do elasticity_multiplier = Application.get_env(:explorer, :elasticity_multiplier) base_fee_max_change_denominator = Application.get_env(:explorer, :base_fee_max_change_denominator) diff --git a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex index 28cb60e11982..ea335fecf786 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex @@ -280,7 +280,7 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do {slow_fee, average_fee, fast_fee, base_fee_wei} = case nil not in [slow_priority_fee_per_gas, average_priority_fee_per_gas, fast_priority_fee_per_gas] && - Block.next_block_base_fee() do + Block.next_block_base_fee_per_gas() do %Decimal{} = base_fee -> base_fee_wei = base_fee |> Wei.from(:wei) diff --git a/apps/explorer/test/explorer/chain/block_test.exs b/apps/explorer/test/explorer/chain/block_test.exs index b7034316c040..e6cb0cce65a8 100644 --- a/apps/explorer/test/explorer/chain/block_test.exs +++ b/apps/explorer/test/explorer/chain/block_test.exs @@ -101,14 +101,14 @@ defmodule Explorer.Chain.BlockTest do end end - describe "next_block_base_fee" do + describe "next_block_base_fee_per_gas" do test "with no blocks in the database returns nil" do - assert Block.next_block_base_fee() == nil + assert Block.next_block_base_fee_per_gas() == nil end test "ignores non consensus blocks" do insert(:block, consensus: false, base_fee_per_gas: Wei.from(Decimal.new(1), :wei)) - assert Block.next_block_base_fee() == nil + assert Block.next_block_base_fee_per_gas() == nil end test "returns the next block base fee" do @@ -119,7 +119,7 @@ defmodule Explorer.Chain.BlockTest do gas_used: Decimal.new(15_000_000) ) - assert Block.next_block_base_fee() == Decimal.new(1000) + assert Block.next_block_base_fee_per_gas() == Decimal.new(1000) insert(:block, consensus: true, @@ -128,7 +128,7 @@ defmodule Explorer.Chain.BlockTest do gas_used: Decimal.new(3_000_000) ) - assert Block.next_block_base_fee() == Decimal.new(900) + assert Block.next_block_base_fee_per_gas() == Decimal.new(900) insert(:block, consensus: true, @@ -137,7 +137,7 @@ defmodule Explorer.Chain.BlockTest do gas_used: Decimal.new(27_000_000) ) - assert Block.next_block_base_fee() == Decimal.new(1100) + assert Block.next_block_base_fee_per_gas() == Decimal.new(1100) end end end From 66b88d49814e1385731b152cda0ecb3e79dc2109 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Sat, 3 Feb 2024 11:00:15 +0300 Subject: [PATCH 399/607] Fix manual uncle reward calculation --- CHANGELOG.md | 1 + .../views/api/rpc/block_view.ex | 3 +-- apps/explorer/lib/explorer/chain/block.ex | 10 ++++---- apps/explorer/lib/explorer/chain/wei.ex | 24 +++++++++++++++++++ .../test/explorer/chain/block_test.exs | 2 +- 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5cd1366efec..b2d8e778b738 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Fixes - [#9317](https://github.com/blockscout/blockscout/pull/9317) - Include null gas price txs in fee calculations +- [#9315](https://github.com/blockscout/blockscout/pull/9315) - Fix manual uncle reward calculation ### Chore diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex index 98f976d75b3d..b0262aaf937c 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex @@ -41,8 +41,7 @@ defmodule BlockScoutWeb.API.RPC.BlockView do "uncleInclusionReward" => static_reward |> Decimal.mult(Enum.count(uncles)) - |> Decimal.mult(Decimal.from_float(Block.uncle_reward_coef())) - |> Decimal.normalize() + |> Decimal.div(Block.uncle_reward_coef()) |> Decimal.to_string(:normal) } diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index db08bd8a7b85..ac33a0109159 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -257,7 +257,7 @@ defmodule Explorer.Chain.Block do defp base_fee_per_gas_to_wei(%Wei{} = wei), do: wei defp base_fee_per_gas_to_wei(base_fee_per_gas), do: %Wei{value: Decimal.new(base_fee_per_gas)} - @uncle_reward_coef 1 / 32 + @uncle_reward_coef 32 @spec block_reward_by_parts(Block.t(), [Transaction.t()]) :: %{ block_number: block_number(), block_hash: Hash.Full.t(), @@ -265,7 +265,7 @@ defmodule Explorer.Chain.Block do static_reward: any(), transaction_fees: any(), burnt_fees: Wei.t() | nil, - uncle_reward: Wei.t() | nil | false + uncle_reward: Wei.t() } def block_reward_by_parts(block, transactions) do %{hash: block_hash, number: block_number} = block @@ -282,10 +282,10 @@ defmodule Explorer.Chain.Block do ) ) || %Wei{value: Decimal.new(0)} - has_uncles? = is_list(block.uncles) and not Enum.empty?(block.uncles) + uncles_count = if is_list(block.uncles), do: Enum.count(block.uncles), else: 0 burnt_fees = burnt_fees(transactions, base_fee_per_gas) - uncle_reward = (has_uncles? && Wei.mult(static_reward, Decimal.from_float(@uncle_reward_coef))) || nil + uncle_reward = static_reward |> Wei.div(@uncle_reward_coef) |> Wei.mult(uncles_count) %{ block_number: block_number, @@ -294,7 +294,7 @@ defmodule Explorer.Chain.Block do static_reward: static_reward, transaction_fees: %Wei{value: transaction_fees}, burnt_fees: burnt_fees || %Wei{value: Decimal.new(0)}, - uncle_reward: uncle_reward || %Wei{value: Decimal.new(0)} + uncle_reward: uncle_reward } end diff --git a/apps/explorer/lib/explorer/chain/wei.ex b/apps/explorer/lib/explorer/chain/wei.ex index c542c9081a13..0a3f23fb4ffe 100644 --- a/apps/explorer/lib/explorer/chain/wei.ex +++ b/apps/explorer/lib/explorer/chain/wei.ex @@ -20,6 +20,7 @@ defmodule Explorer.Chain.Wei do """ + require Decimal alias Explorer.Chain.Wei defstruct ~w(value)a @@ -196,6 +197,29 @@ defmodule Explorer.Chain.Wei do |> from(:wei) end + @doc """ + Divides Wei values by an `t:integer/0` or `t:Decimal.t/0`. + + ## Example + + iex> wei = %Explorer.Chain.Wei{value: Decimal.new(10)} + iex> divisor = 5 + iex> Explorer.Chain.Wei.div(wei, divisor) + %Explorer.Chain.Wei{value: Decimal.new(2)} + """ + @spec div(t(), pos_integer() | Decimal.t()) :: t() + def div(%Wei{value: value}, divisor) when is_integer(divisor) and divisor > 0 do + value + |> Decimal.div(divisor) + |> from(:wei) + end + + def div(%Wei{value: value}, %Decimal{sign: 1} = divisor) do + value + |> Decimal.div(divisor) + |> from(:wei) + end + @doc """ Converts `Decimal` representations of various wei denominations (wei, Gwei, ether) to a wei base unit. diff --git a/apps/explorer/test/explorer/chain/block_test.exs b/apps/explorer/test/explorer/chain/block_test.exs index 703cd8609a7b..5a82def07897 100644 --- a/apps/explorer/test/explorer/chain/block_test.exs +++ b/apps/explorer/test/explorer/chain/block_test.exs @@ -95,7 +95,7 @@ defmodule Explorer.Chain.BlockTest do block = build(:block, number: range.from, uncles: ["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273311"]) - expected_uncle_reward = Wei.mult(reward, Decimal.from_float(1 / 32)) + expected_uncle_reward = Wei.div(reward, 32) assert %{uncle_reward: ^expected_uncle_reward} = Block.block_reward_by_parts(block, []) end From 327c4915281fb6709ad8b6554370d64c0dc32931 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Fri, 9 Feb 2024 19:50:48 +0400 Subject: [PATCH 400/607] fix: linter --- .../lib/block_scout_web/views/api/v2/transaction_view.ex | 2 +- apps/explorer/lib/explorer/chain/block.ex | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index aebfad510f8e..a7538a3f3675 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -521,7 +521,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do |> Map.put( "execution_node", Helper.address_with_info( - single_tx? && conn, + conn, transaction.execution_node, transaction.execution_node_hash, single_tx?, diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index cb34676e0ee0..3b20b957e496 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -154,7 +154,6 @@ defmodule Explorer.Chain.Block do _ -> "" end} """ - Explorer.Chain.Block.Schema.generate() def changeset(%__MODULE__{} = block, attrs) do From e58f5994766a942ee4a79b581ea6e48204e95e0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 18:27:16 +0000 Subject: [PATCH 401/607] Bump credo from 1.7.3 to 1.7.4 Bumps [credo](https://github.com/rrrene/credo) from 1.7.3 to 1.7.4. - [Release notes](https://github.com/rrrene/credo/releases) - [Changelog](https://github.com/rrrene/credo/blob/master/CHANGELOG.md) - [Commits](https://github.com/rrrene/credo/compare/v1.7.3...v1.7.4) --- updated-dependencies: - dependency-name: credo dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 2db02bb5cf22..2ca4e78b1313 100644 --- a/mix.lock +++ b/mix.lock @@ -27,7 +27,7 @@ "cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"}, "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, - "credo": {:hex, :credo, "1.7.3", "05bb11eaf2f2b8db370ecaa6a6bda2ec49b2acd5e0418bc106b73b07128c0436", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "35ea675a094c934c22fb1dca3696f3c31f2728ae6ef5a53b5d648c11180a4535"}, + "credo": {:hex, :credo, "1.7.4", "68ca5cf89071511c12fd9919eb84e388d231121988f6932756596195ccf7fd35", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9cf776d062c78bbe0f0de1ecaee183f18f2c3ec591326107989b054b7dddefc2"}, "csv": {:hex, :csv, "2.5.0", "c47b5a5221bf2e56d6e8eb79e77884046d7fd516280dc7d9b674251e0ae46246", [:mix], [{:parallel_stream, "~> 1.0.4 or ~> 1.1.0", [hex: :parallel_stream, repo: "hexpm", optional: false]}], "hexpm", "e821f541487045c7591a1963eeb42afff0dfa99bdcdbeb3410795a2f59c77d34"}, "dataloader": {:hex, :dataloader, "1.0.11", "49bbfc7dd8a1990423c51000b869b1fecaab9e3ccd6b29eab51616ae8ad0a2f5", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba0b0ec532ec68e9d033d03553561d693129bd7cbd5c649dc7903f07ffba08fe"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, From c4940ad58fc229536760ae1d635bd32ff37a8a43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 18:30:30 +0000 Subject: [PATCH 402/607] Bump postcss from 8.4.33 to 8.4.35 in /apps/block_scout_web/assets Bumps [postcss](https://github.com/postcss/postcss) from 8.4.33 to 8.4.35. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.33...8.4.35) --- updated-dependencies: - dependency-name: postcss dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 7344bedebb7f..9e5d19038f89 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -87,7 +87,7 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "mini-css-extract-plugin": "^2.8.0", - "postcss": "^8.4.33", + "postcss": "^8.4.35", "postcss-loader": "^8.1.0", "sass": "^1.70.0", "sass-loader": "^14.1.0", @@ -13765,9 +13765,9 @@ } }, "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", "dev": true, "funding": [ { @@ -28268,9 +28268,9 @@ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" }, "postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", "dev": true, "requires": { "nanoid": "^3.3.7", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 94d5a38a4146..a2fd7ac23222 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -99,7 +99,7 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "mini-css-extract-plugin": "^2.8.0", - "postcss": "^8.4.33", + "postcss": "^8.4.35", "postcss-loader": "^8.1.0", "sass": "^1.70.0", "sass-loader": "^14.1.0", From 3d445a202d69136e08781c04891ae6b0f564d0bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 18:30:58 +0000 Subject: [PATCH 403/607] Bump mixpanel-browser in /apps/block_scout_web/assets Bumps [mixpanel-browser](https://github.com/mixpanel/mixpanel-js) from 2.48.1 to 2.49.0. - [Release notes](https://github.com/mixpanel/mixpanel-js/releases) - [Changelog](https://github.com/mixpanel/mixpanel-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/mixpanel/mixpanel-js/compare/v2.48.1...v2.49.0) --- updated-dependencies: - dependency-name: mixpanel-browser dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 7344bedebb7f..2d2d559f8bb3 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -46,7 +46,7 @@ "lodash.reduce": "^4.6.0", "luxon": "^3.4.4", "malihu-custom-scrollbar-plugin": "3.1.5", - "mixpanel-browser": "^2.48.1", + "mixpanel-browser": "^2.49.0", "moment": "^2.30.1", "nanomorph": "^5.4.0", "numeral": "^2.0.6", @@ -12925,9 +12925,9 @@ } }, "node_modules/mixpanel-browser": { - "version": "2.48.1", - "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.48.1.tgz", - "integrity": "sha512-vXTuUzZMg+ht7sRqyjtc3dUDy/81Z/H6FLFgFkUZJqKFaAqcx1JSXmOdY/2kmsxCkUdy5JN5zW9m9TMCk+rxGQ==" + "version": "2.49.0", + "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.49.0.tgz", + "integrity": "sha512-RZJCO7XXuuHBAWG5fd9Mavz994M7v7W3Qiaq8NzmN631pa4BQ0vNZQtRFqKcCCOBn4xqOZbX2GkuC7ZkQoL4cQ==" }, "node_modules/mkdirp": { "version": "3.0.1", @@ -27656,9 +27656,9 @@ } }, "mixpanel-browser": { - "version": "2.48.1", - "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.48.1.tgz", - "integrity": "sha512-vXTuUzZMg+ht7sRqyjtc3dUDy/81Z/H6FLFgFkUZJqKFaAqcx1JSXmOdY/2kmsxCkUdy5JN5zW9m9TMCk+rxGQ==" + "version": "2.49.0", + "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.49.0.tgz", + "integrity": "sha512-RZJCO7XXuuHBAWG5fd9Mavz994M7v7W3Qiaq8NzmN631pa4BQ0vNZQtRFqKcCCOBn4xqOZbX2GkuC7ZkQoL4cQ==" }, "mkdirp": { "version": "3.0.1", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 94d5a38a4146..7c42c3fe1f3c 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -58,7 +58,7 @@ "lodash.reduce": "^4.6.0", "luxon": "^3.4.4", "malihu-custom-scrollbar-plugin": "3.1.5", - "mixpanel-browser": "^2.48.1", + "mixpanel-browser": "^2.49.0", "moment": "^2.30.1", "nanomorph": "^5.4.0", "numeral": "^2.0.6", From 72d4a8d5d740c579eb026425dfa7d8b513b75139 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 6 Feb 2024 20:01:08 +0300 Subject: [PATCH 404/607] Process integer balance in genesis.json --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/chain_spec/geth/importer.ex | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94c1bd80dc27..037e1f930d66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixes +- [#9346](https://github.com/blockscout/blockscout/pull/9346) - Process integer balance in genesis.json - [#9317](https://github.com/blockscout/blockscout/pull/9317) - Include null gas price txs in fee calculations - [#9315](https://github.com/blockscout/blockscout/pull/9315) - Fix manual uncle reward calculation diff --git a/apps/explorer/lib/explorer/chain_spec/geth/importer.ex b/apps/explorer/lib/explorer/chain_spec/geth/importer.ex index 9c8083f6fc77..4342a1d3bb4b 100644 --- a/apps/explorer/lib/explorer/chain_spec/geth/importer.ex +++ b/apps/explorer/lib/explorer/chain_spec/geth/importer.ex @@ -83,6 +83,10 @@ defmodule Explorer.ChainSpec.Geth.Importer do |> Enum.to_list() end + defp parse_number(number) when is_integer(number) do + number + end + defp parse_number("0x" <> hex_number) do {number, ""} = Integer.parse(hex_number, 16) From 10520bc0263624668ee13302ad4227a8759536cc Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Tue, 13 Feb 2024 12:33:31 +0300 Subject: [PATCH 405/607] Fix read contract bug (#9300) * Fix read contract bug * Changelog --- CHANGELOG.md | 1 + .../api/v2/smart_contract_controller_test.exs | 82 +++++++++++++++++++ .../lib/ethereum_jsonrpc/encoder.ex | 4 + 3 files changed, 87 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94c1bd80dc27..e3abebd6093c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - [#9317](https://github.com/blockscout/blockscout/pull/9317) - Include null gas price txs in fee calculations - [#9315](https://github.com/blockscout/blockscout/pull/9315) - Fix manual uncle reward calculation +- [#9300](https://github.com/blockscout/blockscout/pull/9300) - Fix read contract bug ### Chore diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index 7a7d9918bad7..3740583462cc 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -2084,6 +2084,88 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "result" => %{"names" => ["bool"], "output" => [%{"type" => "bool", "value" => true}]} } == response end + + test "query read method 1", %{conn: conn} do + abi = [ + %{ + "inputs" => [ + %{ + "internalType" => "uint256", + "name" => "amountIn", + "type" => "uint256" + }, + %{ + "internalType" => "address[]", + "name" => "path", + "type" => "address[]" + } + ], + "name" => "getAmountsOut", + "outputs" => [ + %{ + "internalType" => "uint256[]", + "name" => "amounts", + "type" => "uint256[]" + } + ], + "stateMutability" => "view", + "type" => "function" + } + ] + + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [ + %{ + id: id, + method: "eth_call", + params: [ + %{ + data: + "0xd06ca61f00000000000000000000000000000000000000000000003635c9adc5dea0000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000909fd75ce23a7e61787fe2763652935f921164610000000000000000000000009801eeb848987c0a8d6443912827bd36c288f8fb" + }, + _ + ] + } + ], + _opts -> + {:ok, + [ + %{ + id: id, + jsonrpc: "2.0", + result: + "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000003635c9adc5dea000000000000000000000000000000000000000000000000000000037240fc3496a65" + } + ]} + end + ) + + target_contract = insert(:smart_contract, abi: abi) + + request = + post(conn, "/api/v2/smart-contracts/#{target_contract.address_hash}/query-read-method", %{ + "contract_type" => "regular", + "args" => [ + "1000000000000000000000", + ["0x909Fd75Ce23a7e61787FE2763652935F92116461", "0x9801eeb848987c0a8d6443912827bd36c288f8fb"] + ], + "method_id" => "d06ca61f" + }) + + assert response = json_response(request, 200) + + assert %{ + "is_error" => false, + "result" => %{ + "names" => ["amounts"], + "output" => [ + %{"type" => "uint256[]", "value" => [1_000_000_000_000_000_000_000, 15_520_773_838_563_941]} + ] + } + } == response + end end describe "/smart-contracts/{address_hash}/methods-read-proxy" do diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex index e667ce8eaa89..1b6feee0adbc 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex @@ -25,6 +25,10 @@ defmodule EthereumJSONRPC.Encoder do def encode_function_call(function_selector, args), do: encode_function_call(function_selector, [args]) + defp parse_args(args, {:array, type}) when is_list(args) do + Enum.map(args, fn arg -> parse_args(arg, type) end) + end + defp parse_args(args, types) when is_list(args) do args |> Enum.zip(types) From 00ce0f04800ab207dc699a39ad1ee14e24ffad82 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Tue, 13 Feb 2024 14:13:48 +0300 Subject: [PATCH 406/607] Parse contract codes and balances from custom genesis.json (#9349) * Parse contract codes and balances from custom genesis.json * Process reviewer comment * Process reviewer comment --- .../lib/explorer/chain_spec/genesis_data.ex | 4 + .../lib/explorer/chain_spec/geth/importer.ex | 42 ++++---- .../explorer/chain_spec/parity/importer.ex | 20 +--- apps/explorer/lib/explorer/helper.ex | 24 +++++ .../chain_spec/geth/importer_test.exs | 21 ++++ .../fixture/chain_spec/zkatana_genesis.json | 102 ++++++++++++++++++ cspell.json | 1 + 7 files changed, 176 insertions(+), 38 deletions(-) create mode 100644 apps/explorer/test/support/fixture/chain_spec/zkatana_genesis.json diff --git a/apps/explorer/lib/explorer/chain_spec/genesis_data.ex b/apps/explorer/lib/explorer/chain_spec/genesis_data.ex index 3367faf06cdd..764ca7c69a4e 100644 --- a/apps/explorer/lib/explorer/chain_spec/genesis_data.ex +++ b/apps/explorer/lib/explorer/chain_spec/genesis_data.ex @@ -56,6 +56,10 @@ defmodule Explorer.ChainSpec.GenesisData do {:noreply, state} end + @doc """ + Fetches pre-mined balances and pre-compiled smart-contract bytecodes from genesis.json + """ + @spec fetch_genesis_data() :: Task.t() | :ok def fetch_genesis_data do path = Application.get_env(:explorer, __MODULE__)[:chain_spec_path] diff --git a/apps/explorer/lib/explorer/chain_spec/geth/importer.ex b/apps/explorer/lib/explorer/chain_spec/geth/importer.ex index 4342a1d3bb4b..40db5445a83b 100644 --- a/apps/explorer/lib/explorer/chain_spec/geth/importer.ex +++ b/apps/explorer/lib/explorer/chain_spec/geth/importer.ex @@ -7,8 +7,8 @@ defmodule Explorer.ChainSpec.Geth.Importer do require Logger alias EthereumJSONRPC.Blocks - alias Explorer.Chain - alias Explorer.Chain.Hash.Address, as: AddressHash + alias Explorer.{Chain, Helper} + alias Explorer.Chain.Hash.Address def import_genesis_accounts(chain_spec) do balance_params = @@ -50,11 +50,25 @@ defmodule Explorer.ChainSpec.Geth.Importer do Chain.import(params) end + @spec genesis_accounts(any()) :: [%{address_hash: Address.t(), value: integer(), contract_code: String.t()}] def genesis_accounts(%{"genesis" => genesis}) do genesis_accounts(genesis) end - def genesis_accounts(chain_spec) do + def genesis_accounts(raw_accounts) when is_list(raw_accounts) do + raw_accounts + |> Enum.map(fn account -> + with {:ok, address_hash} <- Chain.string_to_address_hash(account["address"]), + balance <- Helper.parse_number(account["balance"]) do + %{address_hash: address_hash, value: balance, contract_code: account["bytecode"]} + else + _ -> nil + end + end) + |> Enum.filter(&(!is_nil(&1))) + end + + def genesis_accounts(chain_spec) when is_map(chain_spec) do accounts = chain_spec["alloc"] if accounts do @@ -73,8 +87,8 @@ defmodule Explorer.ChainSpec.Geth.Importer do end) |> Stream.map(fn {address, %{"balance" => value} = params} -> formatted_address = if String.starts_with?(address, "0x"), do: address, else: "0x" <> address - {:ok, address_hash} = AddressHash.cast(formatted_address) - balance = parse_number(value) + {:ok, address_hash} = Address.cast(formatted_address) + balance = Helper.parse_number(value) code = params["code"] @@ -82,22 +96,4 @@ defmodule Explorer.ChainSpec.Geth.Importer do end) |> Enum.to_list() end - - defp parse_number(number) when is_integer(number) do - number - end - - defp parse_number("0x" <> hex_number) do - {number, ""} = Integer.parse(hex_number, 16) - - number - end - - defp parse_number(""), do: 0 - - defp parse_number(string_number) do - {number, ""} = Integer.parse(string_number, 10) - - number - end end diff --git a/apps/explorer/lib/explorer/chain_spec/parity/importer.ex b/apps/explorer/lib/explorer/chain_spec/parity/importer.ex index 9fd32144f4eb..58dde7fcaa7c 100644 --- a/apps/explorer/lib/explorer/chain_spec/parity/importer.ex +++ b/apps/explorer/lib/explorer/chain_spec/parity/importer.ex @@ -126,9 +126,9 @@ defmodule Explorer.ChainSpec.Parity.Importer do |> Stream.map(fn {address, %{"balance" => value} = params} -> formatted_address = if String.starts_with?(address, "0x"), do: address, else: "0x" <> address {:ok, address_hash} = AddressHash.cast(formatted_address) - balance = parse_number(value) + balance = ExplorerHelper.parse_number(value) - nonce = parse_number(params["nonce"] || "0") + nonce = ExplorerHelper.parse_number(params["nonce"] || "0") code = params["constructor"] %{address_hash: address_hash, value: balance, nonce: nonce, contract_code: code} @@ -162,26 +162,16 @@ defmodule Explorer.ChainSpec.Parity.Importer do defp parse_hex_numbers(rewards) when is_map(rewards) do Enum.map(rewards, fn {hex_block_number, hex_reward} -> - block_number = parse_number(hex_block_number) - {:ok, reward} = hex_reward |> parse_number() |> Wei.cast() + block_number = ExplorerHelper.parse_number(hex_block_number) + {:ok, reward} = hex_reward |> ExplorerHelper.parse_number() |> Wei.cast() {block_number, reward} end) end defp parse_hex_numbers(reward) do - {:ok, reward} = reward |> parse_number() |> Wei.cast() + {:ok, reward} = reward |> ExplorerHelper.parse_number() |> Wei.cast() [{0, reward}] end - - defp parse_number("0x" <> hex_number) do - {number, ""} = Integer.parse(hex_number, 16) - - number - end - - defp parse_number(string_number) do - ExplorerHelper.parse_integer(string_number) - end end diff --git a/apps/explorer/lib/explorer/helper.ex b/apps/explorer/lib/explorer/helper.ex index ab7214db8f6f..a3161b4b462a 100644 --- a/apps/explorer/lib/explorer/helper.ex +++ b/apps/explorer/lib/explorer/helper.ex @@ -40,6 +40,30 @@ defmodule Explorer.Helper do end end + @doc """ + Parses number from hex string or decimal number string + """ + @spec parse_number(binary() | nil) :: integer() | nil + def parse_number(nil), do: nil + + def parse_number(number) when is_integer(number) do + number + end + + def parse_number("0x" <> hex_number) do + {number, ""} = Integer.parse(hex_number, 16) + + number + end + + def parse_number(""), do: 0 + + def parse_number(string_number) do + {number, ""} = Integer.parse(string_number, 10) + + number + end + @doc """ Function to preload a `struct` for each element of the `list`. You should specify a primary key for a `struct` in `references_field`, diff --git a/apps/explorer/test/explorer/chain_spec/geth/importer_test.exs b/apps/explorer/test/explorer/chain_spec/geth/importer_test.exs index e8aa80eac1ea..0bb7be80166a 100644 --- a/apps/explorer/test/explorer/chain_spec/geth/importer_test.exs +++ b/apps/explorer/test/explorer/chain_spec/geth/importer_test.exs @@ -25,6 +25,10 @@ defmodule Explorer.ChainSpec.Geth.ImporterTest do |> File.read!() |> Jason.decode!() + @zkatana_genesis "#{File.cwd!()}/test/support/fixture/chain_spec/zkatana_genesis.json" + |> File.read!() + |> Jason.decode!() + describe "genesis_accounts/1" do test "parses coin balance and contract code" do coin_balances = Importer.genesis_accounts(@geth_genesis) @@ -76,6 +80,23 @@ defmodule Explorer.ChainSpec.Geth.ImporterTest do } == List.first(coin_balances) end + + test "parses zkatana coin balance and contract code" do + coin_balances = Importer.genesis_accounts(@zkatana_genesis) + + assert Enum.count(coin_balances) == 9 + + assert %{ + address_hash: %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<208, 60, 26, 156, 47, 226, 198, 243, 146, 124, 57, 138, 144, 39, 47, 233, 91, 211, 205, 175>> + }, + value: 0, + contract_code: + "0x60806040526004361061006e575f3560e01c8063715018a61161004c578063715018a6146100e25780638da5cb5b146100f6578063e11ae6cb1461011f578063f2fde38b14610132575f80fd5b80632b79805a146100725780634a94d487146100875780636d07dbf81461009a575b5f80fd5b610085610080366004610908565b610151565b005b6100856100953660046109a2565b6101c2565b3480156100a5575f80fd5b506100b96100b43660046109f5565b610203565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100ed575f80fd5b50610085610215565b348015610101575f80fd5b505f5473ffffffffffffffffffffffffffffffffffffffff166100b9565b61008561012d366004610a15565b610228565b34801561013d575f80fd5b5061008561014c366004610a61565b61028e565b61015961034a565b5f6101658585856103ca565b90506101718183610527565b5060405173ffffffffffffffffffffffffffffffffffffffff821681527fba82f25fed02cd2a23d9f5d11c2ef588d22af5437cbf23bfe61d87257c480e4c9060200160405180910390a15050505050565b6101ca61034a565b6101d583838361056a565b506040517f25adb19089b6a549831a273acdf7908cff8b7ee5f551f8d1d37996cf01c5df5b905f90a1505050565b5f61020e8383610598565b9392505050565b61021d61034a565b6102265f6105a4565b565b61023061034a565b5f61023c8484846103ca565b60405173ffffffffffffffffffffffffffffffffffffffff821681529091507fba82f25fed02cd2a23d9f5d11c2ef588d22af5437cbf23bfe61d87257c480e4c9060200160405180910390a150505050565b61029661034a565b73ffffffffffffffffffffffffffffffffffffffff811661033e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b610347816105a4565b50565b5f5473ffffffffffffffffffffffffffffffffffffffff163314610226576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610335565b5f83471015610435576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f437265617465323a20696e73756666696369656e742062616c616e63650000006044820152606401610335565b81515f0361049f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f437265617465323a2062797465636f6465206c656e677468206973207a65726f6044820152606401610335565b8282516020840186f5905073ffffffffffffffffffffffffffffffffffffffff811661020e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f437265617465323a204661696c6564206f6e206465706c6f79000000000000006044820152606401610335565b606061020e83835f6040518060400160405280601e81526020017f416464726573733a206c6f772d6c6576656c2063616c6c206661696c65640000815250610618565b6060610590848484604051806060016040528060298152602001610b0860299139610618565b949350505050565b5f61020e83833061072d565b5f805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6060824710156106aa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401610335565b5f808673ffffffffffffffffffffffffffffffffffffffff1685876040516106d29190610a9c565b5f6040518083038185875af1925050503d805f811461070c576040519150601f19603f3d011682016040523d82523d5f602084013e610711565b606091505b509150915061072287838387610756565b979650505050505050565b5f604051836040820152846020820152828152600b8101905060ff815360559020949350505050565b606083156107eb5782515f036107e45773ffffffffffffffffffffffffffffffffffffffff85163b6107e4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610335565b5081610590565b61059083838151156108005781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103359190610ab7565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f82601f830112610870575f80fd5b813567ffffffffffffffff8082111561088b5761088b610834565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019082821181831017156108d1576108d1610834565b816040528381528660208588010111156108e9575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f805f806080858703121561091b575f80fd5b8435935060208501359250604085013567ffffffffffffffff80821115610940575f80fd5b61094c88838901610861565b93506060870135915080821115610961575f80fd5b5061096e87828801610861565b91505092959194509250565b803573ffffffffffffffffffffffffffffffffffffffff8116811461099d575f80fd5b919050565b5f805f606084860312156109b4575f80fd5b6109bd8461097a565b9250602084013567ffffffffffffffff8111156109d8575f80fd5b6109e486828701610861565b925050604084013590509250925092565b5f8060408385031215610a06575f80fd5b50508035926020909101359150565b5f805f60608486031215610a27575f80fd5b8335925060208401359150604084013567ffffffffffffffff811115610a4b575f80fd5b610a5786828701610861565b9150509250925092565b5f60208284031215610a71575f80fd5b61020e8261097a565b5f5b83811015610a94578181015183820152602001610a7c565b50505f910152565b5f8251610aad818460208701610a7a565b9190910192915050565b602081525f8251806020840152610ad5816040850160208701610a7a565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fe416464726573733a206c6f772d6c6576656c2063616c6c20776974682076616c7565206661696c6564a2646970667358221220330b94dc698c4d290bf55c23f13b473cde6a6bae0030cb902de18af54e35839f64736f6c63430008140033" + } == + List.first(coin_balances) + end end describe "import_genesis_accounts/1" do diff --git a/apps/explorer/test/support/fixture/chain_spec/zkatana_genesis.json b/apps/explorer/test/support/fixture/chain_spec/zkatana_genesis.json new file mode 100644 index 000000000000..7a7ca0523658 --- /dev/null +++ b/apps/explorer/test/support/fixture/chain_spec/zkatana_genesis.json @@ -0,0 +1,102 @@ +{ + "root": "0xd8efe6b2ede4af8771fa2ab26186b292fd76c359d9307a90c5972e22d5be6676", + "genesis": [ + { + "contractName": "PolygonZkEVMDeployer", + "balance": "0", + "nonce": "4", + "address": "0xd03c1a9c2fe2C6f3927C398A90272FE95bD3CDaF", + "bytecode": "0x60806040526004361061006e575f3560e01c8063715018a61161004c578063715018a6146100e25780638da5cb5b146100f6578063e11ae6cb1461011f578063f2fde38b14610132575f80fd5b80632b79805a146100725780634a94d487146100875780636d07dbf81461009a575b5f80fd5b610085610080366004610908565b610151565b005b6100856100953660046109a2565b6101c2565b3480156100a5575f80fd5b506100b96100b43660046109f5565b610203565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100ed575f80fd5b50610085610215565b348015610101575f80fd5b505f5473ffffffffffffffffffffffffffffffffffffffff166100b9565b61008561012d366004610a15565b610228565b34801561013d575f80fd5b5061008561014c366004610a61565b61028e565b61015961034a565b5f6101658585856103ca565b90506101718183610527565b5060405173ffffffffffffffffffffffffffffffffffffffff821681527fba82f25fed02cd2a23d9f5d11c2ef588d22af5437cbf23bfe61d87257c480e4c9060200160405180910390a15050505050565b6101ca61034a565b6101d583838361056a565b506040517f25adb19089b6a549831a273acdf7908cff8b7ee5f551f8d1d37996cf01c5df5b905f90a1505050565b5f61020e8383610598565b9392505050565b61021d61034a565b6102265f6105a4565b565b61023061034a565b5f61023c8484846103ca565b60405173ffffffffffffffffffffffffffffffffffffffff821681529091507fba82f25fed02cd2a23d9f5d11c2ef588d22af5437cbf23bfe61d87257c480e4c9060200160405180910390a150505050565b61029661034a565b73ffffffffffffffffffffffffffffffffffffffff811661033e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b610347816105a4565b50565b5f5473ffffffffffffffffffffffffffffffffffffffff163314610226576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610335565b5f83471015610435576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f437265617465323a20696e73756666696369656e742062616c616e63650000006044820152606401610335565b81515f0361049f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f437265617465323a2062797465636f6465206c656e677468206973207a65726f6044820152606401610335565b8282516020840186f5905073ffffffffffffffffffffffffffffffffffffffff811661020e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f437265617465323a204661696c6564206f6e206465706c6f79000000000000006044820152606401610335565b606061020e83835f6040518060400160405280601e81526020017f416464726573733a206c6f772d6c6576656c2063616c6c206661696c65640000815250610618565b6060610590848484604051806060016040528060298152602001610b0860299139610618565b949350505050565b5f61020e83833061072d565b5f805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6060824710156106aa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401610335565b5f808673ffffffffffffffffffffffffffffffffffffffff1685876040516106d29190610a9c565b5f6040518083038185875af1925050503d805f811461070c576040519150601f19603f3d011682016040523d82523d5f602084013e610711565b606091505b509150915061072287838387610756565b979650505050505050565b5f604051836040820152846020820152828152600b8101905060ff815360559020949350505050565b606083156107eb5782515f036107e45773ffffffffffffffffffffffffffffffffffffffff85163b6107e4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610335565b5081610590565b61059083838151156108005781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103359190610ab7565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f82601f830112610870575f80fd5b813567ffffffffffffffff8082111561088b5761088b610834565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019082821181831017156108d1576108d1610834565b816040528381528660208588010111156108e9575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f805f806080858703121561091b575f80fd5b8435935060208501359250604085013567ffffffffffffffff80821115610940575f80fd5b61094c88838901610861565b93506060870135915080821115610961575f80fd5b5061096e87828801610861565b91505092959194509250565b803573ffffffffffffffffffffffffffffffffffffffff8116811461099d575f80fd5b919050565b5f805f606084860312156109b4575f80fd5b6109bd8461097a565b9250602084013567ffffffffffffffff8111156109d8575f80fd5b6109e486828701610861565b925050604084013590509250925092565b5f8060408385031215610a06575f80fd5b50508035926020909101359150565b5f805f60608486031215610a27575f80fd5b8335925060208401359150604084013567ffffffffffffffff811115610a4b575f80fd5b610a5786828701610861565b9150509250925092565b5f60208284031215610a71575f80fd5b61020e8261097a565b5f5b83811015610a94578181015183820152602001610a7c565b50505f910152565b5f8251610aad818460208701610a7a565b9190910192915050565b602081525f8251806020840152610ad5816040850160208701610a7a565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fe416464726573733a206c6f772d6c6576656c2063616c6c20776974682076616c7565206661696c6564a2646970667358221220330b94dc698c4d290bf55c23f13b473cde6a6bae0030cb902de18af54e35839f64736f6c63430008140033", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000056b9eaf5d19639acc16c6373c66e5a1f61cf29b6" + } + }, + { + "contractName": "ProxyAdmin", + "balance": "0", + "nonce": "1", + "address": "0xfE3306Bb4124E90eA08Af2776008592052Ecb9e0", + "bytecode": "0x608060405260043610610079575f3560e01c80639623609d1161004c5780639623609d1461012357806399a88ec414610136578063f2fde38b14610155578063f3b7dead14610174575f80fd5b8063204e1c7a1461007d578063715018a6146100c55780637eff275e146100db5780638da5cb5b146100fa575b5f80fd5b348015610088575f80fd5b5061009c6100973660046105e8565b610193565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100d0575f80fd5b506100d9610244565b005b3480156100e6575f80fd5b506100d96100f536600461060a565b610257565b348015610105575f80fd5b505f5473ffffffffffffffffffffffffffffffffffffffff1661009c565b6100d961013136600461066e565b6102e0565b348015610141575f80fd5b506100d961015036600461060a565b610371565b348015610160575f80fd5b506100d961016f3660046105e8565b6103cd565b34801561017f575f80fd5b5061009c61018e3660046105e8565b610489565b5f805f8373ffffffffffffffffffffffffffffffffffffffff166040516101dd907f5c60da1b00000000000000000000000000000000000000000000000000000000815260040190565b5f60405180830381855afa9150503d805f8114610215576040519150601f19603f3d011682016040523d82523d5f602084013e61021a565b606091505b509150915081610228575f80fd5b8080602001905181019061023c919061075b565b949350505050565b61024c6104d3565b6102555f610553565b565b61025f6104d3565b6040517f8f28397000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8281166004830152831690638f283970906024015b5f604051808303815f87803b1580156102c6575f80fd5b505af11580156102d8573d5f803e3d5ffd5b505050505050565b6102e86104d3565b6040517f4f1ef28600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff841690634f1ef28690349061033e9086908690600401610776565b5f604051808303818588803b158015610355575f80fd5b505af1158015610367573d5f803e3d5ffd5b5050505050505050565b6103796104d3565b6040517f3659cfe600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8281166004830152831690633659cfe6906024016102af565b6103d56104d3565b73ffffffffffffffffffffffffffffffffffffffff811661047d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b61048681610553565b50565b5f805f8373ffffffffffffffffffffffffffffffffffffffff166040516101dd907ff851a44000000000000000000000000000000000000000000000000000000000815260040190565b5f5473ffffffffffffffffffffffffffffffffffffffff163314610255576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610474565b5f805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b73ffffffffffffffffffffffffffffffffffffffff81168114610486575f80fd5b5f602082840312156105f8575f80fd5b8135610603816105c7565b9392505050565b5f806040838503121561061b575f80fd5b8235610626816105c7565b91506020830135610636816105c7565b809150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f805f60608486031215610680575f80fd5b833561068b816105c7565b9250602084013561069b816105c7565b9150604084013567ffffffffffffffff808211156106b7575f80fd5b818601915086601f8301126106ca575f80fd5b8135818111156106dc576106dc610641565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561072257610722610641565b8160405282815289602084870101111561073a575f80fd5b826020860160208301375f6020848301015280955050505050509250925092565b5f6020828403121561076b575f80fd5b8151610603816105c7565b73ffffffffffffffffffffffffffffffffffffffff831681525f602060408184015283518060408501525f5b818110156107be578581018301518582016060015282016107a2565b505f6060828601015260607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010192505050939250505056fea26469706673582212203083a4ccc2e42eed60bd19037f2efa77ed086dc7a5403f75bebb995dcba2221c64736f6c63430008140033", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000007d38458ed6b9b04a999e86057143c5faa0d259e9" + } + }, + { + "contractName": "PolygonZkEVMBridge implementation", + "balance": "0", + "nonce": "1", + "address": "0xeaE46b49f2FC2D5aDfC457a19d6E34aEc6Eb6C3A", + "bytecode": "0x60806040526004361062000197575f3560e01c8063647c576c11620000e2578063be5831c71162000086578063dbc16976116200005e578063dbc16976146200061a578063ee25560b1462000631578063fb5708341462000660575f80fd5b8063be5831c71462000591578063cd58657914620005cc578063d02103ca14620005e3575f80fd5b80639e34070f11620000ba5780639e34070f14620004f1578063aaa13cc21462000534578063bab161bf1462000558575f80fd5b8063647c576c146200047157806379e2cf97146200049557806381b1c17414620004ac575f80fd5b80632d2c9d94116200014a57806334ac9cf2116200012257806334ac9cf2146200033a5780633ae0504714620003685780633e197043146200037f575f80fd5b80632d2c9d9414620002695780632dfdf0b5146200028d578063318aee3d14620002b3575f80fd5b806322e95f2c116200017e57806322e95f2c14620001e4578063240ff378146200022e5780632cffd02e1462000245575f80fd5b806315064c96146200019b5780632072f6c514620001cb575b5f80fd5b348015620001a7575f80fd5b50606854620001b69060ff1681565b60405190151581526020015b60405180910390f35b348015620001d7575f80fd5b50620001e262000684565b005b348015620001f0575f80fd5b5062000208620002023660046200323f565b620006e2565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001620001c2565b620001e26200023f366004620032cf565b62000784565b34801562000251575f80fd5b50620001e26200026336600462003360565b620009ab565b34801562000275575f80fd5b50620001e26200028736600462003360565b62000f30565b34801562000299575f80fd5b50620002a460535481565b604051908152602001620001c2565b348015620002bf575f80fd5b5062000308620002d13660046200343e565b606b6020525f908152604090205463ffffffff811690640100000000900473ffffffffffffffffffffffffffffffffffffffff1682565b6040805163ffffffff909316835273ffffffffffffffffffffffffffffffffffffffff909116602083015201620001c2565b34801562000346575f80fd5b50606c54620002089073ffffffffffffffffffffffffffffffffffffffff1681565b34801562000374575f80fd5b50620002a462001130565b3480156200038b575f80fd5b50620002a46200039d36600462003472565b6040517fff0000000000000000000000000000000000000000000000000000000000000060f889901b1660208201527fffffffff0000000000000000000000000000000000000000000000000000000060e088811b821660218401527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606089811b821660258601529188901b909216603984015285901b16603d82015260518101839052607181018290525f90609101604051602081830303815290604052805190602001209050979650505050505050565b3480156200047d575f80fd5b50620001e26200048f366004620034f7565b62001215565b348015620004a1575f80fd5b50620001e26200145e565b348015620004b8575f80fd5b5062000208620004ca36600462003544565b606a6020525f908152604090205473ffffffffffffffffffffffffffffffffffffffff1681565b348015620004fd575f80fd5b50620001b66200050f36600462003544565b600881901c5f90815260696020526040902054600160ff9092169190911b9081161490565b34801562000540575f80fd5b5062000208620005523660046200355c565b62001498565b34801562000564575f80fd5b506068546200057b90610100900463ffffffff1681565b60405163ffffffff9091168152602001620001c2565b3480156200059d575f80fd5b506068546200057b90790100000000000000000000000000000000000000000000000000900463ffffffff1681565b620001e2620005dd36600462003609565b62001682565b348015620005ef575f80fd5b50606854620002089065010000000000900473ffffffffffffffffffffffffffffffffffffffff1681565b34801562000626575f80fd5b50620001e262001bd4565b3480156200063d575f80fd5b50620002a46200064f36600462003544565b60696020525f908152604090205481565b3480156200066c575f80fd5b50620001b66200067e366004620036a5565b62001c30565b606c5473ffffffffffffffffffffffffffffffffffffffff163314620006d6576040517fe2e8106b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b620006e062001d18565b565b6040805160e084901b7fffffffff0000000000000000000000000000000000000000000000000000000016602080830191909152606084901b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016602483015282516018818403018152603890920183528151918101919091205f908152606a909152205473ffffffffffffffffffffffffffffffffffffffff165b92915050565b60685460ff1615620007c2576040517f2f0047fc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60685463ffffffff868116610100909204161480620007e85750600263ffffffff861610155b1562000820576040517f0595ea2e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f501781209a1f8899323b96b4ef08b168df93e0a90c673d1e4cce39366cb62f9b6001606860019054906101000a900463ffffffff16338888348888605354604051620008769998979695949392919062003736565b60405180910390a1620009936200098d6001606860019054906101000a900463ffffffff16338989348989604051620008b1929190620037b0565b60405180910390206040517fff0000000000000000000000000000000000000000000000000000000000000060f889901b1660208201527fffffffff0000000000000000000000000000000000000000000000000000000060e088811b821660218401527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606089811b821660258601529188901b909216603984015285901b16603d82015260518101839052607181018290525f90609101604051602081830303815290604052805190602001209050979650505050505050565b62001dab565b8215620009a457620009a462001ebf565b5050505050565b60685460ff1615620009e9576040517f2f0047fc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b620009ff8b8b8b8b8b8b8b8b8b8b8b5f62001f8f565b73ffffffffffffffffffffffffffffffffffffffff861662000ad757604080515f8082526020820190925273ffffffffffffffffffffffffffffffffffffffff861690859060405162000a53919062003810565b5f6040518083038185875af1925050503d805f811462000a8f576040519150601f19603f3d011682016040523d82523d5f602084013e62000a94565b606091505b505090508062000ad0576040517f6747a28800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5062000eb8565b60685463ffffffff61010090910481169088160362000b195762000b1373ffffffffffffffffffffffffffffffffffffffff871685856200217a565b62000eb8565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e089901b1660208201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606088901b1660248201525f90603801604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291815281516020928301205f818152606a90935291205490915073ffffffffffffffffffffffffffffffffffffffff168062000e2f575f808062000beb868801886200391f565b9250925092505f8584848460405162000c0490620031f8565b62000c1293929190620039db565b8190604051809103905ff590508015801562000c30573d5f803e3d5ffd5b506040517f40c10f1900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8c81166004830152602482018c9052919250908216906340c10f19906044015f604051808303815f87803b15801562000ca3575f80fd5b505af115801562000cb6573d5f803e3d5ffd5b5050505080606a5f8881526020019081526020015f205f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060405180604001604052808e63ffffffff1681526020018d73ffffffffffffffffffffffffffffffffffffffff16815250606b5f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f820151815f015f6101000a81548163ffffffff021916908363ffffffff1602179055506020820151815f0160046101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055509050507f490e59a1701b938786ac72570a1efeac994a3dbe96e2e883e19e902ace6e6a398d8d838b8b60405162000e1d95949392919062003a17565b60405180910390a15050505062000eb5565b6040517f40c10f1900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8781166004830152602482018790528216906340c10f19906044015f604051808303815f87803b15801562000e9d575f80fd5b505af115801562000eb0573d5f803e3d5ffd5b505050505b50505b6040805163ffffffff8c811682528916602082015273ffffffffffffffffffffffffffffffffffffffff88811682840152861660608201526080810185905290517f25308c93ceeed162da955b3f7ce3e3f93606579e40fb92029faa9efe275459839181900360a00190a15050505050505050505050565b60685460ff161562000f6e576040517f2f0047fc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b62000f858b8b8b8b8b8b8b8b8b8b8b600162001f8f565b5f8473ffffffffffffffffffffffffffffffffffffffff1684888a868660405160240162000fb7949392919062003a5e565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f1806b5f200000000000000000000000000000000000000000000000000000000179052516200103a919062003810565b5f6040518083038185875af1925050503d805f811462001076576040519150601f19603f3d011682016040523d82523d5f602084013e6200107b565b606091505b5050905080620010b7576040517f37e391c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805163ffffffff8d811682528a16602082015273ffffffffffffffffffffffffffffffffffffffff89811682840152871660608201526080810186905290517f25308c93ceeed162da955b3f7ce3e3f93606579e40fb92029faa9efe275459839181900360a00190a1505050505050505050505050565b6053545f90819081805b60208110156200120c578083901c6001166001036200119d576033816020811062001169576200116962003aa5565b01546040805160208101929092528101859052606001604051602081830303815290604052805190602001209350620011ca565b60408051602081018690529081018390526060016040516020818303038152906040528051906020012093505b60408051602081018490529081018390526060016040516020818303038152906040528051906020012091508080620012039062003aff565b9150506200113a565b50919392505050565b5f54610100900460ff16158080156200123457505f54600160ff909116105b806200124f5750303b1580156200124f57505f5460ff166001145b620012e1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084015b60405180910390fd5b5f80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905580156200133e575f80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b606880547fffffffffffffff000000000000000000000000000000000000000000000000ff1661010063ffffffff8716027fffffffffffffff0000000000000000000000000000000000000000ffffffffff16176501000000000073ffffffffffffffffffffffffffffffffffffffff8681169190910291909117909155606c80547fffffffffffffffffffffffff000000000000000000000000000000000000000016918416919091179055620013f562002250565b801562001458575f80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050565b605354606854790100000000000000000000000000000000000000000000000000900463ffffffff161015620006e057620006e062001ebf565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e089901b1660208201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606088901b1660248201525f9081906038016040516020818303038152906040528051906020012090505f60ff60f81b3083604051806020016200152c90620031f8565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe082820381018352601f90910116604081905262001577908d908d908d908d908d9060200162003b39565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815290829052620015b5929160200162003b79565b604051602081830303815290604052805190602001206040516020016200163e94939291907fff0000000000000000000000000000000000000000000000000000000000000094909416845260609290921b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001660018401526015830152603582015260550190565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001815291905280516020909101209a9950505050505050505050565b60685460ff1615620016c0576040517f2f0047fc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b620016ca620022f2565b60685463ffffffff888116610100909204161480620016f05750600263ffffffff881610155b1562001728576040517f0595ea2e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8060608773ffffffffffffffffffffffffffffffffffffffff88166200178c5788341462001783576040517fb89240f500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f925062001a79565b3415620017c5576040517f798ee6f100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8089165f908152606b602090815260409182902082518084019093525463ffffffff8116835264010000000090049092169181018290529015620018ae576040517f9dc29fac000000000000000000000000000000000000000000000000000000008152336004820152602481018b905273ffffffffffffffffffffffffffffffffffffffff8a1690639dc29fac906044015f604051808303815f87803b15801562001884575f80fd5b505af115801562001897573d5f803e3d5ffd5b5050505080602001519450805f0151935062001a77565b8515620018c357620018c3898b898962002367565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8b16906370a0823190602401602060405180830381865afa1580156200192e573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001954919062003bab565b90506200197a73ffffffffffffffffffffffffffffffffffffffff8b1633308e6200287a565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8c16906370a0823190602401602060405180830381865afa158015620019e5573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062001a0b919062003bab565b905062001a19828262003bc3565b6068548c9850610100900463ffffffff169650935062001a3987620028da565b62001a448c620029ee565b62001a4f8d62002af7565b60405160200162001a6393929190620039db565b604051602081830303815290604052945050505b505b7f501781209a1f8899323b96b4ef08b168df93e0a90c673d1e4cce39366cb62f9b5f84868e8e868860535460405162001aba98979695949392919062003bd9565b60405180910390a162001bac6200098d5f85878f8f8789805190602001206040517fff0000000000000000000000000000000000000000000000000000000000000060f889901b1660208201527fffffffff0000000000000000000000000000000000000000000000000000000060e088811b821660218401527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606089811b821660258601529188901b909216603984015285901b16603d82015260518101839052607181018290525f90609101604051602081830303815290604052805190602001209050979650505050505050565b861562001bbd5762001bbd62001ebf565b5050505062001bcb60018055565b50505050505050565b606c5473ffffffffffffffffffffffffffffffffffffffff16331462001c26576040517fe2e8106b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b620006e062002bf5565b5f84815b602081101562001d0a57600163ffffffff8616821c8116900362001ca65785816020811062001c675762001c6762003aa5565b60200201358260405160200162001c88929190918252602082015260400190565b60405160208183030381529060405280519060200120915062001cf5565b8186826020811062001cbc5762001cbc62003aa5565b602002013560405160200162001cdc929190918252602082015260400190565b6040516020818303038152906040528051906020012091505b8062001d018162003aff565b91505062001c34565b50821490505b949350505050565b60685460ff161562001d56576040517f2f0047fc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606880547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790556040517f2261efe5aef6fedc1fd1550b25facc9181745623049c7901287030b9ad1a5497905f90a1565b80600162001dbc6020600262003d88565b62001dc8919062003bc3565b6053541062001e03576040517fef5ccf6600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60535f815462001e149062003aff565b918290555090505f5b602081101562001eaf578082901c60011660010362001e5557826033826020811062001e4d5762001e4d62003aa5565b015550505050565b6033816020811062001e6b5762001e6b62003aa5565b01546040805160208101929092528101849052606001604051602081830303815290604052805190602001209250808062001ea69062003aff565b91505062001e1d565b5062001eba62003d95565b505050565b6053546068805463ffffffff909216790100000000000000000000000000000000000000000000000000027fffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffffff909216919091179081905573ffffffffffffffffffffffffffffffffffffffff65010000000000909104166333d6247d62001f4562001130565b6040518263ffffffff1660e01b815260040162001f6491815260200190565b5f604051808303815f87803b15801562001f7c575f80fd5b505af115801562001458573d5f803e3d5ffd5b62001fa08b63ffffffff1662002c84565b6068546040805160208082018e90528183018d9052825180830384018152606083019384905280519101207f257b36320000000000000000000000000000000000000000000000000000000090925260648101919091525f9165010000000000900473ffffffffffffffffffffffffffffffffffffffff169063257b3632906084016020604051808303815f875af11580156200203f573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062002065919062003bab565b9050805f03620020a0576040517e2f6fad00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60685463ffffffff8881166101009092041614620020ea576040517f0595ea2e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6068545f90610100900463ffffffff16620021075750896200210a565b508a5b620021336200212a848c8c8c8c8c8c8c604051620008b1929190620037b0565b8f8f8462001c30565b6200216a576040517fe0417cec00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050505050505050505050505050565b60405173ffffffffffffffffffffffffffffffffffffffff831660248201526044810182905262001eba9084907fa9059cbb00000000000000000000000000000000000000000000000000000000906064015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009093169290921790915262002ce8565b5f54610100900460ff16620022e8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401620012d8565b620006e062002dfa565b60026001540362002360576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401620012d8565b6002600155565b5f62002377600482848662003dc2565b620023829162003deb565b90507f2afa5331000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821601620025fc575f808080808080620023e4896004818d62003dc2565b810190620023f3919062003e34565b96509650965096509650965096503373ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff161462002467576040517f912ecce700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff86163014620024b7576040517f750643af00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8a8514620024f1576040517f03fffc4b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805173ffffffffffffffffffffffffffffffffffffffff89811660248301528881166044830152606482018890526084820187905260ff861660a483015260c4820185905260e48083018590528351808403909101815261010490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fd505accf000000000000000000000000000000000000000000000000000000001790529151918e1691620025ac919062003810565b5f604051808303815f865af19150503d805f8114620025e7576040519150601f19603f3d011682016040523d82523d5f602084013e620025ec565b606091505b50505050505050505050620009a4565b7fffffffff0000000000000000000000000000000000000000000000000000000081167f8fcbaf0c000000000000000000000000000000000000000000000000000000001462002678576040517fe282c0ba00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f808080808080806200268f8a6004818e62003dc2565b8101906200269e919062003e8a565b975097509750975097509750975097503373ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff161462002714576040517f912ecce700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8716301462002764576040517f750643af00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805173ffffffffffffffffffffffffffffffffffffffff8a811660248301528981166044830152606482018990526084820188905286151560a483015260ff861660c483015260e482018590526101048083018590528351808403909101815261012490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f8fcbaf0c000000000000000000000000000000000000000000000000000000001790529151918f169162002828919062003810565b5f604051808303815f865af19150503d805f811462002863576040519150601f19603f3d011682016040523d82523d5f602084013e62002868565b606091505b50505050505050505050505050505050565b60405173ffffffffffffffffffffffffffffffffffffffff80851660248301528316604482015260648101829052620014589085907f23b872dd0000000000000000000000000000000000000000000000000000000090608401620021cd565b60408051600481526024810182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f06fdde030000000000000000000000000000000000000000000000000000000017905290516060915f91829173ffffffffffffffffffffffffffffffffffffffff8616916200295d919062003810565b5f60405180830381855afa9150503d805f811462002997576040519150601f19603f3d011682016040523d82523d5f602084013e6200299c565b606091505b509150915081620029e3576040518060400160405280600781526020017f4e4f5f4e414d450000000000000000000000000000000000000000000000000081525062001d10565b62001d108162002e92565b60408051600481526024810182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f95d89b410000000000000000000000000000000000000000000000000000000017905290516060915f91829173ffffffffffffffffffffffffffffffffffffffff86169162002a71919062003810565b5f60405180830381855afa9150503d805f811462002aab576040519150601f19603f3d011682016040523d82523d5f602084013e62002ab0565b606091505b509150915081620029e3576040518060400160405280600981526020017f4e4f5f53594d424f4c000000000000000000000000000000000000000000000081525062001d10565b60408051600481526024810182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f313ce5670000000000000000000000000000000000000000000000000000000017905290515f918291829173ffffffffffffffffffffffffffffffffffffffff86169162002b79919062003810565b5f60405180830381855afa9150503d805f811462002bb3576040519150601f19603f3d011682016040523d82523d5f602084013e62002bb8565b606091505b509150915081801562002bcc575080516020145b62002bd957601262001d10565b8080602001905181019062001d10919062003f11565b60018055565b60685460ff1662002c32576040517f5386698100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606880547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690556040517f1e5e34eea33501aecf2ebec9fe0e884a40804275ea7fe10b2ba084c8374308b3905f90a1565b600881901c5f8181526069602052604081208054600160ff861690811b91821892839055929091908183169003620009a4576040517f646cf55800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f62002d4b826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff166200307d9092919063ffffffff16565b80519091501562001eba578080602001905181019062002d6c919062003f2f565b62001eba576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152608401620012d8565b5f54610100900460ff1662002bef576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401620012d8565b6060604082511062002eb457818060200190518101906200077e919062003f4d565b81516020036200303f575f5b60208110801562002f0b575082818151811062002ee15762002ee162003aa5565b01602001517fff000000000000000000000000000000000000000000000000000000000000001615155b1562002f26578062002f1d8162003aff565b91505062002ec0565b805f0362002f6957505060408051808201909152601281527f4e4f545f56414c49445f454e434f44494e4700000000000000000000000000006020820152919050565b5f8167ffffffffffffffff81111562002f865762002f86620037bf565b6040519080825280601f01601f19166020018201604052801562002fb1576020820181803683370190505b5090505f5b82811015620030375784818151811062002fd45762002fd462003aa5565b602001015160f81c60f81b82828151811062002ff45762002ff462003aa5565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a905350806200302e8162003aff565b91505062002fb6565b509392505050565b505060408051808201909152601281527f4e4f545f56414c49445f454e434f44494e470000000000000000000000000000602082015290565b919050565b606062001d1084845f85855f808673ffffffffffffffffffffffffffffffffffffffff168587604051620030b2919062003810565b5f6040518083038185875af1925050503d805f8114620030ee576040519150601f19603f3d011682016040523d82523d5f602084013e620030f3565b606091505b5091509150620031068783838762003111565b979650505050505050565b60608315620031ab5782515f03620031a35773ffffffffffffffffffffffffffffffffffffffff85163b620031a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401620012d8565b508162001d10565b62001d108383815115620031c25781518083602001fd5b806040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620012d8919062003fc8565b611b0d8062003fdd83390190565b803563ffffffff8116811462003078575f80fd5b73ffffffffffffffffffffffffffffffffffffffff811681146200323c575f80fd5b50565b5f806040838503121562003251575f80fd5b6200325c8362003206565b915060208301356200326e816200321a565b809150509250929050565b80151581146200323c575f80fd5b5f8083601f84011262003298575f80fd5b50813567ffffffffffffffff811115620032b0575f80fd5b602083019150836020828501011115620032c8575f80fd5b9250929050565b5f805f805f60808688031215620032e4575f80fd5b620032ef8662003206565b9450602086013562003301816200321a565b93506040860135620033138162003279565b9250606086013567ffffffffffffffff8111156200332f575f80fd5b6200333d8882890162003287565b969995985093965092949392505050565b8061040081018310156200077e575f80fd5b5f805f805f805f805f805f6105208c8e0312156200337c575f80fd5b620033888d8d6200334e565b9a50620033996104008d0162003206565b99506104208c013598506104408c01359750620033ba6104608d0162003206565b96506104808c0135620033cd816200321a565b9550620033de6104a08d0162003206565b94506104c08c0135620033f1816200321a565b93506104e08c013592506105008c013567ffffffffffffffff81111562003416575f80fd5b620034248e828f0162003287565b915080935050809150509295989b509295989b9093969950565b5f602082840312156200344f575f80fd5b81356200345c816200321a565b9392505050565b60ff811681146200323c575f80fd5b5f805f805f805f60e0888a03121562003489575f80fd5b8735620034968162003463565b9650620034a66020890162003206565b95506040880135620034b8816200321a565b9450620034c86060890162003206565b93506080880135620034da816200321a565b9699959850939692959460a0840135945060c09093013592915050565b5f805f606084860312156200350a575f80fd5b620035158462003206565b9250602084013562003527816200321a565b9150604084013562003539816200321a565b809150509250925092565b5f6020828403121562003555575f80fd5b5035919050565b5f805f805f805f60a0888a03121562003573575f80fd5b6200357e8862003206565b9650602088013562003590816200321a565b9550604088013567ffffffffffffffff80821115620035ad575f80fd5b620035bb8b838c0162003287565b909750955060608a0135915080821115620035d4575f80fd5b50620035e38a828b0162003287565b9094509250506080880135620035f98162003463565b8091505092959891949750929550565b5f805f805f805f60c0888a03121562003620575f80fd5b6200362b8862003206565b965060208801356200363d816200321a565b955060408801359450606088013562003656816200321a565b93506080880135620036688162003279565b925060a088013567ffffffffffffffff81111562003684575f80fd5b620036928a828b0162003287565b989b979a50959850939692959293505050565b5f805f806104608587031215620036ba575f80fd5b84359350620036cd86602087016200334e565b9250620036de610420860162003206565b939692955092936104400135925050565b81835281816020850137505f602082840101525f60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b5f61010060ff8c16835263ffffffff808c16602085015273ffffffffffffffffffffffffffffffffffffffff808c166040860152818b166060860152808a166080860152508760a08501528160c0850152620037968285018789620036ef565b925080851660e085015250509a9950505050505050505050565b818382375f9101908152919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f5b8381101562003808578181015183820152602001620037ee565b50505f910152565b5f825162003823818460208701620037ec565b9190910192915050565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715620038775762003877620037bf565b604052919050565b5f67ffffffffffffffff8211156200389b576200389b620037bf565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b5f82601f830112620038d7575f80fd5b8135620038ee620038e8826200387f565b6200382d565b81815284602083860101111562003903575f80fd5b816020850160208301375f918101602001919091529392505050565b5f805f6060848603121562003932575f80fd5b833567ffffffffffffffff808211156200394a575f80fd5b6200395887838801620038c7565b945060208601359150808211156200396e575f80fd5b506200397d86828701620038c7565b9250506040840135620035398162003463565b5f8151808452620039a9816020860160208601620037ec565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b606081525f620039ef606083018662003990565b828103602084015262003a03818662003990565b91505060ff83166040830152949350505050565b63ffffffff861681525f73ffffffffffffffffffffffffffffffffffffffff80871660208401528086166040840152506080606083015262003106608083018486620036ef565b73ffffffffffffffffffffffffffffffffffffffff8516815263ffffffff84166020820152606060408201525f62003a9b606083018486620036ef565b9695505050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820362003b325762003b3262003ad2565b5060010190565b606081525f62003b4e606083018789620036ef565b828103602084015262003b63818688620036ef565b91505060ff831660408301529695505050505050565b5f835162003b8c818460208801620037ec565b83519083019062003ba2818360208801620037ec565b01949350505050565b5f6020828403121562003bbc575f80fd5b5051919050565b818103818111156200077e576200077e62003ad2565b5f61010060ff8b16835263ffffffff808b16602085015273ffffffffffffffffffffffffffffffffffffffff808b166040860152818a1660608601528089166080860152508660a08501528160c085015262003c388285018762003990565b925080851660e085015250509998505050505050505050565b600181815b8085111562003cb057817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482111562003c945762003c9462003ad2565b8085161562003ca257918102915b93841c939080029062003c56565b509250929050565b5f8262003cc8575060016200077e565b8162003cd657505f6200077e565b816001811462003cef576002811462003cfa5762003d1a565b60019150506200077e565b60ff84111562003d0e5762003d0e62003ad2565b50506001821b6200077e565b5060208310610133831016604e8410600b841016171562003d3f575081810a6200077e565b62003d4b838362003c51565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482111562003d805762003d8062003ad2565b029392505050565b5f6200345c838362003cb8565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52600160045260245ffd5b5f808585111562003dd1575f80fd5b8386111562003dde575f80fd5b5050820193919092039150565b7fffffffff00000000000000000000000000000000000000000000000000000000813581811691600485101562003e2c5780818660040360031b1b83161692505b505092915050565b5f805f805f805f60e0888a03121562003e4b575f80fd5b873562003e58816200321a565b9650602088013562003e6a816200321a565b955060408801359450606088013593506080880135620034da8162003463565b5f805f805f805f80610100898b03121562003ea3575f80fd5b883562003eb0816200321a565b9750602089013562003ec2816200321a565b96506040890135955060608901359450608089013562003ee28162003279565b935060a089013562003ef48162003463565b979a969950949793969295929450505060c08201359160e0013590565b5f6020828403121562003f22575f80fd5b81516200345c8162003463565b5f6020828403121562003f40575f80fd5b81516200345c8162003279565b5f6020828403121562003f5e575f80fd5b815167ffffffffffffffff81111562003f75575f80fd5b8201601f8101841362003f86575f80fd5b805162003f97620038e8826200387f565b81815285602083850101111562003fac575f80fd5b62003fbf826020830160208601620037ec565b95945050505050565b602081525f6200345c60208301846200399056fe61010060405234801562000011575f80fd5b5060405162001b0d38038062001b0d833981016040819052620000349162000282565b828260036200004483826200038d565b5060046200005382826200038d565b50503360c0525060ff811660e05246608081905262000072906200007f565b60a0525062000455915050565b5f7f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f620000ab6200012c565b805160209182012060408051808201825260018152603160f81b90840152805192830193909352918101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc66060820152608081018390523060a082015260c001604051602081830303815290604052805190602001209050919050565b6060600380546200013d9062000301565b80601f01602080910402602001604051908101604052809291908181526020018280546200016b9062000301565b8015620001ba5780601f106200019057610100808354040283529160200191620001ba565b820191905f5260205f20905b8154815290600101906020018083116200019c57829003601f168201915b5050505050905090565b634e487b7160e01b5f52604160045260245ffd5b5f82601f830112620001e8575f80fd5b81516001600160401b0380821115620002055762000205620001c4565b604051601f8301601f19908116603f01168101908282118183101715620002305762000230620001c4565b816040528381526020925086838588010111156200024c575f80fd5b5f91505b838210156200026f578582018301518183018401529082019062000250565b5f93810190920192909252949350505050565b5f805f6060848603121562000295575f80fd5b83516001600160401b0380821115620002ac575f80fd5b620002ba87838801620001d8565b94506020860151915080821115620002d0575f80fd5b50620002df86828701620001d8565b925050604084015160ff81168114620002f6575f80fd5b809150509250925092565b600181811c908216806200031657607f821691505b6020821081036200033557634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111562000388575f81815260208120601f850160051c81016020861015620003635750805b601f850160051c820191505b8181101562000384578281556001016200036f565b5050505b505050565b81516001600160401b03811115620003a957620003a9620001c4565b620003c181620003ba845462000301565b846200033b565b602080601f831160018114620003f7575f8415620003df5750858301515b5f19600386901b1c1916600185901b17855562000384565b5f85815260208120601f198616915b82811015620004275788860151825594840194600190910190840162000406565b50858210156200044557878501515f19600388901b60f8161c191681555b5050505050600190811b01905550565b60805160a05160c05160e05161166f6200049e5f395f61022d01525f81816102fb015281816105ad015261069401525f61052801525f818161036d01526104f2015261166f5ff3fe608060405234801561000f575f80fd5b506004361061016e575f3560e01c806370a08231116100d2578063a457c2d711610088578063d505accf11610063578063d505accf1461038f578063dd62ed3e146103a2578063ffa1ad74146103e7575f80fd5b8063a457c2d714610342578063a9059cbb14610355578063cd0d009614610368575f80fd5b806395d89b41116100b857806395d89b41146102db5780639dc29fac146102e3578063a3c573eb146102f6575f80fd5b806370a08231146102875780637ecebe00146102bc575f80fd5b806330adf81f116101275780633644e5151161010d5780633644e51514610257578063395093511461025f57806340c10f1914610272575f80fd5b806330adf81f146101ff578063313ce56714610226575f80fd5b806318160ddd1161015757806318160ddd146101b357806320606b70146101c557806323b872dd146101ec575f80fd5b806306fdde0314610172578063095ea7b314610190575b5f80fd5b61017a610423565b60405161018791906113c1565b60405180910390f35b6101a361019e366004611452565b6104b3565b6040519015158152602001610187565b6002545b604051908152602001610187565b6101b77f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f81565b6101a36101fa36600461147a565b6104cc565b6101b77f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b60405160ff7f0000000000000000000000000000000000000000000000000000000000000000168152602001610187565b6101b76104ef565b6101a361026d366004611452565b61054a565b610285610280366004611452565b610595565b005b6101b76102953660046114b3565b73ffffffffffffffffffffffffffffffffffffffff165f9081526020819052604090205490565b6101b76102ca3660046114b3565b60056020525f908152604090205481565b61017a61066d565b6102856102f1366004611452565b61067c565b61031d7f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610187565b6101a3610350366004611452565b61074b565b6101a3610363366004611452565b61081b565b6101b77f000000000000000000000000000000000000000000000000000000000000000081565b61028561039d3660046114d3565b610828565b6101b76103b0366004611540565b73ffffffffffffffffffffffffffffffffffffffff9182165f90815260016020908152604080832093909416825291909152205490565b61017a6040518060400160405280600181526020017f310000000000000000000000000000000000000000000000000000000000000081525081565b60606003805461043290611571565b80601f016020809104026020016040519081016040528092919081815260200182805461045e90611571565b80156104a95780601f10610480576101008083540402835291602001916104a9565b820191905f5260205f20905b81548152906001019060200180831161048c57829003601f168201915b5050505050905090565b5f336104c0818585610b59565b60019150505b92915050565b5f336104d9858285610d0c565b6104e4858585610de2565b506001949350505050565b5f7f00000000000000000000000000000000000000000000000000000000000000004614610525576105204661104f565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b335f81815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff871684529091528120549091906104c090829086906105909087906115ef565b610b59565b3373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461065f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f546f6b656e577261707065643a3a6f6e6c794272696467653a204e6f7420506f60448201527f6c79676f6e5a6b45564d4272696467650000000000000000000000000000000060648201526084015b60405180910390fd5b6106698282611116565b5050565b60606004805461043290611571565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610741576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f546f6b656e577261707065643a3a6f6e6c794272696467653a204e6f7420506f60448201527f6c79676f6e5a6b45564d427269646765000000000000000000000000000000006064820152608401610656565b6106698282611207565b335f81815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff871684529091528120549091908381101561080e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760448201527f207a65726f0000000000000000000000000000000000000000000000000000006064820152608401610656565b6104e48286868403610b59565b5f336104c0818585610de2565b834211156108b7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f546f6b656e577261707065643a3a7065726d69743a204578706972656420706560448201527f726d6974000000000000000000000000000000000000000000000000000000006064820152608401610656565b73ffffffffffffffffffffffffffffffffffffffff87165f90815260056020526040812080547f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9918a918a918a91908661091083611602565b9091555060408051602081019690965273ffffffffffffffffffffffffffffffffffffffff94851690860152929091166060840152608083015260a082015260c0810186905260e0016040516020818303038152906040528051906020012090505f61097a6104ef565b6040517f19010000000000000000000000000000000000000000000000000000000000006020820152602281019190915260428101839052606201604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815282825280516020918201205f80855291840180845281905260ff89169284019290925260608301879052608083018690529092509060019060a0016020604051602081039080840390855afa158015610a3b573d5f803e3d5ffd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615801590610ab657508973ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b610b42576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f546f6b656e577261707065643a3a7065726d69743a20496e76616c696420736960448201527f676e6174757265000000000000000000000000000000000000000000000000006064820152608401610656565b610b4d8a8a8a610b59565b50505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff8316610bfb576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f72657373000000000000000000000000000000000000000000000000000000006064820152608401610656565b73ffffffffffffffffffffffffffffffffffffffff8216610c9e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f73730000000000000000000000000000000000000000000000000000000000006064820152608401610656565b73ffffffffffffffffffffffffffffffffffffffff8381165f8181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b73ffffffffffffffffffffffffffffffffffffffff8381165f908152600160209081526040808320938616835292905220547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610ddc5781811015610dcf576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610656565b610ddc8484848403610b59565b50505050565b73ffffffffffffffffffffffffffffffffffffffff8316610e85576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f20616460448201527f64726573730000000000000000000000000000000000000000000000000000006064820152608401610656565b73ffffffffffffffffffffffffffffffffffffffff8216610f28576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201527f65737300000000000000000000000000000000000000000000000000000000006064820152608401610656565b73ffffffffffffffffffffffffffffffffffffffff83165f9081526020819052604090205481811015610fdd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e742065786365656473206260448201527f616c616e636500000000000000000000000000000000000000000000000000006064820152608401610656565b73ffffffffffffffffffffffffffffffffffffffff8481165f81815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3610ddc565b5f7f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f611079610423565b8051602091820120604080518082018252600181527f310000000000000000000000000000000000000000000000000000000000000090840152805192830193909352918101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc66060820152608081018390523060a082015260c001604051602081830303815290604052805190602001209050919050565b73ffffffffffffffffffffffffffffffffffffffff8216611193576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610656565b8060025f8282546111a491906115ef565b909155505073ffffffffffffffffffffffffffffffffffffffff82165f81815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b73ffffffffffffffffffffffffffffffffffffffff82166112aa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f2061646472657360448201527f73000000000000000000000000000000000000000000000000000000000000006064820152608401610656565b73ffffffffffffffffffffffffffffffffffffffff82165f908152602081905260409020548181101561135f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e60448201527f63650000000000000000000000000000000000000000000000000000000000006064820152608401610656565b73ffffffffffffffffffffffffffffffffffffffff83165f818152602081815260408083208686039055600280548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9101610cff565b5f6020808352835180828501525f5b818110156113ec578581018301518582016040015282016113d0565b505f6040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461144d575f80fd5b919050565b5f8060408385031215611463575f80fd5b61146c8361142a565b946020939093013593505050565b5f805f6060848603121561148c575f80fd5b6114958461142a565b92506114a36020850161142a565b9150604084013590509250925092565b5f602082840312156114c3575f80fd5b6114cc8261142a565b9392505050565b5f805f805f805f60e0888a0312156114e9575f80fd5b6114f28861142a565b96506115006020890161142a565b95506040880135945060608801359350608088013560ff81168114611523575f80fd5b9699959850939692959460a0840135945060c09093013592915050565b5f8060408385031215611551575f80fd5b61155a8361142a565b91506115686020840161142a565b90509250929050565b600181811c9082168061158557607f821691505b6020821081036115bc577f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b50919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b808201808211156104c6576104c66115c2565b5f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611632576116326115c2565b506001019056fea2646970667358221220a04a4613834006222ac539b942dfe3284c1163f5082f3bafb302007d825cd7ff64736f6c63430008140033a2646970667358221220efc010afd4f7ef7135c2d52e7832f4d0832c536dbd0768cad0ec9406e70e02b864736f6c63430008140033" + }, + { + "contractName": "PolygonZkEVMBridge proxy", + "balance": "200000000000000000000000000", + "nonce": "1", + "address": "0xA34BBAf52eE84Cd95a6d5Ac2Eab9de142D4cdB53", + "bytecode": "0x60806040526004361061005d575f3560e01c80635c60da1b116100425780635c60da1b146100a65780638f283970146100e3578063f851a440146101025761006c565b80633659cfe6146100745780634f1ef286146100935761006c565b3661006c5761006a610116565b005b61006a610116565b34801561007f575f80fd5b5061006a61008e366004610854565b610130565b61006a6100a136600461086d565b610178565b3480156100b1575f80fd5b506100ba6101eb565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100ee575f80fd5b5061006a6100fd366004610854565b610228565b34801561010d575f80fd5b506100ba610255565b61011e610282565b61012e610129610359565b610362565b565b610138610380565b73ffffffffffffffffffffffffffffffffffffffff1633036101705761016d8160405180602001604052805f8152505f6103bf565b50565b61016d610116565b610180610380565b73ffffffffffffffffffffffffffffffffffffffff1633036101e3576101de8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250600192506103bf915050565b505050565b6101de610116565b5f6101f4610380565b73ffffffffffffffffffffffffffffffffffffffff16330361021d57610218610359565b905090565b610225610116565b90565b610230610380565b73ffffffffffffffffffffffffffffffffffffffff1633036101705761016d816103e9565b5f61025e610380565b73ffffffffffffffffffffffffffffffffffffffff16330361021d57610218610380565b61028a610380565b73ffffffffffffffffffffffffffffffffffffffff16330361012e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f6574000000000000000000000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b5f61021861044a565b365f80375f80365f845af43d5f803e80801561037c573d5ff35b3d5ffd5b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b5473ffffffffffffffffffffffffffffffffffffffff16919050565b6103c883610471565b5f825111806103d45750805b156101de576103e383836104bd565b50505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f610412610380565b6040805173ffffffffffffffffffffffffffffffffffffffff928316815291841660208301520160405180910390a161016d816104e9565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6103a3565b61047a816105f5565b60405173ffffffffffffffffffffffffffffffffffffffff8216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606104e28383604051806060016040528060278152602001610977602791396106c0565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff811661058c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401610350565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9290921691909117905550565b73ffffffffffffffffffffffffffffffffffffffff81163b610699576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e7472616374000000000000000000000000000000000000006064820152608401610350565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6105af565b60605f808573ffffffffffffffffffffffffffffffffffffffff16856040516106e9919061090b565b5f60405180830381855af49150503d805f8114610721576040519150601f19603f3d011682016040523d82523d5f602084013e610726565b606091505b509150915061073786838387610741565b9695505050505050565b606083156107d65782515f036107cf5773ffffffffffffffffffffffffffffffffffffffff85163b6107cf576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610350565b50816107e0565b6107e083836107e8565b949350505050565b8151156107f85781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103509190610926565b803573ffffffffffffffffffffffffffffffffffffffff8116811461084f575f80fd5b919050565b5f60208284031215610864575f80fd5b6104e28261082c565b5f805f6040848603121561087f575f80fd5b6108888461082c565b9250602084013567ffffffffffffffff808211156108a4575f80fd5b818601915086601f8301126108b7575f80fd5b8135818111156108c5575f80fd5b8760208285010111156108d6575f80fd5b6020830194508093505050509250925092565b5f5b838110156109035781810151838201526020016108eb565b50505f910152565b5f825161091c8184602087016108e9565b9190910192915050565b602081525f82518060208401526109448160408501602087016108e9565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212202ac98acbfbb3d3ac1b74050e18c4e76db25a3ff2801ec69bf85d0c61414d502b64736f6c63430008140033", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000068": "0x00000000000000a40d5f56745a118d0906a34e69aec8c0db1cb8fa0000000100", + "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000fe3306bb4124e90ea08af2776008592052ecb9e0", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x000000000000000000000000eae46b49f2fc2d5adfc457a19d6e34aec6eb6c3a" + } + }, + { + "contractName": "PolygonZkEVMGlobalExitRootL2 implementation", + "balance": "0", + "nonce": "1", + "address": "0x3c26Af9e85715f5767Ba566AAa4dfcD9c4a2fc1a", + "bytecode": "0x608060405234801561000f575f80fd5b506004361061004a575f3560e01c806301fd90441461004e578063257b36321461006a57806333d6247d14610089578063a3c573eb1461009e575b5f80fd5b61005760015481565b6040519081526020015b60405180910390f35b61005761007836600461015e565b5f6020819052908152604090205481565b61009c61009736600461015e565b6100ea565b005b6100c57f000000000000000000000000a34bbaf52ee84cd95a6d5ac2eab9de142d4cdb5381565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610061565b3373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000a34bbaf52ee84cd95a6d5ac2eab9de142d4cdb531614610159576040517fb49365dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600155565b5f6020828403121561016e575f80fd5b503591905056fea26469706673582212205108c6c4f924146b736832a1bdf696e20d900450207b7452462368d150f2c71c64736f6c63430008140033" + }, + { + "contractName": "PolygonZkEVMGlobalExitRootL2 proxy", + "balance": "0", + "nonce": "1", + "address": "0xa40d5f56745a118d0906a34e69aec8c0db1cb8fa", + "bytecode": "0x60806040523661001357610011610017565b005b6100115b61001f6101b7565b6001600160a01b0316336001600160a01b0316141561016f5760606001600160e01b031960003516631b2ce7f360e11b8114156100655761005e6101ea565b9150610167565b6001600160e01b0319811663278f794360e11b14156100865761005e610241565b6001600160e01b031981166308f2839760e41b14156100a75761005e610287565b6001600160e01b031981166303e1469160e61b14156100c85761005e6102b8565b6001600160e01b03198116635c60da1b60e01b14156100e95761005e6102f8565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b61017761030c565b565b606061019e83836040518060600160405280602781526020016108576027913961031c565b9392505050565b90565b6001600160a01b03163b151590565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101f4610394565b600061020336600481846106a2565b81019061021091906106e8565b905061022d8160405180602001604052806000815250600061039f565b505060408051602081019091526000815290565b606060008061025336600481846106a2565b8101906102609190610719565b915091506102708282600161039f565b604051806020016040528060008152509250505090565b6060610291610394565b60006102a036600481846106a2565b8101906102ad91906106e8565b905061022d816103cb565b60606102c2610394565b60006102cc6101b7565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b6060610302610394565b60006102cc610422565b610177610317610422565b610431565b6060600080856001600160a01b0316856040516103399190610807565b600060405180830381855af49150503d8060008114610374576040519150601f19603f3d011682016040523d82523d6000602084013e610379565b606091505b509150915061038a86838387610455565b9695505050505050565b341561017757600080fd5b6103a8836104d3565b6000825111806103b55750805b156103c6576103c48383610179565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103f46101b7565b604080516001600160a01b03928316815291841660208301520160405180910390a161041f81610513565b50565b600061042c6105bc565b905090565b3660008037600080366000845af43d6000803e808015610450573d6000f35b3d6000fd5b606083156104c15782516104ba576001600160a01b0385163b6104ba5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161015e565b50816104cb565b6104cb83836105e4565b949350505050565b6104dc8161060e565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6001600160a01b0381166105785760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161015e565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6101db565b8151156105f45781518083602001fd5b8060405162461bcd60e51b815260040161015e9190610823565b6001600160a01b0381163b61067b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161015e565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61059b565b600080858511156106b257600080fd5b838611156106bf57600080fd5b5050820193919092039150565b80356001600160a01b03811681146106e357600080fd5b919050565b6000602082840312156106fa57600080fd5b61019e826106cc565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561072c57600080fd5b610735836106cc565b9150602083013567ffffffffffffffff8082111561075257600080fd5b818501915085601f83011261076657600080fd5b81358181111561077857610778610703565b604051601f8201601f19908116603f011681019083821181831017156107a0576107a0610703565b816040528281528860208487010111156107b957600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b60005b838110156107f65781810151838201526020016107de565b838111156103c45750506000910152565b600082516108198184602087016107db565b9190910192915050565b60208152600082518060208401526108428160408501602087016107db565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122012bb4f564f73959a03513dc74fc3c6e40e8386e6f02c16b78d6db00ce0aa16af64736f6c63430008090033", + "storage": { + "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000fe3306bb4124e90ea08af2776008592052ecb9e0", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0000000000000000000000003c26af9e85715f5767ba566aaa4dfcd9c4a2fc1a" + } + }, + { + "contractName": "PolygonZkEVMTimelock", + "balance": "0", + "nonce": "1", + "address": "0x7D38458ED6b9B04A999E86057143C5fAa0D259E9", + "bytecode": "0x6080604052600436106101bd575f3560e01c806364d62353116100f2578063b1c5f42711610092578063d547741f11610062578063d547741f1461063a578063e38335e514610659578063f23a6e611461066c578063f27a0c92146106b0575f80fd5b8063b1c5f4271461058d578063bc197c81146105ac578063c4d252f5146105f0578063d45c44351461060f575f80fd5b80638f61f4f5116100cd5780638f61f4f5146104c557806391d14854146104f8578063a217fddf14610547578063b08e51c01461055a575f80fd5b806364d62353146104685780638065657f146104875780638f2a0bb0146104a6575f80fd5b8063248a9ca31161015d57806331d507501161013857806331d50750146103b357806336568abe146103d25780633a6aae72146103f1578063584b153e14610449575f80fd5b8063248a9ca3146103375780632ab0f529146103655780632f2ff15d14610394575f80fd5b80630d3cf6fc116101985780630d3cf6fc1461025e578063134008d31461029157806313bc9f20146102a4578063150b7a02146102c3575f80fd5b806301d5062a146101c857806301ffc9a7146101e957806307bd02651461021d575f80fd5b366101c457005b5f80fd5b3480156101d3575f80fd5b506101e76101e2366004611bf6565b6106c4565b005b3480156101f4575f80fd5b50610208610203366004611c65565b610757565b60405190151581526020015b60405180910390f35b348015610228575f80fd5b506102507fd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e6381565b604051908152602001610214565b348015610269575f80fd5b506102507f5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca581565b6101e761029f366004611ca4565b6107b2565b3480156102af575f80fd5b506102086102be366004611d0b565b6108a7565b3480156102ce575f80fd5b506103066102dd366004611e28565b7f150b7a0200000000000000000000000000000000000000000000000000000000949350505050565b6040517fffffffff000000000000000000000000000000000000000000000000000000009091168152602001610214565b348015610342575f80fd5b50610250610351366004611d0b565b5f9081526020819052604090206001015490565b348015610370575f80fd5b5061020861037f366004611d0b565b5f908152600160208190526040909120541490565b34801561039f575f80fd5b506101e76103ae366004611e8c565b6108cc565b3480156103be575f80fd5b506102086103cd366004611d0b565b6108f5565b3480156103dd575f80fd5b506101e76103ec366004611e8c565b61090d565b3480156103fc575f80fd5b506104247f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610214565b348015610454575f80fd5b50610208610463366004611d0b565b6109c5565b348015610473575f80fd5b506101e7610482366004611d0b565b6109da565b348015610492575f80fd5b506102506104a1366004611ca4565b610aaa565b3480156104b1575f80fd5b506101e76104c0366004611ef7565b610ae8565b3480156104d0575f80fd5b506102507fb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc181565b348015610503575f80fd5b50610208610512366004611e8c565b5f9182526020828152604080842073ffffffffffffffffffffffffffffffffffffffff93909316845291905290205460ff1690565b348015610552575f80fd5b506102505f81565b348015610565575f80fd5b506102507ffd643c72710c63c0180259aba6b2d05451e3591a24e58b62239378085726f78381565b348015610598575f80fd5b506102506105a7366004611fa0565b610d18565b3480156105b7575f80fd5b506103066105c63660046120be565b7fbc197c810000000000000000000000000000000000000000000000000000000095945050505050565b3480156105fb575f80fd5b506101e761060a366004611d0b565b610d5c565b34801561061a575f80fd5b50610250610629366004611d0b565b5f9081526001602052604090205490565b348015610645575f80fd5b506101e7610654366004611e8c565b610e56565b6101e7610667366004611fa0565b610e7a565b348015610677575f80fd5b50610306610686366004612161565b7ff23a6e610000000000000000000000000000000000000000000000000000000095945050505050565b3480156106bb575f80fd5b50610250611121565b7fb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc16106ee81611200565b5f6106fd898989898989610aaa565b9050610709818461120d565b5f817f4cf4410cc57040e44862ef0f45f3dd5a5e02db8eb8add648d4b0e236f1d07dca8b8b8b8b8b8a60405161074496959493929190612208565b60405180910390a3505050505050505050565b5f7fffffffff0000000000000000000000000000000000000000000000000000000082167f4e2312e00000000000000000000000000000000000000000000000000000000014806107ac57506107ac82611359565b92915050565b5f80527fdae2aa361dfd1ca020a396615627d436107c35eff9fe7738a3512819782d70696020527f5ba6852781629bcdcd4bdaa6de76d786f1c64b16acdac474e55bebc0ea157951547fd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e639060ff1661082e5761082e81336113ef565b5f61083d888888888888610aaa565b905061084981856114a6565b610855888888886115e2565b5f817fc2617efa69bab66782fa219543714338489c4e9e178271560a91b82c3f612b588a8a8a8a60405161088c9493929190612252565b60405180910390a361089d816116e2565b5050505050505050565b5f818152600160205260408120546001811180156108c55750428111155b9392505050565b5f828152602081905260409020600101546108e681611200565b6108f0838361178a565b505050565b5f8181526001602052604081205481905b1192915050565b73ffffffffffffffffffffffffffffffffffffffff811633146109b7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201527f20726f6c657320666f722073656c66000000000000000000000000000000000060648201526084015b60405180910390fd5b6109c18282611878565b5050565b5f818152600160208190526040822054610906565b333014610a69576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f54696d656c6f636b436f6e74726f6c6c65723a2063616c6c6572206d7573742060448201527f62652074696d656c6f636b00000000000000000000000000000000000000000060648201526084016109ae565b60025460408051918252602082018390527f11c24f4ead16507c69ac467fbd5e4eed5fb5c699626d2cc6d66421df253886d5910160405180910390a1600255565b5f868686868686604051602001610ac696959493929190612208565b6040516020818303038152906040528051906020012090509695505050505050565b7fb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1610b1281611200565b888714610ba1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f54696d656c6f636b436f6e74726f6c6c65723a206c656e677468206d69736d6160448201527f746368000000000000000000000000000000000000000000000000000000000060648201526084016109ae565b888514610c30576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f54696d656c6f636b436f6e74726f6c6c65723a206c656e677468206d69736d6160448201527f746368000000000000000000000000000000000000000000000000000000000060648201526084016109ae565b5f610c418b8b8b8b8b8b8b8b610d18565b9050610c4d818461120d565b5f5b8a811015610d0a5780827f4cf4410cc57040e44862ef0f45f3dd5a5e02db8eb8add648d4b0e236f1d07dca8e8e85818110610c8c57610c8c612291565b9050602002016020810190610ca191906122be565b8d8d86818110610cb357610cb3612291565b905060200201358c8c87818110610ccc57610ccc612291565b9050602002810190610cde91906122d7565b8c8b604051610cf296959493929190612208565b60405180910390a3610d0381612365565b9050610c4f565b505050505050505050505050565b5f8888888888888888604051602001610d38989796959493929190612447565b60405160208183030381529060405280519060200120905098975050505050505050565b7ffd643c72710c63c0180259aba6b2d05451e3591a24e58b62239378085726f783610d8681611200565b610d8f826109c5565b610e1b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603160248201527f54696d656c6f636b436f6e74726f6c6c65723a206f7065726174696f6e20636160448201527f6e6e6f742062652063616e63656c6c656400000000000000000000000000000060648201526084016109ae565b5f828152600160205260408082208290555183917fbaa1eb22f2a492ba1a5fea61b8df4d27c6c8b5f3971e63bb58fa14ff72eedb7091a25050565b5f82815260208190526040902060010154610e7081611200565b6108f08383611878565b5f80527fdae2aa361dfd1ca020a396615627d436107c35eff9fe7738a3512819782d70696020527f5ba6852781629bcdcd4bdaa6de76d786f1c64b16acdac474e55bebc0ea157951547fd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e639060ff16610ef657610ef681336113ef565b878614610f85576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f54696d656c6f636b436f6e74726f6c6c65723a206c656e677468206d69736d6160448201527f746368000000000000000000000000000000000000000000000000000000000060648201526084016109ae565b878414611014576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f54696d656c6f636b436f6e74726f6c6c65723a206c656e677468206d69736d6160448201527f746368000000000000000000000000000000000000000000000000000000000060648201526084016109ae565b5f6110258a8a8a8a8a8a8a8a610d18565b905061103181856114a6565b5f5b8981101561110b575f8b8b8381811061104e5761104e612291565b905060200201602081019061106391906122be565b90505f8a8a8481811061107857611078612291565b905060200201359050365f8a8a8681811061109557611095612291565b90506020028101906110a791906122d7565b915091506110b7848484846115e2565b84867fc2617efa69bab66782fa219543714338489c4e9e178271560a91b82c3f612b58868686866040516110ee9493929190612252565b60405180910390a3505050508061110490612365565b9050611033565b50611115816116e2565b50505050505050505050565b5f7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16158015906111ef57507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166315064c966040518163ffffffff1660e01b8152600401602060405180830381865afa1580156111cb573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111ef919061250c565b156111f957505f90565b5060025490565b61120a81336113ef565b50565b611216826108f5565b156112a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602f60248201527f54696d656c6f636b436f6e74726f6c6c65723a206f7065726174696f6e20616c60448201527f7265616479207363686564756c6564000000000000000000000000000000000060648201526084016109ae565b6112ab611121565b81101561133a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f54696d656c6f636b436f6e74726f6c6c65723a20696e73756666696369656e7460448201527f2064656c6179000000000000000000000000000000000000000000000000000060648201526084016109ae565b611344814261252b565b5f928352600160205260409092209190915550565b5f7fffffffff0000000000000000000000000000000000000000000000000000000082167f7965db0b0000000000000000000000000000000000000000000000000000000014806107ac57507f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316146107ac565b5f8281526020818152604080832073ffffffffffffffffffffffffffffffffffffffff8516845290915290205460ff166109c15761142c8161192d565b61143783602061194c565b604051602001611448929190612560565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152908290527f08c379a00000000000000000000000000000000000000000000000000000000082526109ae916004016125e0565b6114af826108a7565b61153b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f54696d656c6f636b436f6e74726f6c6c65723a206f7065726174696f6e20697360448201527f206e6f742072656164790000000000000000000000000000000000000000000060648201526084016109ae565b80158061155657505f81815260016020819052604090912054145b6109c1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f54696d656c6f636b436f6e74726f6c6c65723a206d697373696e67206465706560448201527f6e64656e6379000000000000000000000000000000000000000000000000000060648201526084016109ae565b5f8473ffffffffffffffffffffffffffffffffffffffff1684848460405161160b929190612630565b5f6040518083038185875af1925050503d805f8114611645576040519150601f19603f3d011682016040523d82523d5f602084013e61164a565b606091505b50509050806116db576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603360248201527f54696d656c6f636b436f6e74726f6c6c65723a20756e6465726c79696e67207460448201527f72616e73616374696f6e2072657665727465640000000000000000000000000060648201526084016109ae565b5050505050565b6116eb816108a7565b611777576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f54696d656c6f636b436f6e74726f6c6c65723a206f7065726174696f6e20697360448201527f206e6f742072656164790000000000000000000000000000000000000000000060648201526084016109ae565b5f90815260016020819052604090912055565b5f8281526020818152604080832073ffffffffffffffffffffffffffffffffffffffff8516845290915290205460ff166109c1575f8281526020818152604080832073ffffffffffffffffffffffffffffffffffffffff85168452909152902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905561181a3390565b73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b5f8281526020818152604080832073ffffffffffffffffffffffffffffffffffffffff8516845290915290205460ff16156109c1575f8281526020818152604080832073ffffffffffffffffffffffffffffffffffffffff8516808552925280832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b60606107ac73ffffffffffffffffffffffffffffffffffffffff831660145b60605f61195a83600261263f565b61196590600261252b565b67ffffffffffffffff81111561197d5761197d611d22565b6040519080825280601f01601f1916602001820160405280156119a7576020820181803683370190505b5090507f3000000000000000000000000000000000000000000000000000000000000000815f815181106119dd576119dd612291565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053507f780000000000000000000000000000000000000000000000000000000000000081600181518110611a3f57611a3f612291565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a9053505f611a7984600261263f565b611a8490600161252b565b90505b6001811115611b20577f303132333435363738396162636465660000000000000000000000000000000085600f1660108110611ac557611ac5612291565b1a60f81b828281518110611adb57611adb612291565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690815f1a90535060049490941c93611b1981612656565b9050611a87565b5083156108c5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e7460448201526064016109ae565b803573ffffffffffffffffffffffffffffffffffffffff81168114611bac575f80fd5b919050565b5f8083601f840112611bc1575f80fd5b50813567ffffffffffffffff811115611bd8575f80fd5b602083019150836020828501011115611bef575f80fd5b9250929050565b5f805f805f805f60c0888a031215611c0c575f80fd5b611c1588611b89565b965060208801359550604088013567ffffffffffffffff811115611c37575f80fd5b611c438a828b01611bb1565b989b979a50986060810135976080820135975060a09091013595509350505050565b5f60208284031215611c75575f80fd5b81357fffffffff00000000000000000000000000000000000000000000000000000000811681146108c5575f80fd5b5f805f805f8060a08789031215611cb9575f80fd5b611cc287611b89565b955060208701359450604087013567ffffffffffffffff811115611ce4575f80fd5b611cf089828a01611bb1565b979a9699509760608101359660809091013595509350505050565b5f60208284031215611d1b575f80fd5b5035919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715611d9657611d96611d22565b604052919050565b5f82601f830112611dad575f80fd5b813567ffffffffffffffff811115611dc757611dc7611d22565b611df860207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601611d4f565b818152846020838601011115611e0c575f80fd5b816020850160208301375f918101602001919091529392505050565b5f805f8060808587031215611e3b575f80fd5b611e4485611b89565b9350611e5260208601611b89565b925060408501359150606085013567ffffffffffffffff811115611e74575f80fd5b611e8087828801611d9e565b91505092959194509250565b5f8060408385031215611e9d575f80fd5b82359150611ead60208401611b89565b90509250929050565b5f8083601f840112611ec6575f80fd5b50813567ffffffffffffffff811115611edd575f80fd5b6020830191508360208260051b8501011115611bef575f80fd5b5f805f805f805f805f60c08a8c031215611f0f575f80fd5b893567ffffffffffffffff80821115611f26575f80fd5b611f328d838e01611eb6565b909b50995060208c0135915080821115611f4a575f80fd5b611f568d838e01611eb6565b909950975060408c0135915080821115611f6e575f80fd5b50611f7b8c828d01611eb6565b9a9d999c50979a969997986060880135976080810135975060a0013595509350505050565b5f805f805f805f8060a0898b031215611fb7575f80fd5b883567ffffffffffffffff80821115611fce575f80fd5b611fda8c838d01611eb6565b909a50985060208b0135915080821115611ff2575f80fd5b611ffe8c838d01611eb6565b909850965060408b0135915080821115612016575f80fd5b506120238b828c01611eb6565b999c989b509699959896976060870135966080013595509350505050565b5f82601f830112612050575f80fd5b8135602067ffffffffffffffff82111561206c5761206c611d22565b8160051b61207b828201611d4f565b9283528481018201928281019087851115612094575f80fd5b83870192505b848310156120b35782358252918301919083019061209a565b979650505050505050565b5f805f805f60a086880312156120d2575f80fd5b6120db86611b89565b94506120e960208701611b89565b9350604086013567ffffffffffffffff80821115612105575f80fd5b61211189838a01612041565b94506060880135915080821115612126575f80fd5b61213289838a01612041565b93506080880135915080821115612147575f80fd5b5061215488828901611d9e565b9150509295509295909350565b5f805f805f60a08688031215612175575f80fd5b61217e86611b89565b945061218c60208701611b89565b93506040860135925060608601359150608086013567ffffffffffffffff8111156121b5575f80fd5b61215488828901611d9e565b81835281816020850137505f602082840101525f60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b73ffffffffffffffffffffffffffffffffffffffff8716815285602082015260a060408201525f61223d60a0830186886121c1565b60608301949094525060800152949350505050565b73ffffffffffffffffffffffffffffffffffffffff85168152836020820152606060408201525f6122876060830184866121c1565b9695505050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f602082840312156122ce575f80fd5b6108c582611b89565b5f8083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261230a575f80fd5b83018035915067ffffffffffffffff821115612324575f80fd5b602001915036819003821315611bef575f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361239557612395612338565b5060010190565b8183525f6020808501808196508560051b81019150845f5b8781101561243a57828403895281357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18836030181126123f2575f80fd5b8701858101903567ffffffffffffffff81111561240d575f80fd5b80360382131561241b575f80fd5b6124268682846121c1565b9a87019a95505050908401906001016123b4565b5091979650505050505050565b60a080825281018890525f8960c08301825b8b8110156124945773ffffffffffffffffffffffffffffffffffffffff61247f84611b89565b16825260209283019290910190600101612459565b5083810360208501528881527f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8911156124cc575f80fd5b8860051b9150818a602083013701828103602090810160408501526124f4908201878961239c565b60608401959095525050608001529695505050505050565b5f6020828403121561251c575f80fd5b815180151581146108c5575f80fd5b808201808211156107ac576107ac612338565b5f5b83811015612558578181015183820152602001612540565b50505f910152565b7f416363657373436f6e74726f6c3a206163636f756e742000000000000000000081525f835161259781601785016020880161253e565b7f206973206d697373696e6720726f6c652000000000000000000000000000000060179184019182015283516125d481602884016020880161253e565b01602801949350505050565b602081525f82518060208401526125fe81604085016020870161253e565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b818382375f9101908152919050565b80820281158282048414176107ac576107ac612338565b5f8161266457612664612338565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff019056fea2646970667358221220d904e0b10230579952f8427a107aa4349f9a1f5799108d399b11e28b578463e464736f6c63430008140033", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000e10", + "0x6004ee6a16227fd0e871b5f914d8433417e16ad83654d913c1ba69cc35c2132e": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xf5f0a218cd667c05e379a91ca023db2743ccc055fbfe1d11963ca6b052736ab1": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x64494413541ff93b31aa309254e3fed72a7456e9845988b915b4c7a7ceba8814": "0x5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca5", + "0x9db27d76cb60f110b9a2cbcd55f6b5d54993c33cefeaa1979d0bdfd0218c8e9e": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x3412d5605ac6cd444957cedb533e5dacad6378b4bc819ebe3652188a665066d6": "0x5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca5", + "0x3b674ad523dadae6f60882426711ee3b02fd243e4a8e114dfdd20da2bcc3899d": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xdae2aa361dfd1ca020a396615627d436107c35eff9fe7738a3512819782d706a": "0x5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca5", + "0xf825292247308a48f73de280a1dc14cbea6657baa530dcfbfa562b981b9cda2f": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xc3ad33e20b0c56a223ad5104fff154aa010f8715b9c981fd38fdc60a4d1a52fc": "0x5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca5" + } + }, + { + "accountName": "keyless Deployer", + "balance": "0", + "nonce": "1", + "address": "0x6a8F29BB59c1cf00b263691500AC93c867DAD915" + }, + { + "accountName": "deployer", + "balance": "0", + "nonce": "8", + "address": "0x56b9Eaf5D19639ACc16C6373C66e5a1F61CF29b6" + } + ], + "l1Config": { + "chainId": 11155111, + "polygonZkEVMAddress": "0xCE5622f775cF645C179B7Fe189D6a87307A11e05", + "maticTokenAddress": "0x7fee073b978A6AAe2deF14c9A2BB73BD1E39e29c", + "polygonZkEVMGlobalExitRootAddress": "0xD1709c00280E57Cb88CcA035f5f3a5f7EFdC2dfC" + }, + "genesisBlockNumber": 4482328 +} \ No newline at end of file diff --git a/cspell.json b/cspell.json index 503b81b9997d..de8d03da917f 100644 --- a/cspell.json +++ b/cspell.json @@ -546,6 +546,7 @@ "ziczr", "zindex", "zipcode", + "zkatana", "zkbob", "zkevm", "erts", From 262bdd173a4de4fc1ee165e7b8c5d050780147e9 Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:23:44 +0300 Subject: [PATCH 407/607] Add fallback effective gas price calculation (#9305) * Add fallback effective gas price calculation * Process review comments --- CHANGELOG.md | 1 + .../views/api/v2/transaction_view.ex | 17 +-- .../block_scout_web/views/transaction_view.ex | 4 +- .../api/v2/address_controller_test.exs | 10 +- .../lib/explorer/account/notifier/summary.ex | 4 +- apps/explorer/lib/explorer/chain.ex | 52 --------- .../address_token_transfer_csv_exporter.ex | 4 +- .../address_transaction_csv_exporter.ex | 4 +- .../lib/explorer/chain/transaction.ex | 106 +++++++++++++++++- .../chain/transaction/state_change.ex | 4 +- .../explorer/account/notifier/notify_test.exs | 4 +- .../account/notifier/summary_test.exs | 12 +- apps/explorer/test/explorer/chain_test.exs | 21 +++- 13 files changed, 149 insertions(+), 94 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ec0281d001f..34e267ef3d75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - [#9346](https://github.com/blockscout/blockscout/pull/9346) - Process integer balance in genesis.json - [#9317](https://github.com/blockscout/blockscout/pull/9317) - Include null gas price txs in fee calculations - [#9315](https://github.com/blockscout/blockscout/pull/9315) - Fix manual uncle reward calculation +- [#9305](https://github.com/blockscout/blockscout/pull/9305) - Add effective gas price calculation as fallback - [#9300](https://github.com/blockscout/blockscout/pull/9300) - Fix read contract bug ### Chore diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index a7538a3f3675..5eea21c5a697 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -367,7 +367,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do max_priority_fee_per_gas = transaction.max_priority_fee_per_gas max_fee_per_gas = transaction.max_fee_per_gas - priority_fee_per_gas = priority_fee_per_gas(max_priority_fee_per_gas, base_fee_per_gas, max_fee_per_gas) + priority_fee_per_gas = Transaction.priority_fee_per_gas(max_priority_fee_per_gas, base_fee_per_gas, max_fee_per_gas) burnt_fees = burnt_fees(transaction, max_fee_per_gas, base_fee_per_gas) @@ -410,8 +410,8 @@ defmodule BlockScoutWeb.API.V2.TransactionView do "confirmations" => transaction.block |> Chain.confirmations(block_height: block_height) |> format_confirmations(), "confirmation_duration" => processing_time_duration(transaction), "value" => transaction.value, - "fee" => transaction |> Chain.fee(:wei) |> format_fee(), - "gas_price" => transaction.gas_price, + "fee" => transaction |> Transaction.fee(:wei) |> format_fee(), + "gas_price" => transaction.gas_price || Transaction.effective_gas_price(transaction), "type" => transaction.type, "gas_used" => transaction.gas_used, "gas_limit" => transaction.gas, @@ -543,7 +543,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do "gas_price" => transaction.wrapped_gas_price, "fee" => format_fee( - Chain.fee( + Transaction.fee( %Transaction{gas: transaction.wrapped_gas, gas_price: transaction.wrapped_gas_price, gas_used: nil}, :wei ) @@ -589,15 +589,6 @@ defmodule BlockScoutWeb.API.V2.TransactionView do render("transaction_actions.json", %{actions: actions}) end - defp priority_fee_per_gas(max_priority_fee_per_gas, base_fee_per_gas, max_fee_per_gas) do - if is_nil(max_priority_fee_per_gas) or is_nil(base_fee_per_gas), - do: nil, - else: - Enum.min_by([max_priority_fee_per_gas, Wei.sub(max_fee_per_gas, base_fee_per_gas)], fn x -> - Wei.to(x, :wei) - end) - end - defp burnt_fees(transaction, max_fee_per_gas, base_fee_per_gas) do if !is_nil(max_fee_per_gas) and !is_nil(transaction.gas_used) and !is_nil(base_fee_per_gas) do if Decimal.compare(max_fee_per_gas.value, 0) == :eq do diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex index ae493f391341..dac7fc0bd57d 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex @@ -310,7 +310,7 @@ defmodule BlockScoutWeb.TransactionView do def contract_creation?(_), do: false def fee(%Transaction{} = transaction) do - {_, value} = Chain.fee(transaction, :wei) + {_, value} = Transaction.fee(transaction, :wei) value end @@ -320,7 +320,7 @@ defmodule BlockScoutWeb.TransactionView do def formatted_fee(%Transaction{} = transaction, opts) do transaction - |> Chain.fee(:wei) + |> Transaction.fee(:wei) |> fee_to_denomination(opts) |> case do {:actual, value} -> value diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index 34829785f55e..56faa2c78ed0 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -507,7 +507,10 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do txs = (txs_from ++ txs_to) |> Enum.sort( - &(Decimal.compare(&1 |> Chain.fee(:wei) |> elem(1), &2 |> Chain.fee(:wei) |> elem(1)) in [:eq, :lt]) + &(Decimal.compare(&1 |> Transaction.fee(:wei) |> elem(1), &2 |> Transaction.fee(:wei) |> elem(1)) in [ + :eq, + :lt + ]) ) request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "fee", "order" => "asc"}) @@ -543,7 +546,10 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do txs = (txs_from ++ txs_to) |> Enum.sort( - &(Decimal.compare(&1 |> Chain.fee(:wei) |> elem(1), &2 |> Chain.fee(:wei) |> elem(1)) in [:eq, :gt]) + &(Decimal.compare(&1 |> Transaction.fee(:wei) |> elem(1), &2 |> Transaction.fee(:wei) |> elem(1)) in [ + :eq, + :gt + ]) ) request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "fee", "order" => "desc"}) diff --git a/apps/explorer/lib/explorer/account/notifier/summary.ex b/apps/explorer/lib/explorer/account/notifier/summary.ex index 09f370078c20..c38e40107c52 100644 --- a/apps/explorer/lib/explorer/account/notifier/summary.ex +++ b/apps/explorer/lib/explorer/account/notifier/summary.ex @@ -8,7 +8,7 @@ defmodule Explorer.Account.Notifier.Summary do alias Explorer alias Explorer.Account.Notifier.Summary alias Explorer.{Chain, Repo} - alias Explorer.Chain.Wei + alias Explorer.Chain.{Transaction, Wei} defstruct [ :transaction_hash, @@ -205,7 +205,7 @@ defmodule Explorer.Account.Notifier.Summary do def type(%Chain.InternalTransaction{}), do: :coin def fee(%Chain.Transaction{} = transaction) do - {_, fee} = Chain.fee(transaction, :gwei) + {_, fee} = Transaction.fee(transaction, :gwei) fee end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 5138b5d8fdc5..423ee0dc8761 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -826,58 +826,6 @@ defmodule Explorer.Chain do Data.to_iodata(data) end - @doc """ - The fee a `transaction` paid for the `t:Explorer.Transaction.t/0` `gas` - - If the transaction is pending, then the fee will be a range of `unit` - - iex> Explorer.Chain.fee( - ...> %Explorer.Chain.Transaction{ - ...> gas: Decimal.new(3), - ...> gas_price: %Explorer.Chain.Wei{value: Decimal.new(2)}, - ...> gas_used: nil - ...> }, - ...> :wei - ...> ) - {:maximum, Decimal.new(6)} - - If the transaction has been confirmed in block, then the fee will be the actual fee paid in `unit` for the `gas_used` - in the `transaction`. - - iex> Explorer.Chain.fee( - ...> %Explorer.Chain.Transaction{ - ...> gas: Decimal.new(3), - ...> gas_price: %Explorer.Chain.Wei{value: Decimal.new(2)}, - ...> gas_used: Decimal.new(2) - ...> }, - ...> :wei - ...> ) - {:actual, Decimal.new(4)} - - """ - @spec fee(Transaction.t(), :ether | :gwei | :wei) :: {:maximum, Decimal.t()} | {:actual, Decimal.t() | nil} - def fee(%Transaction{gas: _gas, gas_price: nil, gas_used: nil}, _unit), do: {:maximum, nil} - - def fee(%Transaction{gas: gas, gas_price: gas_price, gas_used: nil}, unit) do - fee = - gas_price - |> Wei.to(unit) - |> Decimal.mult(gas) - - {:maximum, fee} - end - - def fee(%Transaction{gas_price: nil, gas_used: _gas_used}, _unit), do: {:actual, nil} - - def fee(%Transaction{gas_price: gas_price, gas_used: gas_used}, unit) do - fee = - gas_price - |> Wei.to(unit) - |> Decimal.mult(gas_used) - - {:actual, fee} - end - @doc """ Checks to see if the chain is down indexing based on the transaction from the oldest block and the pending operation diff --git a/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex index 5a44e272a726..93ff4305113d 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex @@ -11,7 +11,7 @@ defmodule Explorer.Chain.CSVExport.AddressTokenTransferCsvExporter do where: 3 ] - alias Explorer.{Chain, PagingOptions, Repo} + alias Explorer.{PagingOptions, Repo} alias Explorer.Chain.{Address, DenormalizationHelper, Hash, TokenTransfer, Transaction} alias Explorer.Chain.CSVExport.Helper @@ -92,7 +92,7 @@ defmodule Explorer.Chain.CSVExport.AddressTokenTransferCsvExporter do defp fee(transaction) do transaction - |> Chain.fee(:wei) + |> Transaction.fee(:wei) |> case do {:actual, value} -> value {:maximum, value} -> "Max of #{value}" diff --git a/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex index 52f7298c1551..f7263c89c0e7 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex @@ -8,7 +8,7 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do from: 2 ] - alias Explorer.{Chain, Market, PagingOptions, Repo} + alias Explorer.{Market, PagingOptions, Repo} alias Explorer.Market.MarketHistory alias Explorer.Chain.{Address, DenormalizationHelper, Hash, Transaction, Wei} alias Explorer.Chain.CSVExport.Helper @@ -105,7 +105,7 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do defp fee(transaction) do transaction - |> Chain.fee(:wei) + |> Transaction.fee(:wei) |> case do {:actual, value} -> value {:maximum, value} -> "Max of #{value}" diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 1d6e636c3d70..d5341e33b4fe 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -1467,8 +1467,8 @@ defmodule Explorer.Chain.Transaction do :asc_nulls_first -> Decimal.new("inf") end - a_fee = a |> Chain.fee(:wei) |> elem(1) || nil_case - b_fee = b |> Chain.fee(:wei) |> elem(1) || nil_case + a_fee = a |> fee(:wei) |> elem(1) || nil_case + b_fee = b |> fee(:wei) |> elem(1) || nil_case case Decimal.compare(a_fee, b_fee) do :eq -> compare_default_sorting(a, b) @@ -1657,7 +1657,7 @@ defmodule Explorer.Chain.Transaction do %__MODULE__{block_number: block_number, index: index, inserted_at: inserted_at, hash: hash, value: value} = tx ) do %{ - "fee" => tx |> Chain.fee(:wei) |> elem(1), + "fee" => tx |> fee(:wei) |> elem(1), "value" => value, "block_number" => block_number, "index" => index, @@ -1709,4 +1709,104 @@ defmodule Explorer.Chain.Transaction do String.downcase(sanitized) end end + + @doc """ + The fee a `transaction` paid for the `t:Explorer.Transaction.t/0` `gas` + + If the transaction is pending, then the fee will be a range of `unit` + + iex> Explorer.Chain.Transaction.fee( + ...> %Explorer.Chain.Transaction{ + ...> gas: Decimal.new(3), + ...> gas_price: %Explorer.Chain.Wei{value: Decimal.new(2)}, + ...> gas_used: nil + ...> }, + ...> :wei + ...> ) + {:maximum, Decimal.new(6)} + + If the transaction has been confirmed in block, then the fee will be the actual fee paid in `unit` for the `gas_used` + in the `transaction`. + + iex> Explorer.Chain.Transaction.fee( + ...> %Explorer.Chain.Transaction{ + ...> gas: Decimal.new(3), + ...> gas_price: %Explorer.Chain.Wei{value: Decimal.new(2)}, + ...> gas_used: Decimal.new(2) + ...> }, + ...> :wei + ...> ) + {:actual, Decimal.new(4)} + + """ + @spec fee(Transaction.t(), :ether | :gwei | :wei) :: {:maximum, Decimal.t()} | {:actual, Decimal.t() | nil} + def fee(%Transaction{gas: gas, gas_price: nil, gas_used: nil} = transaction, unit) do + gas_price = effective_gas_price(transaction) + + {:maximum, gas_price && gas_price |> Wei.to(unit) |> Decimal.mult(gas)} + end + + def fee(%Transaction{gas: gas, gas_price: gas_price, gas_used: nil}, unit) do + fee = + gas_price + |> Wei.to(unit) + |> Decimal.mult(gas) + + {:maximum, fee} + end + + def fee(%Transaction{gas_price: nil, gas_used: gas_used} = transaction, unit) do + gas_price = effective_gas_price(transaction) + + {:actual, + gas_price && + gas_price + |> Wei.to(unit) + |> Decimal.mult(gas_used)} + end + + def fee(%Transaction{gas_price: gas_price, gas_used: gas_used}, unit) do + fee = + gas_price + |> Wei.to(unit) + |> Decimal.mult(gas_used) + + {:actual, fee} + end + + @doc """ + Calculates effective gas price for transaction with type 2 (EIP-1559) + + `effective_gas_price = priority_fee_per_gas + block.base_fee_per_gas` + """ + @spec effective_gas_price(Transaction.t()) :: Wei.t() | nil + + def effective_gas_price(%Transaction{block: nil}), do: nil + def effective_gas_price(%Transaction{block: %NotLoaded{}}), do: nil + + def effective_gas_price(%Transaction{} = transaction) do + base_fee_per_gas = transaction.block.base_fee_per_gas + max_priority_fee_per_gas = transaction.max_priority_fee_per_gas + max_fee_per_gas = transaction.max_fee_per_gas + + priority_fee_per_gas = priority_fee_per_gas(max_priority_fee_per_gas, base_fee_per_gas, max_fee_per_gas) + + priority_fee_per_gas && Wei.sum(priority_fee_per_gas, base_fee_per_gas) + end + + @doc """ + Calculates priority fee per gas for transaction with type 2 (EIP-1559) + + `priority_fee_per_gas = min(transaction.max_priority_fee_per_gas, transaction.max_fee_per_gas - block.base_fee_per_gas)` + """ + @spec priority_fee_per_gas(Wei.t() | nil, Wei.t() | nil, Wei.t() | nil) :: Wei.t() | nil + def priority_fee_per_gas(max_priority_fee_per_gas, base_fee_per_gas, max_fee_per_gas) do + if is_nil(max_priority_fee_per_gas) or is_nil(base_fee_per_gas), + do: nil, + else: + max_priority_fee_per_gas + |> Wei.to(:wei) + |> Decimal.min(max_fee_per_gas |> Wei.sub(base_fee_per_gas) |> Wei.to(:wei)) + |> Wei.from(:wei) + end end diff --git a/apps/explorer/lib/explorer/chain/transaction/state_change.ex b/apps/explorer/lib/explorer/chain/transaction/state_change.ex index c0b70f23c493..3d5f0f7f8b5e 100644 --- a/apps/explorer/lib/explorer/chain/transaction/state_change.ex +++ b/apps/explorer/lib/explorer/chain/transaction/state_change.ex @@ -4,7 +4,7 @@ defmodule Explorer.Chain.Transaction.StateChange do """ alias Explorer.Chain - alias Explorer.Chain.{Hash, TokenTransfer, Wei} + alias Explorer.Chain.{Hash, TokenTransfer, Transaction, Wei} alias Explorer.Chain.Transaction.StateChange defstruct [:coin_or_token_transfers, :address, :token_id, :balance_before, :balance_after, :balance_diff, :miner?] @@ -140,7 +140,7 @@ defmodule Explorer.Chain.Transaction.StateChange do end def from_loss(tx) do - {_, fee} = Chain.fee(tx, :wei) + {_, fee} = Transaction.fee(tx, :wei) if error?(tx) do %Wei{value: fee} diff --git a/apps/explorer/test/explorer/account/notifier/notify_test.exs b/apps/explorer/test/explorer/account/notifier/notify_test.exs index 860b569f2a15..25a7fd34a3ee 100644 --- a/apps/explorer/test/explorer/account/notifier/notify_test.exs +++ b/apps/explorer/test/explorer/account/notifier/notify_test.exs @@ -68,7 +68,7 @@ defmodule Explorer.Account.Notifier.NotifyTest do hash: _tx_hash } = with_block(insert(:transaction, to_address: %Chain.Address{hash: address_hash})) - {_, fee} = Chain.fee(tx, :gwei) + {_, fee} = Transaction.fee(tx, :gwei) amount = Wei.to(tx.value, :ether) notify = Notify.call([tx]) @@ -107,7 +107,7 @@ defmodule Explorer.Account.Notifier.NotifyTest do hash: _tx_hash } = with_block(insert(:transaction, to_address: %Chain.Address{hash: address_hash})) - {_, fee} = Chain.fee(tx, :gwei) + {_, fee} = Transaction.fee(tx, :gwei) amount = Wei.to(tx.value, :ether) notify = Notify.call([tx]) diff --git a/apps/explorer/test/explorer/account/notifier/summary_test.exs b/apps/explorer/test/explorer/account/notifier/summary_test.exs index 2f899d8e6ff8..54fc0da10925 100644 --- a/apps/explorer/test/explorer/account/notifier/summary_test.exs +++ b/apps/explorer/test/explorer/account/notifier/summary_test.exs @@ -18,7 +18,7 @@ defmodule Explorer.Account.Notifier.SummaryTest do hash: tx_hash } = with_block(insert(:transaction)) - {_, fee} = Chain.fee(tx, :gwei) + {_, fee} = Transaction.fee(tx, :gwei) amount = Wei.to(tx.value, :ether) assert Summary.process(tx) == [ @@ -65,7 +65,7 @@ defmodule Explorer.Account.Notifier.SummaryTest do |> with_contract_creation(contract_address) |> with_block(block) - {_, fee} = Chain.fee(tx, :gwei) + {_, fee} = Transaction.fee(tx, :gwei) amount = Wei.to(tx.value, :ether) assert Summary.process(tx) == [ @@ -107,7 +107,7 @@ defmodule Explorer.Account.Notifier.SummaryTest do :token ]) - {_, fee} = Chain.fee(tx, :gwei) + {_, fee} = Transaction.fee(tx, :gwei) token_decimals = Decimal.to_integer(token.decimals) @@ -159,7 +159,7 @@ defmodule Explorer.Account.Notifier.SummaryTest do :token ]) - {_, fee} = Chain.fee(tx, :gwei) + {_, fee} = Transaction.fee(tx, :gwei) assert Summary.process(transfer) == [ %Summary{ @@ -205,7 +205,7 @@ defmodule Explorer.Account.Notifier.SummaryTest do :token ]) - {_, fee} = Chain.fee(tx, :gwei) + {_, fee} = Transaction.fee(tx, :gwei) assert Summary.process(transfer) == [ %Summary{ @@ -251,7 +251,7 @@ defmodule Explorer.Account.Notifier.SummaryTest do :token ]) - {_, fee} = Chain.fee(tx, :gwei) + {_, fee} = Transaction.fee(tx, :gwei) assert Summary.process(transfer) == [ %Summary{ diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index be01544eeecc..d84a614cbf60 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -643,22 +643,31 @@ defmodule Explorer.ChainTest do describe "fee/2" do test "without receipt with :wei unit" do - assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, gas_used: nil}, :wei) == + assert Transaction.fee( + %Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, gas_used: nil}, + :wei + ) == {:maximum, Decimal.new(6)} end test "without receipt with :gwei unit" do - assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, gas_used: nil}, :gwei) == + assert Transaction.fee( + %Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, gas_used: nil}, + :gwei + ) == {:maximum, Decimal.new("6e-9")} end test "without receipt with :ether unit" do - assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, gas_used: nil}, :ether) == + assert Transaction.fee( + %Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, gas_used: nil}, + :ether + ) == {:maximum, Decimal.new("6e-18")} end test "with receipt with :wei unit" do - assert Chain.fee( + assert Transaction.fee( %Transaction{ gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, @@ -669,7 +678,7 @@ defmodule Explorer.ChainTest do end test "with receipt with :gwei unit" do - assert Chain.fee( + assert Transaction.fee( %Transaction{ gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, @@ -680,7 +689,7 @@ defmodule Explorer.ChainTest do end test "with receipt with :ether unit" do - assert Chain.fee( + assert Transaction.fee( %Transaction{ gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, From dda584eff2139c9b0fe23355a54b524bb81f8ff6 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Tue, 13 Feb 2024 14:25:01 +0300 Subject: [PATCH 408/607] Noves.fi: add proxy endpoint for describeTxs endpoint (#9351) * Add describeTxs endpoint * Support input tx hashes through comma-separated * Update apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex Co-authored-by: nikitosing <32202610+nikitosing@users.noreply.github.com> * Split noves_fi_api_request into 2 clauses --------- Co-authored-by: nikitosing <32202610+nikitosing@users.noreply.github.com> --- CHANGELOG.md | 3 +- .../lib/block_scout_web/api_router.ex | 1 + ...fi_conroller.ex => noves_fi_controller.ex} | 17 +++++++- .../third_party_integrations/noves_fi.ex | 39 ++++++++++++++++++- 4 files changed, 56 insertions(+), 4 deletions(-) rename apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/{noves_fi_conroller.ex => noves_fi_controller.ex} (81%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34e267ef3d75..1332e07447c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,9 @@ ### Features -- [#9168](https://github.com/blockscout/blockscout/pull/9168) - Support EIP4844 blobs indexing & API +- [#9351](https://github.com/blockscout/blockscout/pull/9351) - Noves.fi: add proxy endpoint for describeTxs endpoint - [#9202](https://github.com/blockscout/blockscout/pull/9202) - Add base and priority fee to gas oracle response +- [#9168](https://github.com/blockscout/blockscout/pull/9168) - Support EIP4844 blobs indexing & API ### Fixes diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 3d484030c1fe..ac6f2d098dfe 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -324,6 +324,7 @@ defmodule BlockScoutWeb.ApiRouter do get("/transactions/:transaction_hash_param", V2.Proxy.NovesFiController, :transaction) get("/transactions/:transaction_hash_param/describe", V2.Proxy.NovesFiController, :describe_transaction) get("/addresses/:address_hash_param/transactions", V2.Proxy.NovesFiController, :address_transactions) + get("/transactions", V2.Proxy.NovesFiController, :describe_transactions) end scope "/account-abstraction" do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_conroller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_controller.ex similarity index 81% rename from apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_conroller.ex rename to apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_controller.ex index 7c0e6a327714..e68c260f344a 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_conroller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_controller.ex @@ -45,7 +45,7 @@ defmodule BlockScoutWeb.API.V2.Proxy.NovesFiController do end @doc """ - Function to handle GET requests to `/api/v2/proxy/noves-fi/transactions/:transaction_hash_param/describe` endpoint. + Function to handle GET requests to `/api/v2/proxy/noves-fi/transactions/:transaction_hash_param/transactions` endpoint. """ @spec address_transactions(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} def address_transactions(conn, %{"address_hash_param" => address_hash_string} = params) do @@ -58,4 +58,19 @@ defmodule BlockScoutWeb.API.V2.Proxy.NovesFiController do |> json(response) end end + + @doc """ + Function to handle GET requests to `/api/v2/proxy/noves-fi/transactions` endpoint. + """ + @spec describe_transactions(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def describe_transactions(conn, _) do + url = NovesFi.describe_txs_url() + + with {response, status} <- NovesFi.noves_fi_api_request(url, conn, :post_transactions), + {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do + conn + |> put_status(status) + |> json(response) + end + end end diff --git a/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex b/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex index dcf0e473b24e..39dad308954c 100644 --- a/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex +++ b/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex @@ -11,9 +11,36 @@ defmodule Explorer.ThirdPartyIntegrations.NovesFi do @doc """ Proxy request to noves.fi API endpoints """ - @spec noves_fi_api_request(String.t(), Plug.Conn.t()) :: {any(), integer()} - def noves_fi_api_request(url, conn) do + @spec noves_fi_api_request(String.t(), Plug.Conn.t(), :get | :post_transactions) :: {any(), integer()} + def noves_fi_api_request(url, conn, method \\ :get) + + def noves_fi_api_request(url, conn, :post_transactions) do + headers = [{"apiKey", api_key()}, {"Content-Type", "application/json"}, {"accept", "text/plain"}] + + hashes = + conn.query_params + |> Map.get("hashes") + |> (&if(is_map(&1), + do: Map.values(&1), + else: String.split(&1, ",") + )).() + + prepared_params = + conn.query_params + |> Map.drop(["hashes"]) + + case HTTPoison.post(url, Jason.encode!(hashes), headers, recv_timeout: @recv_timeout, params: prepared_params) do + {:ok, %HTTPoison.Response{status_code: status, body: body}} -> + {Helper.decode_json(body), status} + + _ -> + {nil, 500} + end + end + + def noves_fi_api_request(url, conn, :get) do headers = [{"apiKey", api_key()}] + url_with_params = url <> "?" <> conn.query_string case HTTPoison.get(url_with_params, headers, recv_timeout: @recv_timeout) do @@ -41,6 +68,14 @@ defmodule Explorer.ThirdPartyIntegrations.NovesFi do "#{base_url()}/evm/#{chain_name()}/describeTx/#{transaction_hash_string}" end + @doc """ + Noves.fi /evm/{chain}/describeTxs endpoint + """ + @spec describe_txs_url() :: String.t() + def describe_txs_url do + "#{base_url()}/evm/#{chain_name()}/describeTxs" + end + @doc """ Noves.fi /evm/{chain}/txs/{accountAddress} endpoint """ From 830625eca897b2ac1003c9fdd2bd2bcd9d826e85 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Wed, 7 Feb 2024 20:13:55 +0400 Subject: [PATCH 409/607] Remove ERC-1155 logs params from coin balances params --- CHANGELOG.md | 1 + apps/indexer/lib/indexer/transform/address_coin_balances.ex | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1332e07447c9..0fbed48e90f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Fixes +- [#9356](https://github.com/blockscout/blockscout/pull/9356) - Remove ERC-1155 logs params from coin balances params - [#9346](https://github.com/blockscout/blockscout/pull/9346) - Process integer balance in genesis.json - [#9317](https://github.com/blockscout/blockscout/pull/9317) - Include null gas price txs in fee calculations - [#9315](https://github.com/blockscout/blockscout/pull/9315) - Fix manual uncle reward calculation diff --git a/apps/indexer/lib/indexer/transform/address_coin_balances.ex b/apps/indexer/lib/indexer/transform/address_coin_balances.ex index 4c7a20ca66ea..9f1935da4638 100644 --- a/apps/indexer/lib/indexer/transform/address_coin_balances.ex +++ b/apps/indexer/lib/indexer/transform/address_coin_balances.ex @@ -32,7 +32,11 @@ defmodule Indexer.Transform.AddressCoinBalances do defp reducer({:logs_params, logs_params}, acc) when is_list(logs_params) do # a log MUST have address_hash and block_number logs_params - |> Enum.reject(&(&1.first_topic == TokenTransfer.constant())) + |> Enum.reject( + &(&1.first_topic == TokenTransfer.constant() or + &1.first_topic == TokenTransfer.erc1155_single_transfer_signature() or + &1.first_topic == TokenTransfer.erc1155_batch_transfer_signature()) + ) |> Enum.into(acc, fn %{address_hash: address_hash, block_number: block_number} when is_binary(address_hash) and is_integer(block_number) -> From ae1915482a46b606e9a8bf354af3c77886527441 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Mon, 12 Feb 2024 13:44:55 +0400 Subject: [PATCH 410/607] Filter non-traceable transactions for zetachain --- CHANGELOG.md | 1 + apps/indexer/lib/indexer/fetcher/internal_transaction.ex | 9 +++++++++ cspell.json | 3 ++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fbed48e90f0..cc62d0c71285 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#9379](https://github.com/blockscout/blockscout/pull/9379) - Filter non-traceable transactions for zetachain - [#9351](https://github.com/blockscout/blockscout/pull/9351) - Noves.fi: add proxy endpoint for describeTxs endpoint - [#9202](https://github.com/blockscout/blockscout/pull/9202) - Add base and priority fee to gas oracle response - [#9168](https://github.com/blockscout/blockscout/pull/9168) - Support EIP4844 blobs indexing & API diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex index d5d1af69436a..ad5dfa248205 100644 --- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex @@ -213,6 +213,7 @@ defmodule Indexer.Fetcher.InternalTransaction do block_number, {:ok, acc_list} -> block_number |> Chain.get_transactions_of_block_number() + |> filter_non_traceable_transactions() |> Enum.map(¶ms(&1)) |> case do [] -> @@ -236,6 +237,14 @@ defmodule Indexer.Fetcher.InternalTransaction do end) end + @zetachain_non_traceable_type 88 + defp filter_non_traceable_transactions(transactions) do + case Application.get_env(:explorer, :chain_type) do + "zetachain" -> Enum.reject(transactions, &(&1.type == @zetachain_non_traceable_type)) + _ -> transactions + end + end + defp safe_import_internal_transaction(internal_transactions_params, block_numbers) do import_internal_transaction(internal_transactions_params, block_numbers) rescue diff --git a/cspell.json b/cspell.json index de8d03da917f..38fc3b595e07 100644 --- a/cspell.json +++ b/cspell.json @@ -570,7 +570,8 @@ "verifyproxycontract", "checkproxyverification", "NOTOK", - "sushiswap" + "sushiswap", + "zetachain" ], "enableFiletypes": [ "dotenv", From dbf1b414c2cc4db84dcd3801390c885463faf761 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Tue, 13 Feb 2024 14:03:45 +0400 Subject: [PATCH 411/607] Disable "no internal txs for some transactions" check for zetachain --- .../import/runner/internal_transactions.ex | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex index a4d512e602b8..a423d75ff7af 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex @@ -333,6 +333,9 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do # common_tuples = MapSet.intersection(required_tuples, candidate_tuples) #should be added # |> MapSet.difference(internal_transactions_tuples) should be replaced with |> MapSet.difference(common_tuples) + # Note: for zetachain, the case "# - there are no internal txs for some transactions" is removed since + # there are may be non-traceable transactions + transactions_tuples = MapSet.new(transactions, &{&1.hash, &1.block_number}) internal_transactions_tuples = MapSet.new(internal_transactions_params, &{&1.transaction_hash, &1.block_number}) @@ -340,10 +343,21 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do all_tuples = MapSet.union(transactions_tuples, internal_transactions_tuples) invalid_block_numbers = - all_tuples - |> MapSet.difference(internal_transactions_tuples) - |> MapSet.new(fn {_hash, block_number} -> block_number end) - |> MapSet.to_list() + if Application.get_env(:explorer, :chain_type) == "zetachain" do + Enum.reduce(internal_transactions_tuples, [], fn {transaction_hash, block_number}, acc -> + # credo:disable-for-next-line + case Enum.find(transactions_tuples, fn {t_hash, _block_number} -> t_hash == transaction_hash end) do + nil -> acc + {_t_hash, ^block_number} -> acc + _ -> [block_number | acc] + end + end) + else + all_tuples + |> MapSet.difference(internal_transactions_tuples) + |> MapSet.new(fn {_hash, block_number} -> block_number end) + |> MapSet.to_list() + end {:ok, invalid_block_numbers} end From 362dae45dbfbd15c0ddbfb4495bf1d589421900f Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Mon, 29 Jan 2024 21:16:15 +0300 Subject: [PATCH 412/607] Add license_type field to smart_contracts table; Forward license_type through verification endpoints --- .../api/rpc/contract_controller.ex | 2 + .../api/v2/verification_controller.ex | 11 ++++- .../views/api/v2/smart_contract_view.ex | 6 ++- .../lib/explorer/chain/smart_contract.ex | 42 ++++++++++++++++++- .../smart_contract/solidity/publisher.ex | 18 ++++++-- ...112623_add_smart_contract_license_type.exs | 9 ++++ 6 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 apps/explorer/priv/repo/migrations/20240129112623_add_smart_contract_license_type.exs diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex index c0f6a50ef190..595b6116535b 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex @@ -626,6 +626,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do |> required_param(params, "contractname", "name") |> required_param(params, "compilerversion", "compiler_version") |> optional_param(params, "constructorArguments", "constructor_arguments") + |> optional_param(params, "licenseType", "license_type") end defp fetch_verifysourcecode_solidity_single_file_params(params) do @@ -638,6 +639,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do |> optional_param(params, "runs", "optimization_runs") |> optional_param(params, "evmversion", "evm_version") |> optional_param(params, "constructorArguments", "constructor_arguments") + |> optional_param(params, "licenseType", "license_type") |> prepare_optimization() end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex index 449630b14cad..cb040711a856 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex @@ -41,7 +41,8 @@ defmodule BlockScoutWeb.API.V2.VerificationController do vyper_compiler_versions: vyper_compiler_versions, verification_options: verification_options, vyper_evm_versions: CodeCompiler.evm_versions(:vyper), - is_rust_verifier_microservice_enabled: RustVerifierInterface.enabled?() + is_rust_verifier_microservice_enabled: RustVerifierInterface.enabled?(), + license_types: Enum.into(SmartContract.license_types_enum(), %{}) }) end @@ -70,6 +71,7 @@ defmodule BlockScoutWeb.API.V2.VerificationController do |> Map.put("name", Map.get(params, "contract_name", "")) |> Map.put("external_libraries", Map.get(params, "libraries", %{})) |> Map.put("is_yul", Map.get(params, "is_yul_contract", false)) + |> Map.put("license_type", Map.get(params, "license_type")) log_sc_verification_started(address_hash_string) Que.add(SolidityPublisherWorker, {"flattened_api_v2", verification_params}) @@ -95,6 +97,7 @@ defmodule BlockScoutWeb.API.V2.VerificationController do |> Map.put("autodetect_constructor_args", Map.get(params, "autodetect_constructor_args", true)) |> Map.put("constructor_arguments", Map.get(params, "constructor_args", "")) |> Map.put("name", Map.get(params, "contract_name", "")) + |> Map.put("license_type", Map.get(params, "license_type")) log_sc_verification_started(address_hash_string) Que.add(SolidityPublisherWorker, {"json_api_v2", verification_params, json_input}) @@ -152,6 +155,7 @@ defmodule BlockScoutWeb.API.V2.VerificationController do )).() |> Map.put("evm_version", Map.get(params, "evm_version", "default")) |> Map.put("external_libraries", json) + |> Map.put("license_type", Map.get(params, "license_type")) files_array = files @@ -182,6 +186,7 @@ defmodule BlockScoutWeb.API.V2.VerificationController do |> Map.put("constructor_arguments", Map.get(params, "constructor_args", "") || "") |> Map.put("name", Map.get(params, "contract_name", "Vyper_contract")) |> Map.put("evm_version", Map.get(params, "evm_version")) + |> Map.put("license_type", Map.get(params, "license_type")) log_sc_verification_started(address_hash_string) Que.add(VyperPublisherWorker, {"vyper_flattened", verification_params}) @@ -209,6 +214,7 @@ defmodule BlockScoutWeb.API.V2.VerificationController do } |> Map.put("evm_version", Map.get(params, "evm_version")) |> Map.put("interfaces", interfaces) + |> Map.put("license_type", Map.get(params, "license_type")) files_array = files @@ -235,7 +241,8 @@ defmodule BlockScoutWeb.API.V2.VerificationController do verification_params = %{ "address_hash" => String.downcase(address_hash_string), "compiler_version" => compiler_version, - "input" => json_input + "input" => json_input, + "license_type" => Map.get(params, "license_type") } log_sc_verification_started(address_hash_string) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex index 607fc67e91a7..a0e7196b5843 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex @@ -194,7 +194,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do if(smart_contract_verified, do: format_constructor_arguments(target_contract.abi, target_contract.constructor_arguments) ), - "language" => smart_contract_language(smart_contract) + "language" => smart_contract_language(smart_contract), + "license_type" => smart_contract.license_type } |> Map.merge(bytecode_info(address)) end @@ -304,7 +305,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do "market_cap" => token && token.circulating_market_cap, "has_constructor_args" => !is_nil(smart_contract.constructor_arguments), "coin_balance" => - if(smart_contract.address.fetched_coin_balance, do: smart_contract.address.fetched_coin_balance.value) + if(smart_contract.address.fetched_coin_balance, do: smart_contract.address.fetched_coin_balance.value), + "license_type" => smart_contract.license_type } end diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index 2fa61939e1c4..7d5ba48d0c96 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -221,6 +221,39 @@ defmodule Explorer.Chain.SmartContract do """ @type abi :: [event_description | function_description] + @doc """ + 1. No License (None) + 2. The Unlicense (Unlicense) + 3. MIT License (MIT) + 4. GNU General Public License v2.0 (GNU GPLv2) + 5. GNU General Public License v3.0 (GNU GPLv3) + 6. GNU Lesser General Public License v2.1 (GNU LGPLv2.1) + 7. GNU Lesser General Public License v3.0 (GNU LGPLv3) + 8. BSD 2-clause "Simplified" license (BSD-2-Clause) + 9. BSD 3-clause "New" Or "Revised" license* (BSD-3-Clause) + 10. Mozilla Public License 2.0 (MPL-2.0) + 11. Open Software License 3.0 (OSL-3.0) + 12. Apache 2.0 (Apache-2.0) + 13. GNU Affero General Public License (GNU AGPLv3) + 14. Business Source License (BSL 1.1) + """ + @license_enum [ + none: 1, + unlicense: 2, + mit: 3, + gnu_gpl_v2: 4, + gnu_gpl_v3: 5, + gnu_lgpl_v2_1: 6, + gnu_lgpl_v3: 7, + bsd_2_clause: 8, + bsd_3_clause: 9, + mpl_2_0: 10, + osl_3_0: 11, + apache_2_0: 12, + gnu_agpl_v3: 13, + bsl_1_1: 14 + ] + @typedoc """ * `name` - the human-readable name of the smart contract. * `compiler_version` - the version of the Solidity compiler used to compile `contract_source_code` with `optimization` @@ -271,6 +304,7 @@ defmodule Explorer.Chain.SmartContract do field(:is_yul, :boolean, virtual: true) field(:metadata_from_verified_twin, :boolean, virtual: true) field(:verified_via_eth_bytecode_db, :boolean) + field(:license_type, Ecto.Enum, values: @license_enum, default: :none) has_many( :decompiled_smart_contracts, @@ -322,7 +356,8 @@ defmodule Explorer.Chain.SmartContract do :compiler_settings, :implementation_address_hash, :implementation_fetched_at, - :verified_via_eth_bytecode_db + :verified_via_eth_bytecode_db, + :license_type ]) |> validate_required([ :name, @@ -363,7 +398,8 @@ defmodule Explorer.Chain.SmartContract do :contract_code_md5, :implementation_name, :autodetect_constructor_args, - :verified_via_eth_bytecode_db + :verified_via_eth_bytecode_db, + :license_type ]) |> (&if(verification_with_files?, do: &1, @@ -462,6 +498,8 @@ defmodule Explorer.Chain.SmartContract do |> Changeset.put_change(:contract_source_code, "") end + def license_types_enum, do: @license_enum + @doc """ Returns smart-contract changeset with checksummed address hash """ diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex b/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex index b83895a15e46..b0ea8dd6249b 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex @@ -7,6 +7,7 @@ defmodule Explorer.SmartContract.Solidity.Publisher do import Explorer.SmartContract.Helper, only: [cast_libraries: 1] + alias Explorer.Helper, as: ExplorerHelper alias Explorer.Chain.SmartContract alias Explorer.SmartContract.{CompilerVersion, Helper} alias Explorer.SmartContract.Solidity.Verifier @@ -48,7 +49,7 @@ defmodule Explorer.SmartContract.Solidity.Publisher do "sourceFiles" => _ } = result_params } -> - process_rust_verifier_response(result_params, address_hash, false, false) + process_rust_verifier_response(result_params, address_hash, params, false, false) {:ok, %{abi: abi, constructor_arguments: constructor_arguments}} -> params_with_constructor_arguments = @@ -84,7 +85,7 @@ defmodule Explorer.SmartContract.Solidity.Publisher do "sourceFiles" => _, "compilerSettings" => _ } = result_params} -> - process_rust_verifier_response(result_params, address_hash, true, true) + process_rust_verifier_response(result_params, address_hash, params, true, true) {:ok, %{abi: abi, constructor_arguments: constructor_arguments}, additional_params} -> params_with_constructor_arguments = @@ -124,7 +125,7 @@ defmodule Explorer.SmartContract.Solidity.Publisher do "sourceFiles" => _, "compilerSettings" => _ } = result_params} -> - process_rust_verifier_response(result_params, address_hash, false, true) + process_rust_verifier_response(result_params, address_hash, params, false, true) {:error, error} -> {:error, unverified_smart_contract(address_hash, params, error, nil, true)} @@ -146,6 +147,7 @@ defmodule Explorer.SmartContract.Solidity.Publisher do "matchType" => match_type } = source, address_hash, + initial_params, is_standard_json?, save_file_path?, automatically_verified? \\ false @@ -177,6 +179,7 @@ defmodule Explorer.SmartContract.Solidity.Publisher do |> Map.put("partially_verified", match_type == "PARTIAL") |> Map.put("verified_via_eth_bytecode_db", automatically_verified?) |> Map.put("verified_via_sourcify", source["sourcify?"]) + |> Map.put("license_type", initial_params["license_type"]) publish_smart_contract(address_hash, prepared_params, Jason.decode!(abi_string || "null")) end @@ -271,7 +274,8 @@ defmodule Explorer.SmartContract.Solidity.Publisher do autodetect_constructor_args: params["autodetect_constructor_args"], is_yul: params["is_yul"] || false, compiler_settings: clean_compiler_settings, - verified_via_eth_bytecode_db: params["verified_via_eth_bytecode_db"] || false + verified_via_eth_bytecode_db: params["verified_via_eth_bytecode_db"] || false, + license_type: prepare_license_type(params["license_type"]) || :none } end @@ -284,6 +288,12 @@ defmodule Explorer.SmartContract.Solidity.Publisher do end) end + defp prepare_license_type(atom_or_integer) when is_atom(atom_or_integer) or is_integer(atom_or_integer), + do: atom_or_integer + + defp prepare_license_type(binary) when is_binary(binary), do: ExplorerHelper.parse_integer(binary) || binary + defp prepare_license_type(_), do: nil + defp add_external_libraries(%{"external_libraries" => _} = params, _external_libraries), do: params defp add_external_libraries(params, external_libraries) do diff --git a/apps/explorer/priv/repo/migrations/20240129112623_add_smart_contract_license_type.exs b/apps/explorer/priv/repo/migrations/20240129112623_add_smart_contract_license_type.exs new file mode 100644 index 000000000000..91bea9e1b861 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240129112623_add_smart_contract_license_type.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.AddSmartContractLicenseType do + use Ecto.Migration + + def change do + alter table("smart_contracts") do + add(:license_type, :int2, null: false, default: 1) + end + end +end From 7d40dcbeada10c6f86aef9d78e194832c5e40826 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Mon, 29 Jan 2024 21:18:46 +0300 Subject: [PATCH 413/607] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc62d0c71285..bed3db718e48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - [#9379](https://github.com/blockscout/blockscout/pull/9379) - Filter non-traceable transactions for zetachain - [#9351](https://github.com/blockscout/blockscout/pull/9351) - Noves.fi: add proxy endpoint for describeTxs endpoint +- [#9282](https://github.com/blockscout/blockscout/pull/9282) - Add `license_type` to smart contracts - [#9202](https://github.com/blockscout/blockscout/pull/9202) - Add base and priority fee to gas oracle response - [#9168](https://github.com/blockscout/blockscout/pull/9168) - Support EIP4844 blobs indexing & API From 593e0d027b43ed3c72ec5d47ec8999851390c91f Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Mon, 29 Jan 2024 22:10:50 +0300 Subject: [PATCH 414/607] Some fixes --- .../look_up_smart_contract_sources_on_demand.ex | 6 +++--- apps/explorer/lib/explorer/smart_contract/helper.ex | 11 +++++++++++ .../lib/explorer/smart_contract/solidity/publisher.ex | 9 +-------- .../lib/explorer/smart_contract/vyper/publisher.ex | 11 +++++++---- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex b/apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex index 145b77368abb..47d4cb592098 100644 --- a/apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex +++ b/apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex @@ -130,15 +130,15 @@ defmodule Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand do end def process_contract_source("SOLIDITY", source, address_hash) do - SolidityPublisher.process_rust_verifier_response(source, address_hash, true, true, true) + SolidityPublisher.process_rust_verifier_response(source, address_hash, %{}, true, true, true) end def process_contract_source("VYPER", source, address_hash) do - VyperPublisher.process_rust_verifier_response(source, address_hash, true, true, true) + VyperPublisher.process_rust_verifier_response(source, address_hash, %{}, true, true, true) end def process_contract_source("YUL", source, address_hash) do - SolidityPublisher.process_rust_verifier_response(source, address_hash, true, true, true) + SolidityPublisher.process_rust_verifier_response(source, address_hash, %{}, true, true, true) end def process_contract_source(_, _source, _address_hash), do: false diff --git a/apps/explorer/lib/explorer/smart_contract/helper.ex b/apps/explorer/lib/explorer/smart_contract/helper.ex index 40ffcaa69866..3e1f70806d7b 100644 --- a/apps/explorer/lib/explorer/smart_contract/helper.ex +++ b/apps/explorer/lib/explorer/smart_contract/helper.ex @@ -5,6 +5,7 @@ defmodule Explorer.SmartContract.Helper do alias Explorer.Chain alias Explorer.Chain.{Hash, SmartContract} + alias Explorer.Helper alias Phoenix.HTML def queriable_method?(method) do @@ -193,4 +194,14 @@ defmodule Explorer.SmartContract.Helper do "creationCode" => to_string(init) } end + + @doc """ + Prepare license type for verification. + """ + @spec prepare_license_type(any()) :: atom() | integer() | binary() | nil + def prepare_license_type(atom_or_integer) when is_atom(atom_or_integer) or is_integer(atom_or_integer), + do: atom_or_integer + + def prepare_license_type(binary) when is_binary(binary), do: Helper.parse_integer(binary) || binary + def prepare_license_type(_), do: nil end diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex b/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex index b0ea8dd6249b..a81a0f7ab5d0 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex @@ -5,9 +5,8 @@ defmodule Explorer.SmartContract.Solidity.Publisher do require Logger - import Explorer.SmartContract.Helper, only: [cast_libraries: 1] + import Explorer.SmartContract.Helper, only: [cast_libraries: 1, prepare_license_type: 1] - alias Explorer.Helper, as: ExplorerHelper alias Explorer.Chain.SmartContract alias Explorer.SmartContract.{CompilerVersion, Helper} alias Explorer.SmartContract.Solidity.Verifier @@ -288,12 +287,6 @@ defmodule Explorer.SmartContract.Solidity.Publisher do end) end - defp prepare_license_type(atom_or_integer) when is_atom(atom_or_integer) or is_integer(atom_or_integer), - do: atom_or_integer - - defp prepare_license_type(binary) when is_binary(binary), do: ExplorerHelper.parse_integer(binary) || binary - defp prepare_license_type(_), do: nil - defp add_external_libraries(%{"external_libraries" => _} = params, _external_libraries), do: params defp add_external_libraries(params, external_libraries) do diff --git a/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex b/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex index 7a6087d39063..3a27818dc4ae 100644 --- a/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex +++ b/apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex @@ -3,7 +3,7 @@ defmodule Explorer.SmartContract.Vyper.Publisher do Module responsible to control Vyper contract verification. """ - import Explorer.SmartContract.Helper, only: [cast_libraries: 1] + import Explorer.SmartContract.Helper, only: [cast_libraries: 1, prepare_license_type: 1] require Logger @@ -25,7 +25,7 @@ defmodule Explorer.SmartContract.Vyper.Publisher do "compilerSettings" => _compiler_settings_string } = source } -> - process_rust_verifier_response(source, address_hash, false, false) + process_rust_verifier_response(source, address_hash, params, false, false) {:ok, %{abi: abi}} -> publish_smart_contract(address_hash, params, abi) @@ -60,7 +60,7 @@ defmodule Explorer.SmartContract.Vyper.Publisher do "compilerSettings" => _compiler_settings_string } = source } -> - process_rust_verifier_response(source, address_hash, true, standard_json?) + process_rust_verifier_response(source, address_hash, params, true, standard_json?) {:ok, %{abi: abi}} -> publish_smart_contract(address_hash, params, abi) @@ -85,6 +85,7 @@ defmodule Explorer.SmartContract.Vyper.Publisher do "matchType" => match_type }, address_hash, + initial_params, save_file_path?, standard_json?, automatically_verified? \\ false @@ -115,6 +116,7 @@ defmodule Explorer.SmartContract.Vyper.Publisher do if(is_nil(compiler_settings["optimize"]), do: true, else: compiler_settings["optimize"]) ) |> Map.put("compiler_settings", if(standard_json?, do: compiler_settings)) + |> Map.put("license_type", initial_params["license_type"]) publish_smart_contract(address_hash, prepared_params, Jason.decode!(abi_string)) end @@ -182,7 +184,8 @@ defmodule Explorer.SmartContract.Vyper.Publisher do is_vyper_contract: true, file_path: params["file_path"], verified_via_eth_bytecode_db: params["verified_via_eth_bytecode_db"] || false, - compiler_settings: clean_compiler_settings + compiler_settings: clean_compiler_settings, + license_type: prepare_license_type(params["license_type"]) || :none } end end From b2c53dc87fb1873752f21467306f4a3f324905a6 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Tue, 6 Feb 2024 18:06:38 +0300 Subject: [PATCH 415/607] Fix tests --- .../api/v2/smart_contract_controller_test.exs | 18 ++++++++++++------ .../lib/explorer/smart_contract/helper.ex | 3 +-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index 3740583462cc..6df1eddb73d3 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -112,7 +112,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029", "abi" => target_contract.abi, "is_verified_via_eth_bytecode_db" => target_contract.verified_via_eth_bytecode_db, - "language" => smart_contract_language(target_contract) + "language" => smart_contract_language(target_contract), + "license_type" => "none" } request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(target_contract.address_hash)}") @@ -156,7 +157,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "stateMutability" => "view", "type" => "function" } - ] + ], + license_type: 13 ) insert(:transaction, @@ -203,7 +205,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029", "abi" => target_contract.abi, "is_verified_via_eth_bytecode_db" => target_contract.verified_via_eth_bytecode_db, - "language" => smart_contract_language(target_contract) + "language" => smart_contract_language(target_contract), + "license_type" => "gnu_agpl_v3" } request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(target_contract.address_hash)}") @@ -297,7 +300,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029", "abi" => target_contract.abi, "is_verified_via_eth_bytecode_db" => target_contract.verified_via_eth_bytecode_db, - "language" => smart_contract_language(target_contract) + "language" => smart_contract_language(target_contract), + "license_type" => "none" } request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}") @@ -337,7 +341,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "stateMutability" => "view", "type" => "function" } - ] + ], + license_type: 9 ) insert(:smart_contract_additional_source, @@ -406,7 +411,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "creation_bytecode" => proxy_tx_input, "abi" => implementation_contract.abi, "is_verified_via_eth_bytecode_db" => implementation_contract.verified_via_eth_bytecode_db, - "language" => smart_contract_language(implementation_contract) + "language" => smart_contract_language(implementation_contract), + "license_type" => "bsd_3_clause" } request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(proxy_address.hash)}") diff --git a/apps/explorer/lib/explorer/smart_contract/helper.ex b/apps/explorer/lib/explorer/smart_contract/helper.ex index 3e1f70806d7b..c1efc76c91c5 100644 --- a/apps/explorer/lib/explorer/smart_contract/helper.ex +++ b/apps/explorer/lib/explorer/smart_contract/helper.ex @@ -3,9 +3,8 @@ defmodule Explorer.SmartContract.Helper do SmartContract helper functions """ - alias Explorer.Chain + alias Explorer.{Chain, Helper} alias Explorer.Chain.{Hash, SmartContract} - alias Explorer.Helper alias Phoenix.HTML def queriable_method?(method) do From 995f9c28f8eaa55a0e28ecebde94aa1fc67df83d Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Tue, 13 Feb 2024 14:51:10 +0300 Subject: [PATCH 416/607] Process review comment --- .../lib/explorer/smart_contract/solidity/publisher_worker.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex b/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex index 06911dfd333d..ae2caacc5e5a 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex @@ -64,6 +64,10 @@ defmodule Explorer.SmartContract.Solidity.PublisherWorker do result {:error, changeset} -> + Logger.error( + "Solidity smart-contract verification #{address_hash} failed because of the error: #{inspect(changeset)}" + ) + {:error, changeset} end From 1eb0647597f088e4fdb9bea542023bd6256e9d60 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Mon, 11 Sep 2023 11:06:01 +0300 Subject: [PATCH 417/607] Minor changes --- CHANGELOG.md | 1 + docker-compose/envs/common-blockscout.env | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bed3db718e48..2681cc373a66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -370,6 +370,7 @@ - [#8543](https://github.com/blockscout/blockscout/pull/8543) - Fix polygon tracer - [#8386](https://github.com/blockscout/blockscout/pull/8386) - Add `owner_address_hash` to the `token_instances` - [#8530](https://github.com/blockscout/blockscout/pull/8530) - Add `block_type` to search results +- [#7584](https://github.com/blockscout/blockscout/pull/7584) - Add Polygon zkEVM batches fetcher - [#8180](https://github.com/blockscout/blockscout/pull/8180) - Deposits and Withdrawals for Polygon Edge - [#7996](https://github.com/blockscout/blockscout/pull/7996) - Add CoinBalance fetcher init query limit - [#8658](https://github.com/blockscout/blockscout/pull/8658) - Remove block consensus on import fail diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 691a31ca8cf4..a1e813ce7436 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -169,9 +169,9 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false # INDEXER_POLYGON_EDGE_L2_STATE_RECEIVER_CONTRACT= # INDEXER_POLYGON_EDGE_L2_DEPOSITS_START_BLOCK= # INDEXER_POLYGON_EDGE_ETH_GET_LOGS_RANGE_SIZE= -# INDEXER_ZKEVM_BATCHES_ENABLED= -# INDEXER_ZKEVM_BATCHES_CHUNK_SIZE= -# INDEXER_ZKEVM_BATCHES_RECHECK_INTERVAL= +# INDEXER_POLYGON_ZKEVM_BATCHES_ENABLED= +# INDEXER_POLYGON_ZKEVM_BATCHES_CHUNK_SIZE= +# INDEXER_POLYGON_ZKEVM_BATCHES_RECHECK_INTERVAL= # INDEXER_REALTIME_FETCHER_MAX_GAP= # INDEXER_FETCHER_INIT_QUERY_LIMIT= # INDEXER_TOKEN_BALANCES_FETCHER_INIT_QUERY_LIMIT= From b6859d3547ec7032eb6216c66ba2b782445fc27c Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Fri, 15 Sep 2023 13:22:03 +0300 Subject: [PATCH 418/607] Define chain type --- apps/explorer/config/test.exs | 9 +++++++++ config/runtime.exs | 16 ++++++++-------- config/runtime/dev.exs | 32 ++++++++++++++++---------------- config/runtime/prod.exs | 32 ++++++++++++++++---------------- 4 files changed, 49 insertions(+), 40 deletions(-) diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index 728b931ac91a..16e54f04eb5d 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -64,6 +64,15 @@ for repo <- [ pool_size: 1 end +config :explorer, Explorer.Repo.PolygonZkevm, + database: "explorer_test", + hostname: "localhost", + pool: Ecto.Adapters.SQL.Sandbox, + # Default of `5_000` was too low for `BlockFetcher` test + ownership_timeout: :timer.minutes(1), + timeout: :timer.seconds(60), + queue_target: 1000 + config :logger, :explorer, level: :warn, path: Path.absname("logs/test/explorer.log") diff --git a/config/runtime.exs b/config/runtime.exs index 4e3fee6b8d1a..d7061cd1794b 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -682,14 +682,6 @@ config :indexer, Indexer.Fetcher.PolygonEdge.WithdrawalExit, start_block_l1: System.get_env("INDEXER_POLYGON_EDGE_L1_WITHDRAWALS_START_BLOCK"), exit_helper: System.get_env("INDEXER_POLYGON_EDGE_L1_EXIT_HELPER_CONTRACT") -config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch, - chunk_size: ConfigHelper.parse_integer_env_var("INDEXER_ZKEVM_BATCHES_CHUNK_SIZE", 20), - recheck_interval: ConfigHelper.parse_integer_env_var("INDEXER_ZKEVM_BATCHES_RECHECK_INTERVAL", 60) - -config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch.Supervisor, - enabled: - ConfigHelper.chain_type() == "polygon_zkevm" && ConfigHelper.parse_bool_env_var("INDEXER_ZKEVM_BATCHES_ENABLED") - config :indexer, Indexer.Fetcher.RootstockData.Supervisor, disabled?: ConfigHelper.chain_type() != "rsk" || ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_ROOTSTOCK_DATA_FETCHER") @@ -735,6 +727,14 @@ config :indexer, Indexer.Fetcher.Shibarium.L1.Supervisor, enabled: ConfigHelper. config :indexer, Indexer.Fetcher.Shibarium.L2.Supervisor, enabled: ConfigHelper.chain_type() == "shibarium" +config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch, + chunk_size: ConfigHelper.parse_integer_env_var("INDEXER_ZKEVM_BATCHES_CHUNK_SIZE", 20), + recheck_interval: ConfigHelper.parse_integer_env_var("INDEXER_ZKEVM_BATCHES_RECHECK_INTERVAL", 60) + +config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch.Supervisor, + enabled: + ConfigHelper.chain_type() == "polygon_zkevm" && ConfigHelper.parse_bool_env_var("INDEXER_ZKEVM_BATCHES_ENABLED") + Code.require_file("#{config_env()}.exs", "config/runtime") for config <- "../apps/*/config/runtime/#{config_env()}.exs" |> Path.expand(__DIR__) |> Path.wildcard() do diff --git a/config/runtime/dev.exs b/config/runtime/dev.exs index 4636eb72afda..e9d66539c07a 100644 --- a/config/runtime/dev.exs +++ b/config/runtime/dev.exs @@ -74,8 +74,8 @@ config :explorer, Explorer.Repo.Account, pool_size: ConfigHelper.parse_integer_env_var("ACCOUNT_POOL_SIZE", 10), queue_target: queue_target -# Configure PolygonEdge database -config :explorer, Explorer.Repo.PolygonEdge, +# Configure Beacon Chain database +config :explorer, Explorer.Repo.Beacon, database: database, hostname: hostname, url: System.get_env("DATABASE_URL"), @@ -83,8 +83,8 @@ config :explorer, Explorer.Repo.PolygonEdge, # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type pool_size: 1 -# Configure PolygonZkevm database -config :explorer, Explorer.Repo.PolygonZkevm, +# Configures BridgedTokens database +config :explorer, Explorer.Repo.BridgedTokens, database: database, hostname: hostname, url: System.get_env("DATABASE_URL"), @@ -92,8 +92,8 @@ config :explorer, Explorer.Repo.PolygonZkevm, # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type pool_size: 1 -# Configure Rootstock database -config :explorer, Explorer.Repo.RSK, +# Configure PolygonEdge database +config :explorer, Explorer.Repo.PolygonEdge, database: database, hostname: hostname, url: System.get_env("DATABASE_URL"), @@ -101,8 +101,8 @@ config :explorer, Explorer.Repo.RSK, # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type pool_size: 1 -# Configure Beacon Chain database -config :explorer, Explorer.Repo.Beacon, +# Configure PolygonZkevm database +config :explorer, Explorer.Repo.PolygonZkevm, database: database, hostname: hostname, url: System.get_env("DATABASE_URL"), @@ -110,11 +110,13 @@ config :explorer, Explorer.Repo.Beacon, # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type pool_size: 1 -# Configure Suave database -config :explorer, Explorer.Repo.Suave, +# Configure Rootstock database +config :explorer, Explorer.Repo.RSK, database: database, hostname: hostname, - url: ExplorerConfigHelper.get_suave_db_url(), + url: System.get_env("DATABASE_URL"), + # actually this repo is not started, and its pool size remains unused. + # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type pool_size: 1 # Configure Shibarium database @@ -124,13 +126,11 @@ config :explorer, Explorer.Repo.Shibarium, url: System.get_env("DATABASE_URL"), pool_size: 1 -# Configures BridgedTokens database -config :explorer, Explorer.Repo.BridgedTokens, +# Configure Suave database +config :explorer, Explorer.Repo.Suave, database: database, hostname: hostname, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type + url: ExplorerConfigHelper.get_suave_db_url(), pool_size: 1 variant = Variant.get() diff --git a/config/runtime/prod.exs b/config/runtime/prod.exs index 81c1e4ff24f4..42e253146bb4 100644 --- a/config/runtime/prod.exs +++ b/config/runtime/prod.exs @@ -51,41 +51,43 @@ config :explorer, Explorer.Repo.Account, ssl: ExplorerConfigHelper.ssl_enabled?(), queue_target: queue_target -# Configures PolygonEdge database -config :explorer, Explorer.Repo.PolygonEdge, +# Configure Beacon Chain database +config :explorer, Explorer.Repo.Beacon, url: System.get_env("DATABASE_URL"), # actually this repo is not started, and its pool size remains unused. # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() -# Configures PolygonZkevm database -config :explorer, Explorer.Repo.PolygonZkevm, +# Configures BridgedTokens database +config :explorer, Explorer.Repo.BridgedTokens, url: System.get_env("DATABASE_URL"), # actually this repo is not started, and its pool size remains unused. # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() -# Configures Rootstock database -config :explorer, Explorer.Repo.RSK, +# Configures PolygonEdge database +config :explorer, Explorer.Repo.PolygonEdge, url: System.get_env("DATABASE_URL"), # actually this repo is not started, and its pool size remains unused. # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() -# Configure Beacon Chain database -config :explorer, Explorer.Repo.Beacon, +# Configures PolygonZkevm database +config :explorer, Explorer.Repo.PolygonZkevm, url: System.get_env("DATABASE_URL"), # actually this repo is not started, and its pool size remains unused. # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() -# Configures Suave database -config :explorer, Explorer.Repo.Suave, - url: ExplorerConfigHelper.get_suave_db_url(), +# Configures Rootstock database +config :explorer, Explorer.Repo.RSK, + url: System.get_env("DATABASE_URL"), + # actually this repo is not started, and its pool size remains unused. + # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() @@ -95,11 +97,9 @@ config :explorer, Explorer.Repo.Shibarium, pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() -# Configures BridgedTokens database -config :explorer, Explorer.Repo.BridgedTokens, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type +# Configures Suave database +config :explorer, Explorer.Repo.Suave, + url: ExplorerConfigHelper.get_suave_db_url(), pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() From 8cf0f63b3326d0c1dbc23b47340ca1109aaf39db Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:30:49 +0300 Subject: [PATCH 419/607] Prepare zkevm_bridge table and its importer --- .../import/runner/zkevm/bridge_operations.ex | 115 ++++++++++++++++++ .../lib/explorer/chain/zkevm/bridge.ex | 51 ++++++++ .../20231010093238_add_bridge_table.exs | 24 ++++ config/runtime.exs | 6 +- 4 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex create mode 100644 apps/explorer/lib/explorer/chain/zkevm/bridge.ex create mode 100644 apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_table.exs diff --git a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex b/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex new file mode 100644 index 000000000000..f0f7e916615a --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex @@ -0,0 +1,115 @@ +defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeOperations do + @moduledoc """ + Bulk imports `t:Explorer.Chain.Zkevm.Bridge.t/0`. + """ + + require Ecto.Query + + import Ecto.Query, only: [from: 2] + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.Zkevm.Bridge, as: ZkevmBridge + alias Explorer.Prometheus.Instrumenter + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [ZkevmBridge.t()] + + @impl Import.Runner + def ecto_schema_module, do: ZkevmBridge + + @impl Import.Runner + def option_key, do: :zkevm_bridge_operations + + @impl Import.Runner + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + Multi.run(multi, :insert_zkevm_bridge_operations, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :zkevm_bridge_operations, + :zkevm_bridge_operations + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [ZkevmBridge.t()]} + | {:error, [Changeset.t()]} + def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) + + # Enforce ZkevmBridge ShareLocks order (see docs: sharelock.md) + ordered_changes_list = Enum.sort_by(changes_list, &{&1.type, &1.index}) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + conflict_target: [:type, :index], + on_conflict: on_conflict, + for: ZkevmBridge, + returning: true, + timeout: timeout, + timestamps: timestamps + ) + + {:ok, inserted} + end + + defp default_on_conflict do + from( + op in ZkevmBridge, + update: [ + set: [ + # Don't update `type` as it is part of the composite primary key and used for the conflict target + # Don't update `index` as it is part of the composite primary key and used for the conflict target + l1_transaction_hash: fragment("EXCLUDED.l1_transaction_hash"), + l2_transaction_hash: fragment("EXCLUDED.l2_transaction_hash"), + l1_token_address: fragment("EXCLUDED.l1_token_address"), + l1_token_decimals: fragment("EXCLUDED.l1_token_decimals"), + l1_token_symbol: fragment("EXCLUDED.l1_token_symbol"), + amount: fragment("EXCLUDED.amount"), + block_number: fragment("EXCLUDED.block_number"), + block_timestamp: fragment("EXCLUDED.block_timestamp"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", op.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", op.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.l1_transaction_hash, EXCLUDED.l2_transaction_hash, EXCLUDED.l1_token_address, EXCLUDED.l1_token_decimals, EXCLUDED.l1_token_symbol, EXCLUDED.amount, EXCLUDED.block_number, EXCLUDED.block_timestamp) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?)", + op.l1_transaction_hash, + op.l2_transaction_hash, + op.l1_token_address, + op.l1_token_decimals, + op.l1_token_symbol, + op.amount, + op.block_number, + op.block_timestamp + ) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/zkevm/bridge.ex b/apps/explorer/lib/explorer/chain/zkevm/bridge.ex new file mode 100644 index 000000000000..94d43c880067 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/zkevm/bridge.ex @@ -0,0 +1,51 @@ +defmodule Explorer.Chain.Zkevm.Bridge do + @moduledoc "Models a bridge operation for Polygon zkEVM." + + use Explorer.Schema + + alias Explorer.Chain.{Block, Hash} + + @optional_attrs ~w(l1_transaction_hash l2_transaction_hash l1_token_address l1_token_decimals l1_token_symbol block_number block_timestamp)a + + @required_attrs ~w(type index amount)a + + @type t :: %__MODULE__{ + type: String.t(), + index: non_neg_integer(), + l1_transaction_hash: Hash.t() | nil, + l2_transaction_hash: Hash.t() | nil, + l1_token_address: Hash.Address.t() | nil, + l1_token_decimals: non_neg_integer() | nil, + l1_token_symbol: String.t() | nil, + amount: Decimal.t(), + block_number: Block.block_number() | nil, + block_timestamp: DateTime.t() | nil + } + + @primary_key false + schema "zkevm_bridge" do + field(:type, Ecto.Enum, values: [:deposit, :withdrawal], primary_key: true) + field(:index, :integer, primary_key: true) + field(:l1_transaction_hash, Hash.Full) + field(:l2_transaction_hash, Hash.Full) + field(:l1_token_address, Hash.Address) + field(:l1_token_decimals, :integer) + field(:l1_token_symbol, :string) + field(:amount, :decimal) + field(:block_number, :integer) + field(:block_timestamp, :utc_datetime_usec) + + timestamps() + end + + @doc """ + Checks that the `attrs` are valid. + """ + @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() + def changeset(%__MODULE__{} = operations, attrs \\ %{}) do + operations + |> cast(attrs, @required_attrs ++ @optional_attrs) + |> validate_required(@required_attrs) + |> unique_constraint([:type, :index]) + end +end diff --git a/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_table.exs b/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_table.exs new file mode 100644 index 000000000000..fbd9b131a80e --- /dev/null +++ b/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_table.exs @@ -0,0 +1,24 @@ +defmodule Explorer.Repo.PolygonZkevm.Migrations.AddBridgeTable do + use Ecto.Migration + + def change do + execute( + "CREATE TYPE zkevm_bridge_op_type AS ENUM ('deposit', 'withdrawal')", + "DROP TYPE zkevm_bridge_op_type" + ) + + create table(:zkevm_bridge, primary_key: false) do + add(:type, :zkevm_bridge_op_type, null: false, primary_key: true) + add(:index, :integer, null: false, primary_key: true) + add(:l1_transaction_hash, :bytea, null: true, default: nil) + add(:l2_transaction_hash, :bytea, null: true, default: nil) + add(:l1_token_address, :bytea, null: true, default: nil) + add(:l1_token_decimals, :smallint, null: true, default: nil) + add(:l1_token_symbol, :string, size: 16, null: true, default: nil) + add(:amount, :numeric, precision: 100, null: false) + add(:block_number, :bigint, null: true, default: nil) + add(:block_timestamp, :"timestamp without time zone", null: true, default: nil) + timestamps(null: false, type: :utc_datetime_usec) + end + end +end diff --git a/config/runtime.exs b/config/runtime.exs index d7061cd1794b..6b6fbc9cf14d 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -728,12 +728,12 @@ config :indexer, Indexer.Fetcher.Shibarium.L1.Supervisor, enabled: ConfigHelper. config :indexer, Indexer.Fetcher.Shibarium.L2.Supervisor, enabled: ConfigHelper.chain_type() == "shibarium" config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch, - chunk_size: ConfigHelper.parse_integer_env_var("INDEXER_ZKEVM_BATCHES_CHUNK_SIZE", 20), - recheck_interval: ConfigHelper.parse_integer_env_var("INDEXER_ZKEVM_BATCHES_RECHECK_INTERVAL", 60) + chunk_size: ConfigHelper.parse_integer_env_var("INDEXER_POLYGON_ZKEVM_BATCHES_CHUNK_SIZE", 20), + recheck_interval: ConfigHelper.parse_integer_env_var("INDEXER_POLYGON_ZKEVM_BATCHES_RECHECK_INTERVAL", 60) config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch.Supervisor, enabled: - ConfigHelper.chain_type() == "polygon_zkevm" && ConfigHelper.parse_bool_env_var("INDEXER_ZKEVM_BATCHES_ENABLED") + ConfigHelper.chain_type() == "polygon_zkevm" && ConfigHelper.parse_bool_env_var("INDEXER_POLYGON_ZKEVM_BATCHES_ENABLED") Code.require_file("#{config_env()}.exs", "config/runtime") From 1515055579f51f1d5d7c9fd77b38c929f40bb04e Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Tue, 12 Dec 2023 11:35:34 +0300 Subject: [PATCH 420/607] Add zkevm_bridge_l1_tokens table --- .../import/runner/zkevm/bridge_l1_tokens.ex | 101 ++++++++++++++++++ .../import/runner/zkevm/bridge_operations.ex | 10 +- .../chain/import/stage/block_referencing.ex | 4 +- .../lib/explorer/chain/zkevm/bridge.ex | 13 ++- .../explorer/chain/zkevm/bridge_l1_token.ex | 37 +++++++ ...s => 20231010093238_add_bridge_tables.exs} | 22 +++- 6 files changed, 168 insertions(+), 19 deletions(-) create mode 100644 apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_l1_tokens.ex create mode 100644 apps/explorer/lib/explorer/chain/zkevm/bridge_l1_token.ex rename apps/explorer/priv/polygon_zkevm/migrations/{20231010093238_add_bridge_table.exs => 20231010093238_add_bridge_tables.exs} (53%) diff --git a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_l1_tokens.ex b/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_l1_tokens.ex new file mode 100644 index 000000000000..3053eca2ffb0 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_l1_tokens.ex @@ -0,0 +1,101 @@ +defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeL1Tokens do + @moduledoc """ + Bulk imports `t:Explorer.Chain.Zkevm.BridgeL1Token.t/0`. + """ + + require Ecto.Query + + import Ecto.Query, only: [from: 2] + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.Zkevm.BridgeL1Token + alias Explorer.Prometheus.Instrumenter + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [BridgeL1Token.t()] + + @impl Import.Runner + def ecto_schema_module, do: BridgeL1Token + + @impl Import.Runner + def option_key, do: :zkevm_bridge_l1_tokens + + @impl Import.Runner + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + Multi.run(multi, :insert_zkevm_bridge_l1_tokens, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :zkevm_bridge_l1_tokens, + :zkevm_bridge_l1_tokens + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [BridgeL1Token.t()]} + | {:error, [Changeset.t()]} + def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) + + # Enforce BridgeL1Token ShareLocks order (see docs: sharelock.md) + ordered_changes_list = Enum.sort_by(changes_list, &{&1.id}) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + conflict_target: :address, + on_conflict: on_conflict, + for: BridgeL1Token, + returning: true, + timeout: timeout, + timestamps: timestamps + ) + + {:ok, inserted} + end + + defp default_on_conflict do + from( + t in BridgeL1Token, + update: [ + set: [ + decimals: fragment("EXCLUDED.decimals"), + symbol: fragment("EXCLUDED.symbol"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", t.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", t.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.decimals, EXCLUDED.symbol) IS DISTINCT FROM (?, ?)", + t.decimals, + t.symbol + ) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex b/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex index f0f7e916615a..fc91cd4e4fe4 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex @@ -88,9 +88,7 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeOperations do # Don't update `index` as it is part of the composite primary key and used for the conflict target l1_transaction_hash: fragment("EXCLUDED.l1_transaction_hash"), l2_transaction_hash: fragment("EXCLUDED.l2_transaction_hash"), - l1_token_address: fragment("EXCLUDED.l1_token_address"), - l1_token_decimals: fragment("EXCLUDED.l1_token_decimals"), - l1_token_symbol: fragment("EXCLUDED.l1_token_symbol"), + l1_token_id: fragment("EXCLUDED.l1_token_id"), amount: fragment("EXCLUDED.amount"), block_number: fragment("EXCLUDED.block_number"), block_timestamp: fragment("EXCLUDED.block_timestamp"), @@ -100,12 +98,10 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeOperations do ], where: fragment( - "(EXCLUDED.l1_transaction_hash, EXCLUDED.l2_transaction_hash, EXCLUDED.l1_token_address, EXCLUDED.l1_token_decimals, EXCLUDED.l1_token_symbol, EXCLUDED.amount, EXCLUDED.block_number, EXCLUDED.block_timestamp) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?)", + "(EXCLUDED.l1_transaction_hash, EXCLUDED.l2_transaction_hash, EXCLUDED.l1_token_id, EXCLUDED.amount, EXCLUDED.block_number, EXCLUDED.block_timestamp) IS DISTINCT FROM (?, ?, ?, ?, ?, ?)", op.l1_transaction_hash, op.l2_transaction_hash, - op.l1_token_address, - op.l1_token_decimals, - op.l1_token_symbol, + op.l1_token_id, op.amount, op.block_number, op.block_timestamp diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex index 845231277594..4baec4a08240 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex @@ -28,7 +28,9 @@ defmodule Explorer.Chain.Import.Stage.BlockReferencing do @polygon_zkevm_runners [ Runner.Zkevm.LifecycleTransactions, Runner.Zkevm.TransactionBatches, - Runner.Zkevm.BatchTransactions + Runner.Zkevm.BatchTransactions, + Runner.Zkevm.BridgeL1Tokens, + Runner.Zkevm.BridgeOperations ] @shibarium_runners [ diff --git a/apps/explorer/lib/explorer/chain/zkevm/bridge.ex b/apps/explorer/lib/explorer/chain/zkevm/bridge.ex index 94d43c880067..55da9f0a53af 100644 --- a/apps/explorer/lib/explorer/chain/zkevm/bridge.ex +++ b/apps/explorer/lib/explorer/chain/zkevm/bridge.ex @@ -4,8 +4,9 @@ defmodule Explorer.Chain.Zkevm.Bridge do use Explorer.Schema alias Explorer.Chain.{Block, Hash} + alias Explorer.Chain.Zkevm.BridgeL1Token - @optional_attrs ~w(l1_transaction_hash l2_transaction_hash l1_token_address l1_token_decimals l1_token_symbol block_number block_timestamp)a + @optional_attrs ~w(l1_transaction_hash l2_transaction_hash l1_token_id block_number block_timestamp)a @required_attrs ~w(type index amount)a @@ -14,9 +15,8 @@ defmodule Explorer.Chain.Zkevm.Bridge do index: non_neg_integer(), l1_transaction_hash: Hash.t() | nil, l2_transaction_hash: Hash.t() | nil, - l1_token_address: Hash.Address.t() | nil, - l1_token_decimals: non_neg_integer() | nil, - l1_token_symbol: String.t() | nil, + l1_token: %Ecto.Association.NotLoaded{} | BridgeL1Token.t() | nil, + l1_token_id: non_neg_integer() | nil, amount: Decimal.t(), block_number: Block.block_number() | nil, block_timestamp: DateTime.t() | nil @@ -28,9 +28,7 @@ defmodule Explorer.Chain.Zkevm.Bridge do field(:index, :integer, primary_key: true) field(:l1_transaction_hash, Hash.Full) field(:l2_transaction_hash, Hash.Full) - field(:l1_token_address, Hash.Address) - field(:l1_token_decimals, :integer) - field(:l1_token_symbol, :string) + belongs_to(:l1_token, BridgeL1Token, foreign_key: :l1_token_id, references: :id, type: :integer) field(:amount, :decimal) field(:block_number, :integer) field(:block_timestamp, :utc_datetime_usec) @@ -47,5 +45,6 @@ defmodule Explorer.Chain.Zkevm.Bridge do |> cast(attrs, @required_attrs ++ @optional_attrs) |> validate_required(@required_attrs) |> unique_constraint([:type, :index]) + |> foreign_key_constraint(:l1_token_id) end end diff --git a/apps/explorer/lib/explorer/chain/zkevm/bridge_l1_token.ex b/apps/explorer/lib/explorer/chain/zkevm/bridge_l1_token.ex new file mode 100644 index 000000000000..d54951eb3f64 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/zkevm/bridge_l1_token.ex @@ -0,0 +1,37 @@ +defmodule Explorer.Chain.Zkevm.BridgeL1Token do + @moduledoc "Models a bridge token on L1 for Polygon zkEVM." + + use Explorer.Schema + + alias Explorer.Chain.Hash + + @optional_attrs ~w(decimals symbol)a + + @required_attrs ~w(address)a + + @type t :: %__MODULE__{ + address: Hash.Address.t(), + decimals: non_neg_integer() | nil, + symbol: String.t() | nil + } + + @primary_key {:id, :id, autogenerate: true} + schema "zkevm_bridge_l1_tokens" do + field(:address, Hash.Address) + field(:decimals, :integer) + field(:symbol, :string) + + timestamps() + end + + @doc """ + Checks that the `attrs` are valid. + """ + @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() + def changeset(%__MODULE__{} = tokens, attrs \\ %{}) do + tokens + |> cast(attrs, @required_attrs ++ @optional_attrs) + |> validate_required(@required_attrs) + |> unique_constraint(:id) + end +end diff --git a/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_table.exs b/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs similarity index 53% rename from apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_table.exs rename to apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs index fbd9b131a80e..696999d416bf 100644 --- a/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_table.exs +++ b/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs @@ -1,7 +1,17 @@ -defmodule Explorer.Repo.PolygonZkevm.Migrations.AddBridgeTable do +defmodule Explorer.Repo.PolygonZkevm.Migrations.AddBridgeTables do use Ecto.Migration def change do + create table(:zkevm_bridge_l1_tokens, primary_key: false) do + add(:id, :identity, primary_key: true, start_value: 1, increment: 1) + add(:address, :bytea, null: false) + add(:decimals, :smallint, null: true, default: nil) + add(:symbol, :string, size: 16, null: true, default: nil) + timestamps(null: false, type: :utc_datetime_usec) + end + + create(unique_index(:zkevm_bridge_l1_tokens, :address)) + execute( "CREATE TYPE zkevm_bridge_op_type AS ENUM ('deposit', 'withdrawal')", "DROP TYPE zkevm_bridge_op_type" @@ -12,9 +22,13 @@ defmodule Explorer.Repo.PolygonZkevm.Migrations.AddBridgeTable do add(:index, :integer, null: false, primary_key: true) add(:l1_transaction_hash, :bytea, null: true, default: nil) add(:l2_transaction_hash, :bytea, null: true, default: nil) - add(:l1_token_address, :bytea, null: true, default: nil) - add(:l1_token_decimals, :smallint, null: true, default: nil) - add(:l1_token_symbol, :string, size: 16, null: true, default: nil) + + add( + :l1_token_id, + references(:zkevm_bridge_l1_tokens, on_delete: :restrict, on_update: :update_all, type: :identity), + null: true + ) + add(:amount, :numeric, precision: 100, null: false) add(:block_number, :bigint, null: true, default: nil) add(:block_timestamp, :"timestamp without time zone", null: true, default: nil) From 1fda56ef15a02f3d2f2b910cb27458934026d1e1 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Wed, 13 Dec 2023 14:11:27 +0300 Subject: [PATCH 421/607] Small changes --- .../20231010093238_add_bridge_tables.exs | 2 +- .../lib/indexer/fetcher/zkevm/bridge_l1.ex | 688 ++++++++++++++++++ 2 files changed, 689 insertions(+), 1 deletion(-) create mode 100644 apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex diff --git a/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs b/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs index 696999d416bf..42b3fc597613 100644 --- a/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs +++ b/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs @@ -3,7 +3,7 @@ defmodule Explorer.Repo.PolygonZkevm.Migrations.AddBridgeTables do def change do create table(:zkevm_bridge_l1_tokens, primary_key: false) do - add(:id, :identity, primary_key: true, start_value: 1, increment: 1) + add(:id, :identity, primary_key: true, start_value: 0, increment: 1) add(:address, :bytea, null: false) add(:decimals, :smallint, null: true, default: nil) add(:symbol, :string, size: 16, null: true, default: nil) diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex new file mode 100644 index 000000000000..20679290e94d --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex @@ -0,0 +1,688 @@ +defmodule Indexer.Fetcher.Zkevm.BridgeL1 do + @moduledoc """ + Fills zkevm_bridge DB table. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + import Ecto.Query + + import EthereumJSONRPC, + only: [ + integer_to_quantity: 1, + json_rpc: 2, + quantity_to_integer: 1, + request: 1 + ] + + import Explorer.Helper, only: [parse_integer: 1, decode_data: 2] + + alias EthereumJSONRPC.Block.ByNumber + alias EthereumJSONRPC.Blocks + alias Explorer.Chain.Zkevm.{Bridge, BridgeL1Token} + alias Explorer.{Chain, Repo} + alias Indexer.{BoundQueue, Helper} + + @block_check_interval_range_size 100 + @eth_get_logs_range_size 1000 + @fetcher_name :zkevm_bridge_l1 + @empty_hash "0x0000000000000000000000000000000000000000000000000000000000000000" + + # 32-byte signature of the event BridgeEvent(uint8 leafType, uint32 originNetwork, address originAddress, uint32 destinationNetwork, address destinationAddress, uint256 amount, bytes metadata, uint32 depositCount) + @bridge_event "0x501781209a1f8899323b96b4ef08b168df93e0a90c673d1e4cce39366cb62f9b" + + # 32-byte signature of the event ClaimEvent(uint32 index, uint32 originNetwork, address originAddress, address destinationAddress, uint256 amount) + @claim_event "0x25308c93ceeed162da955b3f7ce3e3f93606579e40fb92029faa9efe27545983" + + def child_spec(start_link_arguments) do + spec = %{ + id: __MODULE__, + start: {__MODULE__, :start_link, start_link_arguments}, + restart: :transient, + type: :worker + } + + Supervisor.child_spec(spec, []) + end + + def start_link(args, gen_server_options \\ []) do + GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__)) + end + + @impl GenServer + def init(_args) do + {:ok, %{}, {:continue, :ok}} + end + + @impl GenServer + def handle_continue(_, state) do + Logger.metadata(fetcher: @fetcher_name) + # two seconds pause needed to avoid exceeding Supervisor restart intensity when DB issues + Process.send_after(self(), :init_with_delay, 2000) + {:noreply, state} + end + + @impl GenServer + def handle_info(:init_with_delay, _state) do + env = Application.get_all_env(:indexer)[__MODULE__] + + with {:start_block_undefined, false} <- {:start_block_undefined, is_nil(env[:start_block])}, + rpc = env[:rpc], + {:rpc_undefined, false} <- {:rpc_undefined, is_nil(rpc)}, + {:deposit_manager_address_is_valid, true} <- + {:deposit_manager_address_is_valid, Helper.is_address_correct?(env[:deposit_manager_proxy])}, + {:ether_predicate_address_is_valid, true} <- + {:ether_predicate_address_is_valid, Helper.is_address_correct?(env[:ether_predicate_proxy])}, + {:erc20_predicate_address_is_valid, true} <- + {:erc20_predicate_address_is_valid, Helper.is_address_correct?(env[:erc20_predicate_proxy])}, + {:erc721_predicate_address_is_valid, true} <- + {:erc721_predicate_address_is_valid, + is_nil(env[:erc721_predicate_proxy]) or Helper.is_address_correct?(env[:erc721_predicate_proxy])}, + {:erc1155_predicate_address_is_valid, true} <- + {:erc1155_predicate_address_is_valid, + is_nil(env[:erc1155_predicate_proxy]) or Helper.is_address_correct?(env[:erc1155_predicate_proxy])}, + {:withdraw_manager_address_is_valid, true} <- + {:withdraw_manager_address_is_valid, Helper.is_address_correct?(env[:withdraw_manager_proxy])}, + start_block = parse_integer(env[:start_block]), + false <- is_nil(start_block), + true <- start_block > 0, + {last_l1_block_number, last_l1_transaction_hash} <- get_last_l1_item(), + {:start_block_valid, true} <- + {:start_block_valid, start_block <= last_l1_block_number || last_l1_block_number == 0}, + json_rpc_named_arguments = json_rpc_named_arguments(rpc), + {:ok, last_l1_tx} <- Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), + {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)}, + {:ok, block_check_interval, latest_block} <- get_block_check_interval(json_rpc_named_arguments), + {:start_block_valid, true} <- {:start_block_valid, start_block <= latest_block} do + Process.send(self(), :reorg_monitor, []) + Process.send(self(), :continue, []) + + {:noreply, + %{ + deposit_manager_proxy: env[:deposit_manager_proxy], + ether_predicate_proxy: env[:ether_predicate_proxy], + erc20_predicate_proxy: env[:erc20_predicate_proxy], + erc721_predicate_proxy: env[:erc721_predicate_proxy], + erc1155_predicate_proxy: env[:erc1155_predicate_proxy], + withdraw_manager_proxy: env[:withdraw_manager_proxy], + block_check_interval: block_check_interval, + start_block: max(start_block, last_l1_block_number), + end_block: latest_block, + json_rpc_named_arguments: json_rpc_named_arguments, + reorg_monitor_prev_latest: 0 + }} + else + {:start_block_undefined, true} -> + # the process shouldn't start if the start block is not defined + {:stop, :normal, %{}} + + {:rpc_undefined, true} -> + Logger.error("L1 RPC URL is not defined.") + {:stop, :normal, %{}} + + {:deposit_manager_address_is_valid, false} -> + Logger.error("DepositManagerProxy contract address is invalid or not defined.") + {:stop, :normal, %{}} + + {:ether_predicate_address_is_valid, false} -> + Logger.error("EtherPredicateProxy contract address is invalid or not defined.") + {:stop, :normal, %{}} + + {:erc20_predicate_address_is_valid, false} -> + Logger.error("ERC20PredicateProxy contract address is invalid or not defined.") + {:stop, :normal, %{}} + + {:erc721_predicate_address_is_valid, false} -> + Logger.error("ERC721PredicateProxy contract address is invalid.") + {:stop, :normal, %{}} + + {:erc1155_predicate_address_is_valid, false} -> + Logger.error("ERC1155PredicateProxy contract address is invalid.") + {:stop, :normal, %{}} + + {:withdraw_manager_address_is_valid, false} -> + Logger.error("WithdrawManagerProxy contract address is invalid or not defined.") + {:stop, :normal, %{}} + + {:start_block_valid, false} -> + Logger.error("Invalid L1 Start Block value. Please, check the value and shibarium_bridge table.") + {:stop, :normal, %{}} + + {:error, error_data} -> + Logger.error( + "Cannot get last L1 transaction from RPC by its hash, latest block, or block timestamp by its number due to RPC error: #{inspect(error_data)}" + ) + + {:stop, :normal, %{}} + + {:l1_tx_not_found, true} -> + Logger.error( + "Cannot find last L1 transaction from RPC by its hash. Probably, there was a reorg on L1 chain. Please, check shibarium_bridge table." + ) + + {:stop, :normal, %{}} + + _ -> + Logger.error("L1 Start Block is invalid or zero.") + {:stop, :normal, %{}} + end + end + + @impl GenServer + def handle_info( + :reorg_monitor, + %{ + block_check_interval: block_check_interval, + json_rpc_named_arguments: json_rpc_named_arguments, + reorg_monitor_prev_latest: prev_latest + } = state + ) do + {:ok, latest} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + + if latest < prev_latest do + Logger.warning("Reorg detected: previous latest block ##{prev_latest}, current latest block ##{latest}.") + reorg_block_push(latest) + end + + Process.send_after(self(), :reorg_monitor, block_check_interval) + + {:noreply, %{state | reorg_monitor_prev_latest: latest}} + end + + @impl GenServer + def handle_info( + :continue, + %{ + deposit_manager_proxy: deposit_manager_proxy, + ether_predicate_proxy: ether_predicate_proxy, + erc20_predicate_proxy: erc20_predicate_proxy, + erc721_predicate_proxy: erc721_predicate_proxy, + erc1155_predicate_proxy: erc1155_predicate_proxy, + withdraw_manager_proxy: withdraw_manager_proxy, + block_check_interval: block_check_interval, + start_block: start_block, + end_block: end_block, + json_rpc_named_arguments: json_rpc_named_arguments + } = state + ) do + time_before = Timex.now() + + last_written_block = + start_block..end_block + |> Enum.chunk_every(@eth_get_logs_range_size) + |> Enum.reduce_while(start_block - 1, fn current_chunk, _ -> + chunk_start = List.first(current_chunk) + chunk_end = List.last(current_chunk) + + if chunk_start <= chunk_end do + Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, "L1") + + operations = + {chunk_start, chunk_end} + |> get_logs_all( + deposit_manager_proxy, + ether_predicate_proxy, + erc20_predicate_proxy, + erc721_predicate_proxy, + erc1155_predicate_proxy, + withdraw_manager_proxy, + json_rpc_named_arguments + ) + |> prepare_operations(json_rpc_named_arguments) + + {:ok, _} = + operations + |> get_import_options() + |> Chain.import() + + Helper.log_blocks_chunk_handling( + chunk_start, + chunk_end, + start_block, + end_block, + "#{Enum.count(operations)} L1 operation(s)", + "L1" + ) + end + + reorg_block = reorg_block_pop() + + if !is_nil(reorg_block) && reorg_block > 0 do + reorg_handle(reorg_block) + {:halt, if(reorg_block <= chunk_end, do: reorg_block - 1, else: chunk_end)} + else + {:cont, chunk_end} + end + end) + + new_start_block = last_written_block + 1 + {:ok, new_end_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + + delay = + if new_end_block == last_written_block do + # there is no new block, so wait for some time to let the chain issue the new block + max(block_check_interval - Timex.diff(Timex.now(), time_before, :milliseconds), 0) + else + 0 + end + + Process.send_after(self(), :continue, delay) + + {:noreply, %{state | start_block: new_start_block, end_block: new_end_block}} + end + + @impl GenServer + def handle_info({ref, _result}, state) do + Process.demonitor(ref, [:flush]) + {:noreply, state} + end + + defp filter_deposit_events(events) do + Enum.filter(events, fn event -> + topic0 = Enum.at(event["topics"], 0) + is_deposit(topic0) + end) + end + + defp get_block_check_interval(json_rpc_named_arguments) do + with {:ok, latest_block} <- Helper.get_block_number_by_tag("latest", json_rpc_named_arguments), + first_block = max(latest_block - @block_check_interval_range_size, 1), + {:ok, first_block_timestamp} <- Helper.get_block_timestamp_by_number(first_block, json_rpc_named_arguments), + {:ok, last_safe_block_timestamp} <- + Helper.get_block_timestamp_by_number(latest_block, json_rpc_named_arguments) do + block_check_interval = + ceil((last_safe_block_timestamp - first_block_timestamp) / (latest_block - first_block) * 1000 / 2) + + Logger.info("Block check interval is calculated as #{block_check_interval} ms.") + {:ok, block_check_interval, latest_block} + else + {:error, error} -> + {:error, "Failed to calculate block check interval due to #{inspect(error)}"} + end + end + + defp get_blocks_by_events(events, json_rpc_named_arguments, retries) do + request = + events + |> Enum.reduce(%{}, fn event, acc -> + Map.put(acc, event["blockNumber"], 0) + end) + |> Stream.map(fn {block_number, _} -> %{number: block_number} end) + |> Stream.with_index() + |> Enum.into(%{}, fn {params, id} -> {id, params} end) + |> Blocks.requests(&ByNumber.request(&1, false, false)) + + error_message = &"Cannot fetch blocks with batch request. Error: #{inspect(&1)}. Request: #{inspect(request)}" + + case Helper.repeated_call(&json_rpc/2, [request, json_rpc_named_arguments], error_message, retries) do + {:ok, results} -> Enum.map(results, fn %{result: result} -> result end) + {:error, _} -> [] + end + end + + defp get_import_options(operations) do + # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise + if System.get_env("CHAIN_TYPE") == "shibarium" do + %{ + shibarium_bridge_operations: %{params: prepare_insert_items(operations, __MODULE__)}, + timeout: :infinity + } + else + %{} + end + end + + defp get_last_l1_item do + query = + from(sb in Bridge, + select: {sb.l1_block_number, sb.l1_transaction_hash}, + where: not is_nil(sb.l1_block_number), + order_by: [desc: sb.l1_block_number], + limit: 1 + ) + + query + |> Repo.one() + |> Kernel.||({0, nil}) + end + + defp get_logs(from_block, to_block, address, topics, json_rpc_named_arguments, retries \\ 100_000_000) do + processed_from_block = if is_integer(from_block), do: integer_to_quantity(from_block), else: from_block + processed_to_block = if is_integer(to_block), do: integer_to_quantity(to_block), else: to_block + + req = + request(%{ + id: 0, + method: "eth_getLogs", + params: [ + %{ + :fromBlock => processed_from_block, + :toBlock => processed_to_block, + :address => address, + :topics => topics + } + ] + }) + + error_message = &"Cannot fetch logs for the block range #{from_block}..#{to_block}. Error: #{inspect(&1)}" + + Helper.repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) + end + + defp get_logs_all( + {chunk_start, chunk_end}, + deposit_manager_proxy, + ether_predicate_proxy, + erc20_predicate_proxy, + erc721_predicate_proxy, + erc1155_predicate_proxy, + withdraw_manager_proxy, + json_rpc_named_arguments + ) do + {:ok, known_tokens_result} = + get_logs( + chunk_start, + chunk_end, + [deposit_manager_proxy, ether_predicate_proxy, erc20_predicate_proxy, withdraw_manager_proxy], + [ + [ + @new_deposit_block_event, + @locked_ether_event, + @locked_erc20_event, + @locked_erc721_event, + @locked_erc721_batch_event, + @locked_batch_erc1155_event, + @withdraw_event, + @exited_ether_event + ] + ], + json_rpc_named_arguments + ) + + contract_addresses = + if is_nil(erc721_predicate_proxy) do + [pad_address_hash(erc20_predicate_proxy)] + else + [pad_address_hash(erc20_predicate_proxy), pad_address_hash(erc721_predicate_proxy)] + end + + {:ok, unknown_erc20_erc721_tokens_result} = + get_logs( + chunk_start, + chunk_end, + nil, + [ + @transfer_event, + contract_addresses + ], + json_rpc_named_arguments + ) + + {:ok, unknown_erc1155_tokens_result} = + if is_nil(erc1155_predicate_proxy) do + {:ok, []} + else + get_logs( + chunk_start, + chunk_end, + nil, + [ + [@transfer_single_event, @transfer_batch_event], + nil, + pad_address_hash(erc1155_predicate_proxy) + ], + json_rpc_named_arguments + ) + end + + known_tokens_result ++ unknown_erc20_erc721_tokens_result ++ unknown_erc1155_tokens_result + end + + defp get_op_user(topic0, event) do + cond do + Enum.member?([@new_deposit_block_event, @exited_ether_event], topic0) -> + truncate_address_hash(Enum.at(event["topics"], 1)) + + Enum.member?( + [ + @locked_ether_event, + @locked_erc20_event, + @locked_erc721_event, + @locked_erc721_batch_event, + @locked_batch_erc1155_event, + @withdraw_event, + @transfer_event + ], + topic0 + ) -> + truncate_address_hash(Enum.at(event["topics"], 2)) + + Enum.member?([@transfer_single_event, @transfer_batch_event], topic0) -> + truncate_address_hash(Enum.at(event["topics"], 3)) + end + end + + defp get_op_amounts(topic0, event) do + cond do + topic0 == @new_deposit_block_event -> + [amount_or_nft_id, deposit_block_id] = decode_data(event["data"], [{:uint, 256}, {:uint, 256}]) + {[amount_or_nft_id], deposit_block_id} + + topic0 == @transfer_event -> + indexed_token_id = Enum.at(event["topics"], 3) + + if is_nil(indexed_token_id) do + {decode_data(event["data"], [{:uint, 256}]), 0} + else + {[quantity_to_integer(indexed_token_id)], 0} + end + + Enum.member?( + [ + @locked_ether_event, + @locked_erc20_event, + @locked_erc721_event, + @withdraw_event, + @exited_ether_event + ], + topic0 + ) -> + {decode_data(event["data"], [{:uint, 256}]), 0} + + topic0 == @locked_erc721_batch_event -> + [ids] = decode_data(event["data"], [{:array, {:uint, 256}}]) + {ids, 0} + + true -> + {[nil], 0} + end + end + + defp get_op_erc1155_data(topic0, event) do + cond do + Enum.member?([@locked_batch_erc1155_event, @transfer_batch_event], topic0) -> + [ids, amounts] = decode_data(event["data"], [{:array, {:uint, 256}}, {:array, {:uint, 256}}]) + {ids, amounts} + + Enum.member?([@transfer_single_event], topic0) -> + [id, amount] = decode_data(event["data"], [{:uint, 256}, {:uint, 256}]) + {[id], [amount]} + + true -> + {[], []} + end + end + + defp is_deposit(topic0) do + Enum.member?( + [ + @new_deposit_block_event, + @locked_ether_event, + @locked_erc20_event, + @locked_erc721_event, + @locked_erc721_batch_event, + @locked_batch_erc1155_event + ], + topic0 + ) + end + + defp json_rpc_named_arguments(rpc_url) do + [ + transport: EthereumJSONRPC.HTTP, + transport_options: [ + http: EthereumJSONRPC.HTTP.HTTPoison, + url: rpc_url, + http_options: [ + recv_timeout: :timer.minutes(10), + timeout: :timer.minutes(10), + hackney: [pool: :ethereum_jsonrpc] + ] + ] + ] + end + + defp prepare_operations(events, json_rpc_named_arguments) do + timestamps = + events + |> filter_deposit_events() + |> get_blocks_by_events(json_rpc_named_arguments, 100_000_000) + |> Enum.reduce(%{}, fn block, acc -> + block_number = quantity_to_integer(Map.get(block, "number")) + {:ok, timestamp} = DateTime.from_unix(quantity_to_integer(Map.get(block, "timestamp"))) + Map.put(acc, block_number, timestamp) + end) + + events + |> Enum.map(fn event -> + topic0 = Enum.at(event["topics"], 0) + + user = get_op_user(topic0, event) + {amounts_or_ids, operation_id} = get_op_amounts(topic0, event) + {erc1155_ids, erc1155_amounts} = get_op_erc1155_data(topic0, event) + + l1_block_number = quantity_to_integer(event["blockNumber"]) + + {operation_type, timestamp} = + if is_deposit(topic0) do + {:deposit, Map.get(timestamps, l1_block_number)} + else + {:withdrawal, nil} + end + + token_type = + cond do + Enum.member?([@new_deposit_block_event, @withdraw_event], topic0) -> + "bone" + + Enum.member?([@locked_ether_event, @exited_ether_event], topic0) -> + "eth" + + true -> + "other" + end + + Enum.map(amounts_or_ids, fn amount_or_id -> + %{ + user: user, + amount_or_id: amount_or_id, + erc1155_ids: if(Enum.empty?(erc1155_ids), do: nil, else: erc1155_ids), + erc1155_amounts: if(Enum.empty?(erc1155_amounts), do: nil, else: erc1155_amounts), + l1_transaction_hash: event["transactionHash"], + l1_block_number: l1_block_number, + l2_transaction_hash: @empty_hash, + operation_hash: calc_operation_hash(user, amount_or_id, erc1155_ids, erc1155_amounts, operation_id), + operation_type: operation_type, + token_type: token_type, + timestamp: timestamp + } + end) + end) + |> List.flatten() + end + + defp pad_address_hash(address) do + "0x" <> + (address + |> String.trim_leading("0x") + |> String.pad_leading(64, "0")) + end + + defp truncate_address_hash("0x000000000000000000000000" <> truncated_hash) do + "0x#{truncated_hash}" + end + + defp reorg_block_pop do + table_name = reorg_table_name(@fetcher_name) + + case BoundQueue.pop_front(reorg_queue_get(table_name)) do + {:ok, {block_number, updated_queue}} -> + :ets.insert(table_name, {:queue, updated_queue}) + block_number + + {:error, :empty} -> + nil + end + end + + defp reorg_block_push(block_number) do + table_name = reorg_table_name(@fetcher_name) + {:ok, updated_queue} = BoundQueue.push_back(reorg_queue_get(table_name), block_number) + :ets.insert(table_name, {:queue, updated_queue}) + end + + defp reorg_handle(reorg_block) do + {deleted_count, _} = + Repo.delete_all(from(sb in Bridge, where: sb.l1_block_number >= ^reorg_block and is_nil(sb.l2_transaction_hash))) + + {updated_count1, _} = + Repo.update_all( + from(sb in Bridge, + where: + sb.l1_block_number >= ^reorg_block and not is_nil(sb.l2_transaction_hash) and + sb.operation_type == :deposit + ), + set: [timestamp: nil] + ) + + {updated_count2, _} = + Repo.update_all( + from(sb in Bridge, where: sb.l1_block_number >= ^reorg_block and not is_nil(sb.l2_transaction_hash)), + set: [l1_transaction_hash: nil, l1_block_number: nil] + ) + + updated_count = max(updated_count1, updated_count2) + + if deleted_count > 0 or updated_count > 0 do + Logger.warning( + "As L1 reorg was detected, some rows with l1_block_number >= #{reorg_block} were affected (removed or updated) in the shibarium_bridge table. Number of removed rows: #{deleted_count}. Number of updated rows: >= #{updated_count}." + ) + end + end + + defp reorg_queue_get(table_name) do + if :ets.whereis(table_name) == :undefined do + :ets.new(table_name, [ + :set, + :named_table, + :public, + read_concurrency: true, + write_concurrency: true + ]) + end + + with info when info != :undefined <- :ets.info(table_name), + [{_, value}] <- :ets.lookup(table_name, :queue) do + value + else + _ -> %BoundQueue{} + end + end + + defp reorg_table_name(fetcher_name) do + :"#{fetcher_name}#{:_reorgs}" + end +end From 0092b1a4c3690696486649d3bf30b66e0766b122 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Wed, 13 Dec 2023 15:37:25 +0300 Subject: [PATCH 422/607] Draft --- .../lib/indexer/fetcher/zkevm/bridge_l1.ex | 86 +++++++------------ apps/indexer/lib/indexer/supervisor.ex | 8 +- config/runtime.exs | 15 ++++ config/runtime/prod.exs | 2 - 4 files changed, 49 insertions(+), 62 deletions(-) diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex index 20679290e94d..7f5564175513 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex @@ -72,45 +72,26 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do with {:start_block_undefined, false} <- {:start_block_undefined, is_nil(env[:start_block])}, rpc = env[:rpc], {:rpc_undefined, false} <- {:rpc_undefined, is_nil(rpc)}, - {:deposit_manager_address_is_valid, true} <- - {:deposit_manager_address_is_valid, Helper.is_address_correct?(env[:deposit_manager_proxy])}, - {:ether_predicate_address_is_valid, true} <- - {:ether_predicate_address_is_valid, Helper.is_address_correct?(env[:ether_predicate_proxy])}, - {:erc20_predicate_address_is_valid, true} <- - {:erc20_predicate_address_is_valid, Helper.is_address_correct?(env[:erc20_predicate_proxy])}, - {:erc721_predicate_address_is_valid, true} <- - {:erc721_predicate_address_is_valid, - is_nil(env[:erc721_predicate_proxy]) or Helper.is_address_correct?(env[:erc721_predicate_proxy])}, - {:erc1155_predicate_address_is_valid, true} <- - {:erc1155_predicate_address_is_valid, - is_nil(env[:erc1155_predicate_proxy]) or Helper.is_address_correct?(env[:erc1155_predicate_proxy])}, - {:withdraw_manager_address_is_valid, true} <- - {:withdraw_manager_address_is_valid, Helper.is_address_correct?(env[:withdraw_manager_proxy])}, + {:bridge_contract_address_is_valid, true} <- {:bridge_contract_address_is_valid, Helper.is_address_correct?(env[:bridge_contract])}, start_block = parse_integer(env[:start_block]), false <- is_nil(start_block), true <- start_block > 0, {last_l1_block_number, last_l1_transaction_hash} <- get_last_l1_item(), - {:start_block_valid, true} <- - {:start_block_valid, start_block <= last_l1_block_number || last_l1_block_number == 0}, json_rpc_named_arguments = json_rpc_named_arguments(rpc), + {:ok, block_check_interval, safe_block, safe_block_is_latest} <- get_block_check_interval(json_rpc_named_arguments), + {:start_block_valid, true} <- {:start_block_valid, (start_block <= last_l1_block_number || last_l1_block_number == 0) && start_block <= safe_block}, {:ok, last_l1_tx} <- Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), - {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)}, - {:ok, block_check_interval, latest_block} <- get_block_check_interval(json_rpc_named_arguments), - {:start_block_valid, true} <- {:start_block_valid, start_block <= latest_block} do + {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)} do Process.send(self(), :reorg_monitor, []) Process.send(self(), :continue, []) {:noreply, %{ - deposit_manager_proxy: env[:deposit_manager_proxy], - ether_predicate_proxy: env[:ether_predicate_proxy], - erc20_predicate_proxy: env[:erc20_predicate_proxy], - erc721_predicate_proxy: env[:erc721_predicate_proxy], - erc1155_predicate_proxy: env[:erc1155_predicate_proxy], - withdraw_manager_proxy: env[:withdraw_manager_proxy], + bridge_contract: env[:bridge_contract], block_check_interval: block_check_interval, start_block: max(start_block, last_l1_block_number), - end_block: latest_block, + safe_block: safe_block, + safe_block_is_latest: safe_block_is_latest, json_rpc_named_arguments: json_rpc_named_arguments, reorg_monitor_prev_latest: 0 }} @@ -123,28 +104,8 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do Logger.error("L1 RPC URL is not defined.") {:stop, :normal, %{}} - {:deposit_manager_address_is_valid, false} -> - Logger.error("DepositManagerProxy contract address is invalid or not defined.") - {:stop, :normal, %{}} - - {:ether_predicate_address_is_valid, false} -> - Logger.error("EtherPredicateProxy contract address is invalid or not defined.") - {:stop, :normal, %{}} - - {:erc20_predicate_address_is_valid, false} -> - Logger.error("ERC20PredicateProxy contract address is invalid or not defined.") - {:stop, :normal, %{}} - - {:erc721_predicate_address_is_valid, false} -> - Logger.error("ERC721PredicateProxy contract address is invalid.") - {:stop, :normal, %{}} - - {:erc1155_predicate_address_is_valid, false} -> - Logger.error("ERC1155PredicateProxy contract address is invalid.") - {:stop, :normal, %{}} - - {:withdraw_manager_address_is_valid, false} -> - Logger.error("WithdrawManagerProxy contract address is invalid or not defined.") + {:bridge_contract_address_is_valid, false} -> + Logger.error("PolygonZkEVMBridge contract address is invalid or not defined.") {:stop, :normal, %{}} {:start_block_valid, false} -> @@ -160,7 +121,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do {:l1_tx_not_found, true} -> Logger.error( - "Cannot find last L1 transaction from RPC by its hash. Probably, there was a reorg on L1 chain. Please, check shibarium_bridge table." + "Cannot find last L1 transaction from RPC by its hash. Probably, there was a reorg on L1 chain. Please, check zkevm_bridge table." ) {:stop, :normal, %{}} @@ -288,16 +249,16 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do end defp get_block_check_interval(json_rpc_named_arguments) do - with {:ok, latest_block} <- Helper.get_block_number_by_tag("latest", json_rpc_named_arguments), - first_block = max(latest_block - @block_check_interval_range_size, 1), - {:ok, first_block_timestamp} <- Helper.get_block_timestamp_by_number(first_block, json_rpc_named_arguments), - {:ok, last_safe_block_timestamp} <- - Helper.get_block_timestamp_by_number(latest_block, json_rpc_named_arguments) do - block_check_interval = - ceil((last_safe_block_timestamp - first_block_timestamp) / (latest_block - first_block) * 1000 / 2) + {last_safe_block, safe_block_is_latest} = get_safe_block(json_rpc_named_arguments) + + first_block = max(latest_block - @block_check_interval_range_size, 1) + + with {:ok, first_block_timestamp} <- Helper.get_block_timestamp_by_number(first_block, json_rpc_named_arguments), + {:ok, last_safe_block_timestamp} <- Helper.get_block_timestamp_by_number(last_safe_block, json_rpc_named_arguments) do + block_check_interval = ceil((last_safe_block_timestamp - first_block_timestamp) / (last_safe_block - first_block) * 1000 / 2) Logger.info("Block check interval is calculated as #{block_check_interval} ms.") - {:ok, block_check_interval, latest_block} + {:ok, block_check_interval, last_safe_block, safe_block_is_latest} else {:error, error} -> {:error, "Failed to calculate block check interval due to #{inspect(error)}"} @@ -530,6 +491,17 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do ) end + defp get_safe_block(json_rpc_named_arguments) do + case Helper.get_block_number_by_tag("safe", json_rpc_named_arguments) do + {:ok, safe_block} -> + {safe_block, false} + + {:error, :not_found} -> + {:ok, latest_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + {latest_block, true} + end + end + defp json_rpc_named_arguments(rpc_url) do [ transport: EthereumJSONRPC.HTTP, diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index aabb9fa85b2c..9ced4dee8954 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -44,8 +44,6 @@ defmodule Indexer.Supervisor do Withdrawal } - alias Indexer.Fetcher.Zkevm.TransactionBatch - alias Indexer.Temporary.{ BlocksTransactionsMismatch, UncatalogedTokenTransfers, @@ -147,7 +145,11 @@ defmodule Indexer.Supervisor do [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), configure(Indexer.Fetcher.Shibarium.L1.Supervisor, [[memory_monitor: memory_monitor]]), - configure(TransactionBatch.Supervisor, [ + configure(Indexer.Fetcher.Zkevm.BridgeL1.Supervisor, [[memory_monitor: memory_monitor]]), + configure(Indexer.Fetcher.Zkevm.BridgeL2.Supervisor, [ + [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] + ]), + configure(Indexer.Fetcher.Zkevm.TransactionBatch.Supervisor, [ [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), {Indexer.Fetcher.Beacon.Blob.Supervisor, [[memory_monitor: memory_monitor]]}, diff --git a/config/runtime.exs b/config/runtime.exs index 6b6fbc9cf14d..9c8e13684b8f 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -727,6 +727,21 @@ config :indexer, Indexer.Fetcher.Shibarium.L1.Supervisor, enabled: ConfigHelper. config :indexer, Indexer.Fetcher.Shibarium.L2.Supervisor, enabled: ConfigHelper.chain_type() == "shibarium" +config :indexer, Indexer.Fetcher.Zkevm.BridgeL1, + rpc: System.get_env("INDEXER_POLYGON_ZKEVM_L1_RPC"), + start_block: System.get_env("INDEXER_POLYGON_ZKEVM_L1_BRIDGE_START_BLOCK"), + bridge_contract: System.get_env("INDEXER_POLYGON_ZKEVM_L1_BRIDGE_CONTRACT"), + native_symbol: System.get_env("INDEXER_POLYGON_ZKEVM_L1_BRIDGE_NATIVE_SYMBOL", "ETH"), + native_decimals: ConfigHelper.parse_integer_env_var("INDEXER_POLYGON_ZKEVM_L1_BRIDGE_NATIVE_DECIMALS", 18) + +config :indexer, Indexer.Fetcher.Zkevm.BridgeL1.Supervisor, enabled: ConfigHelper.chain_type() == "polygon_zkevm" + +config :indexer, Indexer.Fetcher.Zkevm.BridgeL2, + start_block: System.get_env("INDEXER_POLYGON_ZKEVM_L2_BRIDGE_START_BLOCK"), + bridge_contract: System.get_env("INDEXER_POLYGON_ZKEVM_L2_BRIDGE_CONTRACT"), + +config :indexer, Indexer.Fetcher.Zkevm.BridgeL2.Supervisor, enabled: ConfigHelper.chain_type() == "polygon_zkevm" + config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch, chunk_size: ConfigHelper.parse_integer_env_var("INDEXER_POLYGON_ZKEVM_BATCHES_CHUNK_SIZE", 20), recheck_interval: ConfigHelper.parse_integer_env_var("INDEXER_POLYGON_ZKEVM_BATCHES_RECHECK_INTERVAL", 60) diff --git a/config/runtime/prod.exs b/config/runtime/prod.exs index 42e253146bb4..21f40dc30a15 100644 --- a/config/runtime/prod.exs +++ b/config/runtime/prod.exs @@ -78,8 +78,6 @@ config :explorer, Explorer.Repo.PolygonEdge, # Configures PolygonZkevm database config :explorer, Explorer.Repo.PolygonZkevm, url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() From a285285b827e118caa7cee1f6fb794b90bef34cb Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Thu, 14 Dec 2023 15:53:31 +0300 Subject: [PATCH 423/607] Draft --- .../lib/indexer/fetcher/zkevm/bridge_l1.ex | 490 ++++++++---------- 1 file changed, 223 insertions(+), 267 deletions(-) diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex index 7f5564175513..097b23e7a6b4 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex @@ -22,8 +22,10 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do alias EthereumJSONRPC.Block.ByNumber alias EthereumJSONRPC.Blocks + alias Explorer.Chain.Hash alias Explorer.Chain.Zkevm.{Bridge, BridgeL1Token} alias Explorer.{Chain, Repo} + alias Explorer.SmartContract.Reader alias Indexer.{BoundQueue, Helper} @block_check_interval_range_size 100 @@ -37,6 +39,27 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do # 32-byte signature of the event ClaimEvent(uint32 index, uint32 originNetwork, address originAddress, address destinationAddress, uint256 amount) @claim_event "0x25308c93ceeed162da955b3f7ce3e3f93606579e40fb92029faa9efe27545983" + @erc20_abi [ + %{ + "constant" => true, + "inputs" => [], + "name" => "symbol", + "outputs" => [%{"name" => "", "type" => "string"}], + "payable" => false, + "stateMutability" => "view", + "type" => "function" + }, + %{ + "constant" => true, + "inputs" => [], + "name" => "decimals", + "outputs" => [%{"name" => "", "type" => "uint8"}], + "payable" => false, + "stateMutability" => "view", + "type" => "function" + } + ] + def child_spec(start_link_arguments) do spec = %{ id: __MODULE__, @@ -76,9 +99,9 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do start_block = parse_integer(env[:start_block]), false <- is_nil(start_block), true <- start_block > 0, - {last_l1_block_number, last_l1_transaction_hash} <- get_last_l1_item(), + {last_l1_block_number, last_l1_transaction_hash} = get_last_l1_item(), json_rpc_named_arguments = json_rpc_named_arguments(rpc), - {:ok, block_check_interval, safe_block, safe_block_is_latest} <- get_block_check_interval(json_rpc_named_arguments), + {:ok, block_check_interval, safe_block} <- get_block_check_interval(json_rpc_named_arguments), {:start_block_valid, true} <- {:start_block_valid, (start_block <= last_l1_block_number || last_l1_block_number == 0) && start_block <= safe_block}, {:ok, last_l1_tx} <- Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)} do @@ -87,13 +110,12 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do {:noreply, %{ - bridge_contract: env[:bridge_contract], block_check_interval: block_check_interval, - start_block: max(start_block, last_l1_block_number), - safe_block: safe_block, - safe_block_is_latest: safe_block_is_latest, + bridge_contract: env[:bridge_contract], json_rpc_named_arguments: json_rpc_named_arguments, - reorg_monitor_prev_latest: 0 + reorg_monitor_prev_latest: 0, + safe_block: safe_block, + start_block: max(start_block, last_l1_block_number) }} else {:start_block_undefined, true} -> @@ -109,7 +131,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do {:stop, :normal, %{}} {:start_block_valid, false} -> - Logger.error("Invalid L1 Start Block value. Please, check the value and shibarium_bridge table.") + Logger.error("Invalid L1 Start Block value. Please, check the value and zkevm_bridge table.") {:stop, :normal, %{}} {:error, error_data} -> @@ -157,15 +179,10 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do def handle_info( :continue, %{ - deposit_manager_proxy: deposit_manager_proxy, - ether_predicate_proxy: ether_predicate_proxy, - erc20_predicate_proxy: erc20_predicate_proxy, - erc721_predicate_proxy: erc721_predicate_proxy, - erc1155_predicate_proxy: erc1155_predicate_proxy, - withdraw_manager_proxy: withdraw_manager_proxy, + bridge_contract: bridge_contract, block_check_interval: block_check_interval, start_block: start_block, - end_block: end_block, + safe_block: end_block, json_rpc_named_arguments: json_rpc_named_arguments } = state ) do @@ -183,21 +200,13 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do operations = {chunk_start, chunk_end} - |> get_logs_all( - deposit_manager_proxy, - ether_predicate_proxy, - erc20_predicate_proxy, - erc721_predicate_proxy, - erc1155_predicate_proxy, - withdraw_manager_proxy, - json_rpc_named_arguments - ) + |> get_logs_all(bridge_contract, json_rpc_named_arguments) |> prepare_operations(json_rpc_named_arguments) - {:ok, _} = - operations - |> get_import_options() - |> Chain.import() + {:ok, _} = Chain.import(%{ + zkevm_bridge_operations: %{params: operations}, + timeout: :infinity + }) Helper.log_blocks_chunk_handling( chunk_start, @@ -241,24 +250,22 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do {:noreply, state} end - defp filter_deposit_events(events) do - Enum.filter(events, fn event -> - topic0 = Enum.at(event["topics"], 0) - is_deposit(topic0) - end) - end + defp atomized_key("symbol"), do: :symbol + defp atomized_key("decimals"), do: :decimals + defp atomized_key("95d89b41"), do: :symbol + defp atomized_key("313ce567"), do: :decimals defp get_block_check_interval(json_rpc_named_arguments) do - {last_safe_block, safe_block_is_latest} = get_safe_block(json_rpc_named_arguments) + {last_safe_block, _} = get_safe_block(json_rpc_named_arguments) - first_block = max(latest_block - @block_check_interval_range_size, 1) + first_block = max(last_safe_block - @block_check_interval_range_size, 1) - with {:ok, first_block_timestamp} <- Helper.get_block_timestamp_by_number(first_block, json_rpc_named_arguments), - {:ok, last_safe_block_timestamp} <- Helper.get_block_timestamp_by_number(last_safe_block, json_rpc_named_arguments) do + with {:ok, first_block_timestamp} <- Helper.get_block_timestamp_by_number(first_block, json_rpc_named_arguments, 100_000_000), + {:ok, last_safe_block_timestamp} <- Helper.get_block_timestamp_by_number(last_safe_block, json_rpc_named_arguments, 100_000_000) do block_check_interval = ceil((last_safe_block_timestamp - first_block_timestamp) / (last_safe_block - first_block) * 1000 / 2) Logger.info("Block check interval is calculated as #{block_check_interval} ms.") - {:ok, block_check_interval, last_safe_block, safe_block_is_latest} + {:ok, block_check_interval, last_safe_block} else {:error, error} -> {:error, "Failed to calculate block check interval due to #{inspect(error)}"} @@ -284,24 +291,12 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do end end - defp get_import_options(operations) do - # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise - if System.get_env("CHAIN_TYPE") == "shibarium" do - %{ - shibarium_bridge_operations: %{params: prepare_insert_items(operations, __MODULE__)}, - timeout: :infinity - } - else - %{} - end - end - defp get_last_l1_item do query = - from(sb in Bridge, - select: {sb.l1_block_number, sb.l1_transaction_hash}, - where: not is_nil(sb.l1_block_number), - order_by: [desc: sb.l1_block_number], + from(b in Bridge, + select: {b.l1_block_number, b.l1_transaction_hash}, + where: b.type == :deposit and not is_nil(b.block_number), + order_by: [desc: b.index], limit: 1 ) @@ -333,173 +328,123 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do Helper.repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) end - defp get_logs_all( - {chunk_start, chunk_end}, - deposit_manager_proxy, - ether_predicate_proxy, - erc20_predicate_proxy, - erc721_predicate_proxy, - erc1155_predicate_proxy, - withdraw_manager_proxy, - json_rpc_named_arguments - ) do - {:ok, known_tokens_result} = + defp get_logs_all({chunk_start, chunk_end}, bridge_contract, json_rpc_named_arguments) do + {:ok, result} = get_logs( chunk_start, chunk_end, - [deposit_manager_proxy, ether_predicate_proxy, erc20_predicate_proxy, withdraw_manager_proxy], - [ - [ - @new_deposit_block_event, - @locked_ether_event, - @locked_erc20_event, - @locked_erc721_event, - @locked_erc721_batch_event, - @locked_batch_erc1155_event, - @withdraw_event, - @exited_ether_event - ] - ], + bridge_contract, + [[@bridge_event, @claim_event]], json_rpc_named_arguments ) - contract_addresses = - if is_nil(erc721_predicate_proxy) do - [pad_address_hash(erc20_predicate_proxy)] - else - [pad_address_hash(erc20_predicate_proxy), pad_address_hash(erc721_predicate_proxy)] - end - - {:ok, unknown_erc20_erc721_tokens_result} = - get_logs( - chunk_start, - chunk_end, - nil, - [ - @transfer_event, - contract_addresses - ], - json_rpc_named_arguments - ) + result + end - {:ok, unknown_erc1155_tokens_result} = - if is_nil(erc1155_predicate_proxy) do - {:ok, []} - else - get_logs( - chunk_start, - chunk_end, - nil, - [ - [@transfer_single_event, @transfer_batch_event], - nil, - pad_address_hash(erc1155_predicate_proxy) - ], - json_rpc_named_arguments - ) - end + defp get_safe_block(json_rpc_named_arguments) do + case Helper.get_block_number_by_tag("safe", json_rpc_named_arguments) do + {:ok, safe_block} -> + {safe_block, false} - known_tokens_result ++ unknown_erc20_erc721_tokens_result ++ unknown_erc1155_tokens_result + {:error, :not_found} -> + {:ok, latest_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + {latest_block, true} + end end - defp get_op_user(topic0, event) do - cond do - Enum.member?([@new_deposit_block_event, @exited_ether_event], topic0) -> - truncate_address_hash(Enum.at(event["topics"], 1)) - - Enum.member?( - [ - @locked_ether_event, - @locked_erc20_event, - @locked_erc721_event, - @locked_erc721_batch_event, - @locked_batch_erc1155_event, - @withdraw_event, - @transfer_event - ], - topic0 - ) -> - truncate_address_hash(Enum.at(event["topics"], 2)) - - Enum.member?([@transfer_single_event, @transfer_batch_event], topic0) -> - truncate_address_hash(Enum.at(event["topics"], 3)) - end + defp get_token_data(token_addresses) do + # first, we're trying to read token data from the DB. + # if tokens are not in the DB, read them through RPC. + token_addresses + |> get_token_data_from_db() + |> get_token_data_from_rpc() end - defp get_op_amounts(topic0, event) do - cond do - topic0 == @new_deposit_block_event -> - [amount_or_nft_id, deposit_block_id] = decode_data(event["data"], [{:uint, 256}, {:uint, 256}]) - {[amount_or_nft_id], deposit_block_id} + defp get_token_data_from_db(token_addresses) do + # try to read token symbols and decimals from the database + query = + from( + t in BridgeL1Token, + where: t.address in ^token_addresses, + select: {t.address, t.decimals, t.symbol} + ) - topic0 == @transfer_event -> - indexed_token_id = Enum.at(event["topics"], 3) + token_data = + query + |> Repo.all() + |> Enum.reduce(%{}, fn {address, decimals, symbol}, acc -> + token_address = String.downcase(Hash.to_string(address)) + Map.put(acc, token_address, %{symbol: symbol, decimals: decimals}) + end) - if is_nil(indexed_token_id) do - {decode_data(event["data"], [{:uint, 256}]), 0} - else - {[quantity_to_integer(indexed_token_id)], 0} - end + token_addresses_for_rpc = + token_addresses + |> Enum.reject(fn address -> + Map.has_key?(token_data, String.downcase(address)) + end) - Enum.member?( - [ - @locked_ether_event, - @locked_erc20_event, - @locked_erc721_event, - @withdraw_event, - @exited_ether_event - ], - topic0 - ) -> - {decode_data(event["data"], [{:uint, 256}]), 0} - - topic0 == @locked_erc721_batch_event -> - [ids] = decode_data(event["data"], [{:array, {:uint, 256}}]) - {ids, 0} - - true -> - {[nil], 0} - end + {token_data, token_addresses_for_rpc} end - defp get_op_erc1155_data(topic0, event) do - cond do - Enum.member?([@locked_batch_erc1155_event, @transfer_batch_event], topic0) -> - [ids, amounts] = decode_data(event["data"], [{:array, {:uint, 256}}, {:array, {:uint, 256}}]) - {ids, amounts} + defp get_token_data_from_rpc({token_data, token_addresses}) do + {requests, responses} = get_token_data_request_symbol_decimals(token_addresses) + + requests + |> Enum.zip(responses) + |> Enum.reduce(token_data, fn {request, {status, response} = _resp}, token_data_acc -> + if status == :ok do + response = parse_response(response) - Enum.member?([@transfer_single_event], topic0) -> - [id, amount] = decode_data(event["data"], [{:uint, 256}, {:uint, 256}]) - {[id], [amount]} + address = String.downcase(request.contract_address) - true -> - {[], []} + new_data = get_new_data(token_data_acc[address] || %{}, request, response) + + Map.put(token_data_acc, address, new_data) + else + token_data_acc + end + end) + end + + defp parse_response(response) do + case response do + [item] -> item + items -> items end end - defp is_deposit(topic0) do - Enum.member?( - [ - @new_deposit_block_event, - @locked_ether_event, - @locked_erc20_event, - @locked_erc721_event, - @locked_erc721_batch_event, - @locked_batch_erc1155_event - ], - topic0 - ) + defp get_new_data(data, request, response) do + if atomized_key(request.method_id) == :symbol do + %{data | symbol: response} + else + %{data | decimals: response} + end end - defp get_safe_block(json_rpc_named_arguments) do - case Helper.get_block_number_by_tag("safe", json_rpc_named_arguments) do - {:ok, safe_block} -> - {safe_block, false} + defp get_token_data_request_symbol_decimals(token_addresses) do + requests = + token_addresses + |> Enum.map(fn address -> + # we will call symbol() and decimals() public getters + Enum.map(["95d89b41", "313ce567"], fn method_id -> + %{ + contract_address: address, + method_id: method_id, + args: [] + } + end) + end) + |> List.flatten() - {:error, :not_found} -> - {:ok, latest_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) - {latest_block, true} + {responses, error_messages} = read_contracts_with_retries(requests, @erc20_abi, 3) + + if !Enum.empty?(error_messages) or Enum.count(requests) != Enum.count(responses) do + Logger.warning( + "Cannot read symbol and decimals of an ERC-20 token contract. Error messages: #{Enum.join(error_messages, ", ")}. Addresses: #{Enum.join(token_addresses, ", ")}" + ) end + + {requests, responses} end defp json_rpc_named_arguments(rpc_url) do @@ -518,9 +463,12 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do end defp prepare_operations(events, json_rpc_named_arguments) do - timestamps = + deposit_events = events - |> filter_deposit_events() + |> Enum.filter(fn event -> Enum.at(event["topics"], 0) == @bridge_event end) + + timestamps = + deposit_events |> get_blocks_by_events(json_rpc_named_arguments, 100_000_000) |> Enum.reduce(%{}, fn block, acc -> block_number = quantity_to_integer(Map.get(block, "number")) @@ -528,63 +476,89 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do Map.put(acc, block_number, timestamp) end) - events - |> Enum.map(fn event -> - topic0 = Enum.at(event["topics"], 0) - - user = get_op_user(topic0, event) - {amounts_or_ids, operation_id} = get_op_amounts(topic0, event) - {erc1155_ids, erc1155_amounts} = get_op_erc1155_data(topic0, event) - - l1_block_number = quantity_to_integer(event["blockNumber"]) + token_data = + deposit_events + |> Enum.reduce(%{}, fn event, acc -> + [leaf_type, _origin_network, origin_address, _destination_network, _destination_address, _amount, _metadata, _deposit_count] = decode_data(event["data"], [{:uint, 8}, {:uint, 32}, :address, {:uint, 32}, :address, {:uint, 256}, :bytes, {:uint, 32}]) - {operation_type, timestamp} = - if is_deposit(topic0) do - {:deposit, Map.get(timestamps, l1_block_number)} + if leaf_type != 1 do + Map.put(acc, origin_address, true) else - {:withdrawal, nil} + acc end + end) + |> Map.values() + |> get_token_data() - token_type = - cond do - Enum.member?([@new_deposit_block_event, @withdraw_event], topic0) -> - "bone" - - Enum.member?([@locked_ether_event, @exited_ether_event], topic0) -> - "eth" - - true -> - "other" - end + events + |> Enum.map(fn event -> + topic0 = Enum.at(event["topics"], 0) - Enum.map(amounts_or_ids, fn amount_or_id -> - %{ - user: user, - amount_or_id: amount_or_id, - erc1155_ids: if(Enum.empty?(erc1155_ids), do: nil, else: erc1155_ids), - erc1155_amounts: if(Enum.empty?(erc1155_amounts), do: nil, else: erc1155_amounts), - l1_transaction_hash: event["transactionHash"], - l1_block_number: l1_block_number, - l2_transaction_hash: @empty_hash, - operation_hash: calc_operation_hash(user, amount_or_id, erc1155_ids, erc1155_amounts, operation_id), - operation_type: operation_type, - token_type: token_type, - timestamp: timestamp - } - end) + # user = get_op_user(topic0, event) + # {amounts_or_ids, operation_id} = get_op_amounts(topic0, event) + # {erc1155_ids, erc1155_amounts} = get_op_erc1155_data(topic0, event) + + # l1_block_number = quantity_to_integer(event["blockNumber"]) + + # {operation_type, timestamp} = + # if is_deposit(topic0) do + # {:deposit, Map.get(timestamps, l1_block_number)} + # else + # {:withdrawal, nil} + # end + + # token_type = + # cond do + # Enum.member?([@new_deposit_block_event, @withdraw_event], topic0) -> + # "bone" + + # Enum.member?([@locked_ether_event, @exited_ether_event], topic0) -> + # "eth" + + # true -> + # "other" + # end + + # %{ + # user: user, + # amount_or_id: amount_or_id, + # erc1155_ids: if(Enum.empty?(erc1155_ids), do: nil, else: erc1155_ids), + # erc1155_amounts: if(Enum.empty?(erc1155_amounts), do: nil, else: erc1155_amounts), + # l1_transaction_hash: event["transactionHash"], + # l1_block_number: l1_block_number, + # l2_transaction_hash: @empty_hash, + # operation_hash: calc_operation_hash(user, amount_or_id, erc1155_ids, erc1155_amounts, operation_id), + # operation_type: operation_type, + # token_type: token_type, + # timestamp: timestamp + # } end) - |> List.flatten() end - defp pad_address_hash(address) do - "0x" <> - (address - |> String.trim_leading("0x") - |> String.pad_leading(64, "0")) - end + defp read_contracts_with_retries(requests, abi, retries_left) when retries_left > 0 do + responses = Reader.query_contracts(requests, abi) + + error_messages = + Enum.reduce(responses, [], fn {status, error_message}, acc -> + acc ++ + if status == :error do + [error_message] + else + [] + end + end) + + if Enum.empty?(error_messages) do + {responses, []} + else + retries_left = retries_left - 1 - defp truncate_address_hash("0x000000000000000000000000" <> truncated_hash) do - "0x#{truncated_hash}" + if retries_left == 0 do + {responses, Enum.uniq(error_messages)} + else + read_contracts_with_retries(requests, abi, retries_left) + end + end end defp reorg_block_pop do @@ -608,29 +582,11 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do defp reorg_handle(reorg_block) do {deleted_count, _} = - Repo.delete_all(from(sb in Bridge, where: sb.l1_block_number >= ^reorg_block and is_nil(sb.l2_transaction_hash))) - - {updated_count1, _} = - Repo.update_all( - from(sb in Bridge, - where: - sb.l1_block_number >= ^reorg_block and not is_nil(sb.l2_transaction_hash) and - sb.operation_type == :deposit - ), - set: [timestamp: nil] - ) - - {updated_count2, _} = - Repo.update_all( - from(sb in Bridge, where: sb.l1_block_number >= ^reorg_block and not is_nil(sb.l2_transaction_hash)), - set: [l1_transaction_hash: nil, l1_block_number: nil] - ) - - updated_count = max(updated_count1, updated_count2) + Repo.delete_all(from(b in Bridge, where: b.type == :deposit and b.l1_block_number >= ^reorg_block)) - if deleted_count > 0 or updated_count > 0 do + if deleted_count > 0 do Logger.warning( - "As L1 reorg was detected, some rows with l1_block_number >= #{reorg_block} were affected (removed or updated) in the shibarium_bridge table. Number of removed rows: #{deleted_count}. Number of updated rows: >= #{updated_count}." + "As L1 reorg was detected, some deposits with block_number >= #{reorg_block} were removed from zkevm_bridge table. Number of removed rows: #{deleted_count}." ) end end From 19ef37c39867e76c5ffcfceb4063664e7dc52617 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:10:45 +0300 Subject: [PATCH 424/607] Prepare for filling and scanning zkevm_bridge_l1_tokens table --- .../import/runner/zkevm/bridge_l1_tokens.ex | 2 +- .../lib/indexer/fetcher/zkevm/bridge_l1.ex | 138 ++++++++++-------- apps/indexer/lib/indexer/helper.ex | 117 ++++++++++++++- apps/indexer/lib/indexer/supervisor.ex | 6 +- config/runtime.exs | 2 +- 5 files changed, 197 insertions(+), 68 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_l1_tokens.ex b/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_l1_tokens.ex index 3053eca2ffb0..5cb7e5624bd1 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_l1_tokens.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_l1_tokens.ex @@ -62,7 +62,7 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeL1Tokens do on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) # Enforce BridgeL1Token ShareLocks order (see docs: sharelock.md) - ordered_changes_list = Enum.sort_by(changes_list, &{&1.id}) + ordered_changes_list = Enum.sort_by(changes_list, &{&1.address}) {:ok, inserted} = Import.insert_changes_list( diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex index 097b23e7a6b4..7bf03972e5ce 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex @@ -18,6 +18,8 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do request: 1 ] + import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] + import Explorer.Helper, only: [parse_integer: 1, decode_data: 2] alias EthereumJSONRPC.Block.ByNumber @@ -31,7 +33,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do @block_check_interval_range_size 100 @eth_get_logs_range_size 1000 @fetcher_name :zkevm_bridge_l1 - @empty_hash "0x0000000000000000000000000000000000000000000000000000000000000000" + # @empty_hash "0x0000000000000000000000000000000000000000000000000000000000000000" # 32-byte signature of the event BridgeEvent(uint8 leafType, uint32 originNetwork, address originAddress, uint32 destinationNetwork, address destinationAddress, uint256 amount, bytes metadata, uint32 depositCount) @bridge_event "0x501781209a1f8899323b96b4ef08b168df93e0a90c673d1e4cce39366cb62f9b" @@ -95,7 +97,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do with {:start_block_undefined, false} <- {:start_block_undefined, is_nil(env[:start_block])}, rpc = env[:rpc], {:rpc_undefined, false} <- {:rpc_undefined, is_nil(rpc)}, - {:bridge_contract_address_is_valid, true} <- {:bridge_contract_address_is_valid, Helper.is_address_correct?(env[:bridge_contract])}, + {:bridge_contract_address_is_valid, true} <- {:bridge_contract_address_is_valid, Helper.address_correct?(env[:bridge_contract])}, start_block = parse_integer(env[:start_block]), false <- is_nil(start_block), true <- start_block > 0, @@ -294,7 +296,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do defp get_last_l1_item do query = from(b in Bridge, - select: {b.l1_block_number, b.l1_transaction_hash}, + select: {b.block_number, b.l1_transaction_hash}, where: b.type == :deposit and not is_nil(b.block_number), order_by: [desc: b.index], limit: 1 @@ -352,12 +354,12 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do end end - defp get_token_data(token_addresses) do + defp get_token_data(token_addresses, json_rpc_named_arguments) do # first, we're trying to read token data from the DB. # if tokens are not in the DB, read them through RPC. token_addresses |> get_token_data_from_db() - |> get_token_data_from_rpc() + |> get_token_data_from_rpc(json_rpc_named_arguments) end defp get_token_data_from_db(token_addresses) do @@ -386,8 +388,8 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do {token_data, token_addresses_for_rpc} end - defp get_token_data_from_rpc({token_data, token_addresses}) do - {requests, responses} = get_token_data_request_symbol_decimals(token_addresses) + defp get_token_data_from_rpc({token_data, token_addresses}, json_rpc_named_arguments) do + {requests, responses} = get_token_data_request_symbol_decimals(token_addresses, json_rpc_named_arguments) requests |> Enum.zip(responses) @@ -415,13 +417,13 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do defp get_new_data(data, request, response) do if atomized_key(request.method_id) == :symbol do - %{data | symbol: response} + Map.put(data, :symbol, response) else - %{data | decimals: response} + Map.put(data, :decimals, response) end end - defp get_token_data_request_symbol_decimals(token_addresses) do + defp get_token_data_request_symbol_decimals(token_addresses, json_rpc_named_arguments) do requests = token_addresses |> Enum.map(fn address -> @@ -436,7 +438,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do end) |> List.flatten() - {responses, error_messages} = read_contracts_with_retries(requests, @erc20_abi, 3) + {responses, error_messages} = read_contracts_with_retries(requests, @erc20_abi, json_rpc_named_arguments, 3) if !Enum.empty?(error_messages) or Enum.count(requests) != Enum.count(responses) do Logger.warning( @@ -476,67 +478,87 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do Map.put(acc, block_number, timestamp) end) + bridge_event_params = [{:uint, 8}, {:uint, 32}, :address, {:uint, 32}, :address, {:uint, 256}, :bytes, {:uint, 32}] + claim_event_params = [{:uint, 32}, {:uint, 32}, :address, :address, {:uint, 256}] + token_data = deposit_events |> Enum.reduce(%{}, fn event, acc -> - [leaf_type, _origin_network, origin_address, _destination_network, _destination_address, _amount, _metadata, _deposit_count] = decode_data(event["data"], [{:uint, 8}, {:uint, 32}, :address, {:uint, 32}, :address, {:uint, 256}, :bytes, {:uint, 32}]) + [leaf_type, _origin_network, origin_address, _destination_network, _destination_address, _amount, _metadata, _deposit_count] = decode_data(event["data"], bridge_event_params) + + origin_address = "0x" <> Base.encode16(origin_address, case: :lower) - if leaf_type != 1 do + if leaf_type != 1 and origin_address != burn_address_hash_string() do Map.put(acc, origin_address, true) else acc end end) - |> Map.values() - |> get_token_data() + |> Map.keys() + |> get_token_data(json_rpc_named_arguments) + + tokens = + token_data + |> Enum.map(fn {address, data} -> + Map.put(data, :address, address) + end) + + # todo: select known tokens from zkevm_bridge_l1_tokens table + + Logger.warn("tokens: #{inspect(tokens)}") + + # todo: insert only unknown tokens + {:ok, tokens_inserted} = Chain.import(%{ + zkevm_bridge_l1_tokens: %{params: tokens}, + timeout: :infinity + }) + + # todo: select remaining tokens from zkevm_bridge_l1_tokens table if they are not in `tokens_inserted` + + Logger.warn("tokens_inserted: #{inspect(tokens_inserted)}") events |> Enum.map(fn event -> - topic0 = Enum.at(event["topics"], 0) - - # user = get_op_user(topic0, event) - # {amounts_or_ids, operation_id} = get_op_amounts(topic0, event) - # {erc1155_ids, erc1155_amounts} = get_op_erc1155_data(topic0, event) - - # l1_block_number = quantity_to_integer(event["blockNumber"]) - - # {operation_type, timestamp} = - # if is_deposit(topic0) do - # {:deposit, Map.get(timestamps, l1_block_number)} - # else - # {:withdrawal, nil} - # end - - # token_type = - # cond do - # Enum.member?([@new_deposit_block_event, @withdraw_event], topic0) -> - # "bone" - - # Enum.member?([@locked_ether_event, @exited_ether_event], topic0) -> - # "eth" - - # true -> - # "other" - # end - - # %{ - # user: user, - # amount_or_id: amount_or_id, - # erc1155_ids: if(Enum.empty?(erc1155_ids), do: nil, else: erc1155_ids), - # erc1155_amounts: if(Enum.empty?(erc1155_amounts), do: nil, else: erc1155_amounts), - # l1_transaction_hash: event["transactionHash"], - # l1_block_number: l1_block_number, - # l2_transaction_hash: @empty_hash, - # operation_hash: calc_operation_hash(user, amount_or_id, erc1155_ids, erc1155_amounts, operation_id), - # operation_type: operation_type, - # token_type: token_type, - # timestamp: timestamp - # } + {type, index, amount, block_number, block_timestamp} = + if Enum.at(event["topics"], 0) == @bridge_event do + [_leaf_type, _origin_network, _origin_address, _destination_network, _destination_address, amount, _metadata, deposit_count] = decode_data(event["data"], bridge_event_params) + + block_number = quantity_to_integer(event["blockNumber"]) + block_timestamp = Map.get(timestamps, block_number) + + {:deposit, deposit_count, amount, block_number, block_timestamp} + else + [index, _origin_network, _origin_address, _destination_address, amount] = decode_data(event["data"], claim_event_params) + + {:withdrawal, index, amount, nil, nil} + end + + result = + %{ + type: type, + index: index, + l1_transaction_hash: event["transactionHash"], + # l1_token_id: ..., + amount: amount + } + + result = + if not is_nil(block_number) do + Map.put(result, :block_number, block_number) + else + result + end + + if not is_nil(block_timestamp) do + Map.put(result, :block_timestamp, block_timestamp) + else + result + end end) end - defp read_contracts_with_retries(requests, abi, retries_left) when retries_left > 0 do - responses = Reader.query_contracts(requests, abi) + defp read_contracts_with_retries(requests, abi, json_rpc_named_arguments, retries_left) when retries_left > 0 do + responses = Reader.query_contracts(requests, abi, json_rpc_named_arguments: json_rpc_named_arguments) error_messages = Enum.reduce(responses, [], fn {status, error_message}, acc -> @@ -556,7 +578,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do if retries_left == 0 do {responses, Enum.uniq(error_messages)} else - read_contracts_with_retries(requests, abi, retries_left) + read_contracts_with_retries(requests, abi, json_rpc_named_arguments, retries_left) end end end diff --git a/apps/indexer/lib/indexer/helper.ex b/apps/indexer/lib/indexer/helper.ex index 1c37af8f4589..7b980728bd6f 100644 --- a/apps/indexer/lib/indexer/helper.ex +++ b/apps/indexer/lib/indexer/helper.ex @@ -16,6 +16,15 @@ defmodule Indexer.Helper do alias EthereumJSONRPC.Block.ByNumber alias Explorer.Chain.Hash + @spec address_correct?(binary()) :: boolean() + def address_correct?(address) when is_binary(address) do + String.match?(address, ~r/^0x[[:xdigit:]]{40}$/i) + end + + def address_correct?(_address) do + false + end + @spec address_hash_to_string(binary(), boolean()) :: binary() def address_hash_to_string(hash, downcase \\ false) @@ -35,13 +44,65 @@ defmodule Indexer.Helper do end end - @spec address_correct?(binary()) :: boolean() - def address_correct?(address) when is_binary(address) do - String.match?(address, ~r/^0x[[:xdigit:]]{40}$/i) + @spec get_block_number_by_tag(binary(), list(), integer()) :: {:ok, non_neg_integer()} | {:error, atom()} + def get_block_number_by_tag(tag, json_rpc_named_arguments, retries \\ 3) do + error_message = &"Cannot fetch #{tag} block number. Error: #{inspect(&1)}" + repeated_call(&fetch_block_number_by_tag/2, [tag, json_rpc_named_arguments], error_message, retries) end - def address_correct?(_address) do - false + def get_transaction_by_hash(hash, json_rpc_named_arguments, retries_left \\ 3) + + def get_transaction_by_hash(hash, _json_rpc_named_arguments, _retries_left) when is_nil(hash), do: {:ok, nil} + + def get_transaction_by_hash(hash, json_rpc_named_arguments, retries) do + req = + request(%{ + id: 0, + method: "eth_getTransactionByHash", + params: [hash] + }) + + error_message = &"eth_getTransactionByHash failed. Error: #{inspect(&1)}" + + repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) + end + + def log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, items_count, layer) do + is_start = is_nil(items_count) + + {type, found} = + if is_start do + {"Start", ""} + else + {"Finish", " Found #{items_count}."} + end + + target_range = + if chunk_start != start_block or chunk_end != end_block do + progress = + if is_start do + "" + else + percentage = + (chunk_end - start_block + 1) + |> Decimal.div(end_block - start_block + 1) + |> Decimal.mult(100) + |> Decimal.round(2) + |> Decimal.to_string() + + " Progress: #{percentage}%" + end + + " Target range: #{start_block}..#{end_block}.#{progress}" + else + "" + end + + if chunk_start == chunk_end do + Logger.info("#{type} handling #{layer} block ##{chunk_start}.#{found}#{target_range}") + else + Logger.info("#{type} handling #{layer} block range #{chunk_start}..#{chunk_end}.#{found}#{target_range}") + end end @doc """ @@ -195,4 +256,50 @@ defmodule Indexer.Helper do Hash.to_string(topic) end end + + def repeated_call(func, args, error_message, retries_left) do + case apply(func, args) do + {:ok, _} = res -> + res + + {:error, message} = err -> + retries_left = retries_left - 1 + + if retries_left <= 0 do + Logger.error(error_message.(message)) + err + else + Logger.error("#{error_message.(message)} Retrying...") + :timer.sleep(3000) + repeated_call(func, args, error_message, retries_left) + end + end + end + + def get_block_timestamp_by_number(number, json_rpc_named_arguments, retries \\ 3) do + func = &get_block_timestamp_by_number_inner/2 + args = [number, json_rpc_named_arguments] + error_message = &"Cannot fetch block ##{number} or its timestamp. Error: #{inspect(&1)}" + repeated_call(func, args, error_message, retries) + end + + defp get_block_timestamp_by_number_inner(number, json_rpc_named_arguments) do + result = + %{id: 0, number: number} + |> ByNumber.request(false) + |> json_rpc(json_rpc_named_arguments) + + with {:ok, block} <- result, + false <- is_nil(block), + timestamp <- Map.get(block, "timestamp"), + false <- is_nil(timestamp) do + {:ok, quantity_to_integer(timestamp)} + else + {:error, message} -> + {:error, message} + + true -> + {:error, "RPC returned nil."} + end + end end diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index 9ced4dee8954..8a6fc9c52ea1 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -146,9 +146,9 @@ defmodule Indexer.Supervisor do ]), configure(Indexer.Fetcher.Shibarium.L1.Supervisor, [[memory_monitor: memory_monitor]]), configure(Indexer.Fetcher.Zkevm.BridgeL1.Supervisor, [[memory_monitor: memory_monitor]]), - configure(Indexer.Fetcher.Zkevm.BridgeL2.Supervisor, [ - [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] - ]), + # configure(Indexer.Fetcher.Zkevm.BridgeL2.Supervisor, [ + # [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] + # ]), configure(Indexer.Fetcher.Zkevm.TransactionBatch.Supervisor, [ [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), diff --git a/config/runtime.exs b/config/runtime.exs index 9c8e13684b8f..05fc85397823 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -738,7 +738,7 @@ config :indexer, Indexer.Fetcher.Zkevm.BridgeL1.Supervisor, enabled: ConfigHelpe config :indexer, Indexer.Fetcher.Zkevm.BridgeL2, start_block: System.get_env("INDEXER_POLYGON_ZKEVM_L2_BRIDGE_START_BLOCK"), - bridge_contract: System.get_env("INDEXER_POLYGON_ZKEVM_L2_BRIDGE_CONTRACT"), + bridge_contract: System.get_env("INDEXER_POLYGON_ZKEVM_L2_BRIDGE_CONTRACT") config :indexer, Indexer.Fetcher.Zkevm.BridgeL2.Supervisor, enabled: ConfigHelper.chain_type() == "polygon_zkevm" From 316077077ab38b4cd868e08a9f9431b1c7afe4c6 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Tue, 19 Dec 2023 13:52:12 +0300 Subject: [PATCH 425/607] Preliminary Indexer.Fetcher.Zkevm.BridgeL1 --- .../lib/indexer/fetcher/zkevm/bridge_l1.ex | 160 ++++++++++++------ apps/indexer/lib/indexer/supervisor.ex | 6 +- 2 files changed, 115 insertions(+), 51 deletions(-) diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex index 7bf03972e5ce..3a1a4309ad4b 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex @@ -3,6 +3,8 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do Fills zkevm_bridge DB table. """ + # todo: handle case with L2 address of token in origin_address field + use GenServer use Indexer.Fetcher @@ -33,7 +35,6 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do @block_check_interval_range_size 100 @eth_get_logs_range_size 1000 @fetcher_name :zkevm_bridge_l1 - # @empty_hash "0x0000000000000000000000000000000000000000000000000000000000000000" # 32-byte signature of the event BridgeEvent(uint8 leafType, uint32 originNetwork, address originAddress, uint32 destinationNetwork, address destinationAddress, uint256 amount, bytes metadata, uint32 depositCount) @bridge_event "0x501781209a1f8899323b96b4ef08b168df93e0a90c673d1e4cce39366cb62f9b" @@ -104,7 +105,9 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do {last_l1_block_number, last_l1_transaction_hash} = get_last_l1_item(), json_rpc_named_arguments = json_rpc_named_arguments(rpc), {:ok, block_check_interval, safe_block} <- get_block_check_interval(json_rpc_named_arguments), - {:start_block_valid, true} <- {:start_block_valid, (start_block <= last_l1_block_number || last_l1_block_number == 0) && start_block <= safe_block}, + {:start_block_valid, true} <- + {:start_block_valid, + (start_block <= last_l1_block_number || last_l1_block_number == 0) && start_block <= safe_block}, {:ok, last_l1_tx} <- Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)} do Process.send(self(), :reorg_monitor, []) @@ -116,7 +119,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do bridge_contract: env[:bridge_contract], json_rpc_named_arguments: json_rpc_named_arguments, reorg_monitor_prev_latest: 0, - safe_block: safe_block, + end_block: safe_block, start_block: max(start_block, last_l1_block_number) }} else @@ -184,7 +187,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do bridge_contract: bridge_contract, block_check_interval: block_check_interval, start_block: start_block, - safe_block: end_block, + end_block: end_block, json_rpc_named_arguments: json_rpc_named_arguments } = state ) do @@ -205,10 +208,11 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do |> get_logs_all(bridge_contract, json_rpc_named_arguments) |> prepare_operations(json_rpc_named_arguments) - {:ok, _} = Chain.import(%{ - zkevm_bridge_operations: %{params: operations}, - timeout: :infinity - }) + {:ok, _} = + Chain.import(%{ + zkevm_bridge_operations: %{params: operations}, + timeout: :infinity + }) Helper.log_blocks_chunk_handling( chunk_start, @@ -262,9 +266,12 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do first_block = max(last_safe_block - @block_check_interval_range_size, 1) - with {:ok, first_block_timestamp} <- Helper.get_block_timestamp_by_number(first_block, json_rpc_named_arguments, 100_000_000), - {:ok, last_safe_block_timestamp} <- Helper.get_block_timestamp_by_number(last_safe_block, json_rpc_named_arguments, 100_000_000) do - block_check_interval = ceil((last_safe_block_timestamp - first_block_timestamp) / (last_safe_block - first_block) * 1000 / 2) + with {:ok, first_block_timestamp} <- + Helper.get_block_timestamp_by_number(first_block, json_rpc_named_arguments, 100_000_000), + {:ok, last_safe_block_timestamp} <- + Helper.get_block_timestamp_by_number(last_safe_block, json_rpc_named_arguments, 100_000_000) do + block_check_interval = + ceil((last_safe_block_timestamp - first_block_timestamp) / (last_safe_block - first_block) * 1000 / 2) Logger.info("Block check interval is calculated as #{block_check_interval} ms.") {:ok, block_check_interval, last_safe_block} @@ -483,64 +490,110 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do token_data = deposit_events - |> Enum.reduce(%{}, fn event, acc -> - [leaf_type, _origin_network, origin_address, _destination_network, _destination_address, _amount, _metadata, _deposit_count] = decode_data(event["data"], bridge_event_params) - - origin_address = "0x" <> Base.encode16(origin_address, case: :lower) - - if leaf_type != 1 and origin_address != burn_address_hash_string() do - Map.put(acc, origin_address, true) - else - acc + |> Enum.reduce(%MapSet{}, fn event, acc -> + [ + leaf_type, + _origin_network, + origin_address, + _destination_network, + _destination_address, + _amount, + _metadata, + _deposit_count + ] = decode_data(event["data"], bridge_event_params) + + case token_address_by_origin_address(origin_address, leaf_type) do + nil -> acc + token_address -> + #if token_address == "0x4f9a0e7fd2bf6067db6994cf12e4495df938e6e9" do + # Logger.warn("event = #{inspect(event)}") + #end + MapSet.put(acc, token_address) end end) - |> Map.keys() + |> MapSet.to_list() |> get_token_data(json_rpc_named_arguments) - tokens = + token_addresses = Map.keys(token_data) + + tokens_existing = + from(t in BridgeL1Token, select: {t.address, t.id}, where: t.address in ^token_addresses) + |> Repo.all(timeout: :infinity) + |> Enum.reduce(%{}, fn {address, id}, acc -> Map.put(acc, Hash.to_string(address), id) end) + + tokens_to_insert = token_data - |> Enum.map(fn {address, data} -> - Map.put(data, :address, address) - end) + |> Enum.reject(fn {address, _} -> Map.has_key?(tokens_existing, address) end) + |> Enum.map(fn {address, data} -> Map.put(data, :address, address) end) - # todo: select known tokens from zkevm_bridge_l1_tokens table + {:ok, inserts} = + Chain.import(%{ + zkevm_bridge_l1_tokens: %{params: tokens_to_insert}, + timeout: :infinity + }) - Logger.warn("tokens: #{inspect(tokens)}") + tokens_inserted = Map.get(inserts, :insert_zkevm_bridge_l1_tokens, []) - # todo: insert only unknown tokens - {:ok, tokens_inserted} = Chain.import(%{ - zkevm_bridge_l1_tokens: %{params: tokens}, - timeout: :infinity - }) + tokens_uninserted = + tokens_to_insert + |> Enum.reject(fn token -> + Enum.any?(tokens_inserted, fn inserted -> token.address == Hash.to_string(inserted.address) end) + end) + |> Enum.map(& &1.address) - # todo: select remaining tokens from zkevm_bridge_l1_tokens table if they are not in `tokens_inserted` + tokens_inserted_outside = + from(t in BridgeL1Token, select: {t.address, t.id}, where: t.address in ^tokens_uninserted) + |> Repo.all(timeout: :infinity) + |> Enum.reduce(%{}, fn {address, id}, acc -> Map.put(acc, Hash.to_string(address), id) end) - Logger.warn("tokens_inserted: #{inspect(tokens_inserted)}") + address_to_id = + tokens_inserted + |> Enum.reduce(%{}, fn t, acc -> Map.put(acc, Hash.to_string(t.address), t.id) end) + |> Map.merge(tokens_existing) + |> Map.merge(tokens_inserted_outside) events |> Enum.map(fn event -> - {type, index, amount, block_number, block_timestamp} = + {type, index, l1_token_id, amount, block_number, block_timestamp} = if Enum.at(event["topics"], 0) == @bridge_event do - [_leaf_type, _origin_network, _origin_address, _destination_network, _destination_address, amount, _metadata, deposit_count] = decode_data(event["data"], bridge_event_params) - + [ + leaf_type, + _origin_network, + origin_address, + _destination_network, + _destination_address, + amount, + _metadata, + deposit_count + ] = decode_data(event["data"], bridge_event_params) + + token_address = token_address_by_origin_address(origin_address, leaf_type) + + l1_token_id = Map.get(address_to_id, token_address) block_number = quantity_to_integer(event["blockNumber"]) block_timestamp = Map.get(timestamps, block_number) - {:deposit, deposit_count, amount, block_number, block_timestamp} + {:deposit, deposit_count, l1_token_id, amount, block_number, block_timestamp} else - [index, _origin_network, _origin_address, _destination_address, amount] = decode_data(event["data"], claim_event_params) - - {:withdrawal, index, amount, nil, nil} + [index, _origin_network, _origin_address, _destination_address, amount] = + decode_data(event["data"], claim_event_params) + + {:withdrawal, index, nil, amount, nil, nil} end + result = %{ + type: type, + index: index, + l1_transaction_hash: event["transactionHash"], + amount: amount + } + result = - %{ - type: type, - index: index, - l1_transaction_hash: event["transactionHash"], - # l1_token_id: ..., - amount: amount - } + if not is_nil(l1_token_id) do + Map.put(result, :l1_token_id, l1_token_id) + else + result + end result = if not is_nil(block_number) do @@ -578,6 +631,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do if retries_left == 0 do {responses, Enum.uniq(error_messages)} else + :timer.sleep(1000) read_contracts_with_retries(requests, abi, json_rpc_named_arguments, retries_left) end end @@ -635,4 +689,14 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do defp reorg_table_name(fetcher_name) do :"#{fetcher_name}#{:_reorgs}" end + + defp token_address_by_origin_address(origin_address, leaf_type) do + with true <- leaf_type != 1, + token_address = "0x" <> Base.encode16(origin_address, case: :lower), + true <- token_address != burn_address_hash_string() do + token_address + else + _ -> nil + end + end end diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index 8a6fc9c52ea1..9ced4dee8954 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -146,9 +146,9 @@ defmodule Indexer.Supervisor do ]), configure(Indexer.Fetcher.Shibarium.L1.Supervisor, [[memory_monitor: memory_monitor]]), configure(Indexer.Fetcher.Zkevm.BridgeL1.Supervisor, [[memory_monitor: memory_monitor]]), - # configure(Indexer.Fetcher.Zkevm.BridgeL2.Supervisor, [ - # [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] - # ]), + configure(Indexer.Fetcher.Zkevm.BridgeL2.Supervisor, [ + [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] + ]), configure(Indexer.Fetcher.Zkevm.TransactionBatch.Supervisor, [ [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), From 09f2429e9a92a2a82e30db61675d9f6c3390d4f9 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Tue, 19 Dec 2023 14:47:07 +0300 Subject: [PATCH 426/607] Add l2_token_address field for preliminary Indexer.Fetcher.Zkevm.BridgeL1 --- .../import/runner/zkevm/bridge_operations.ex | 4 +- .../lib/explorer/chain/zkevm/bridge.ex | 7 ++- .../20231010093238_add_bridge_tables.exs | 1 + .../lib/indexer/fetcher/zkevm/bridge_l1.ex | 51 +++++++++++-------- 4 files changed, 40 insertions(+), 23 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex b/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex index fc91cd4e4fe4..4b6dcca7ef7f 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex @@ -89,6 +89,7 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeOperations do l1_transaction_hash: fragment("EXCLUDED.l1_transaction_hash"), l2_transaction_hash: fragment("EXCLUDED.l2_transaction_hash"), l1_token_id: fragment("EXCLUDED.l1_token_id"), + l2_token_address: fragment("EXCLUDED.l2_token_address"), amount: fragment("EXCLUDED.amount"), block_number: fragment("EXCLUDED.block_number"), block_timestamp: fragment("EXCLUDED.block_timestamp"), @@ -98,10 +99,11 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeOperations do ], where: fragment( - "(EXCLUDED.l1_transaction_hash, EXCLUDED.l2_transaction_hash, EXCLUDED.l1_token_id, EXCLUDED.amount, EXCLUDED.block_number, EXCLUDED.block_timestamp) IS DISTINCT FROM (?, ?, ?, ?, ?, ?)", + "(EXCLUDED.l1_transaction_hash, EXCLUDED.l2_transaction_hash, EXCLUDED.l1_token_id, EXCLUDED.l2_token_address, EXCLUDED.amount, EXCLUDED.block_number, EXCLUDED.block_timestamp) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?)", op.l1_transaction_hash, op.l2_transaction_hash, op.l1_token_id, + op.l2_token_address, op.amount, op.block_number, op.block_timestamp diff --git a/apps/explorer/lib/explorer/chain/zkevm/bridge.ex b/apps/explorer/lib/explorer/chain/zkevm/bridge.ex index 55da9f0a53af..8a2bf7855979 100644 --- a/apps/explorer/lib/explorer/chain/zkevm/bridge.ex +++ b/apps/explorer/lib/explorer/chain/zkevm/bridge.ex @@ -3,10 +3,10 @@ defmodule Explorer.Chain.Zkevm.Bridge do use Explorer.Schema - alias Explorer.Chain.{Block, Hash} + alias Explorer.Chain.{Block, Hash, Token} alias Explorer.Chain.Zkevm.BridgeL1Token - @optional_attrs ~w(l1_transaction_hash l2_transaction_hash l1_token_id block_number block_timestamp)a + @optional_attrs ~w(l1_transaction_hash l2_transaction_hash l1_token_id l2_token_address block_number block_timestamp)a @required_attrs ~w(type index amount)a @@ -17,6 +17,8 @@ defmodule Explorer.Chain.Zkevm.Bridge do l2_transaction_hash: Hash.t() | nil, l1_token: %Ecto.Association.NotLoaded{} | BridgeL1Token.t() | nil, l1_token_id: non_neg_integer() | nil, + l2_token: %Ecto.Association.NotLoaded{} | Token.t() | nil, + l2_token_address: Hash.Address.t() | nil, amount: Decimal.t(), block_number: Block.block_number() | nil, block_timestamp: DateTime.t() | nil @@ -29,6 +31,7 @@ defmodule Explorer.Chain.Zkevm.Bridge do field(:l1_transaction_hash, Hash.Full) field(:l2_transaction_hash, Hash.Full) belongs_to(:l1_token, BridgeL1Token, foreign_key: :l1_token_id, references: :id, type: :integer) + belongs_to(:l2_token, Token, foreign_key: :l2_token_address, references: :contract_address_hash, type: Hash.Address) field(:amount, :decimal) field(:block_number, :integer) field(:block_timestamp, :utc_datetime_usec) diff --git a/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs b/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs index 42b3fc597613..fa32ae967776 100644 --- a/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs +++ b/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs @@ -29,6 +29,7 @@ defmodule Explorer.Repo.PolygonZkevm.Migrations.AddBridgeTables do null: true ) + add(:l2_token_address, :bytea, null: true, default: nil) add(:amount, :numeric, precision: 100, null: false) add(:block_number, :bigint, null: true, default: nil) add(:block_timestamp, :"timestamp without time zone", null: true, default: nil) diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex index 3a1a4309ad4b..6f631774d5f3 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex @@ -3,8 +3,6 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do Fills zkevm_bridge DB table. """ - # todo: handle case with L2 address of token in origin_address field - use GenServer use Indexer.Fetcher @@ -493,7 +491,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do |> Enum.reduce(%MapSet{}, fn event, acc -> [ leaf_type, - _origin_network, + origin_network, origin_address, _destination_network, _destination_address, @@ -502,13 +500,9 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do _deposit_count ] = decode_data(event["data"], bridge_event_params) - case token_address_by_origin_address(origin_address, leaf_type) do - nil -> acc - token_address -> - #if token_address == "0x4f9a0e7fd2bf6067db6994cf12e4495df938e6e9" do - # Logger.warn("event = #{inspect(event)}") - #end - MapSet.put(acc, token_address) + case token_address_by_origin_address(origin_address, origin_network, leaf_type) do + {nil, _} -> acc + {token_address, nil} -> MapSet.put(acc, token_address) end end) |> MapSet.to_list() @@ -534,6 +528,9 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do tokens_inserted = Map.get(inserts, :insert_zkevm_bridge_l1_tokens, []) + # we need to query uninserted tokens separately from DB as they + # could be inserted by BridgeL2 module at the same time (a race condition). + # this is an unlikely case but we handle it here as well tokens_uninserted = tokens_to_insert |> Enum.reject(fn token -> @@ -554,11 +551,11 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do events |> Enum.map(fn event -> - {type, index, l1_token_id, amount, block_number, block_timestamp} = + {type, index, l1_token_id, l2_token_address, amount, block_number, block_timestamp} = if Enum.at(event["topics"], 0) == @bridge_event do [ leaf_type, - _origin_network, + origin_network, origin_address, _destination_network, _destination_address, @@ -567,18 +564,19 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do deposit_count ] = decode_data(event["data"], bridge_event_params) - token_address = token_address_by_origin_address(origin_address, leaf_type) + {l1_token_address, l2_token_address} = + token_address_by_origin_address(origin_address, origin_network, leaf_type) - l1_token_id = Map.get(address_to_id, token_address) + l1_token_id = Map.get(address_to_id, l1_token_address) block_number = quantity_to_integer(event["blockNumber"]) block_timestamp = Map.get(timestamps, block_number) - {:deposit, deposit_count, l1_token_id, amount, block_number, block_timestamp} + {:deposit, deposit_count, l1_token_id, l2_token_address, amount, block_number, block_timestamp} else [index, _origin_network, _origin_address, _destination_address, amount] = decode_data(event["data"], claim_event_params) - {:withdrawal, index, nil, amount, nil, nil} + {:withdrawal, index, nil, nil, amount, nil, nil} end result = %{ @@ -595,6 +593,13 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do result end + result = + if not is_nil(l2_token_address) do + Map.put(result, :l2_token_address, l2_token_address) + else + result + end + result = if not is_nil(block_number) do Map.put(result, :block_number, block_number) @@ -690,13 +695,19 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do :"#{fetcher_name}#{:_reorgs}" end - defp token_address_by_origin_address(origin_address, leaf_type) do - with true <- leaf_type != 1, + defp token_address_by_origin_address(origin_address, origin_network, leaf_type) do + with true <- leaf_type != 1 and origin_network <= 1, token_address = "0x" <> Base.encode16(origin_address, case: :lower), true <- token_address != burn_address_hash_string() do - token_address + if origin_network == 0 do + # this is L1 address + {token_address, nil} + else + # this is L2 address + {nil, token_address} + end else - _ -> nil + _ -> {nil, nil} end end end From 4d779600bcb202e50b3f33fd2c036b5f21894c85 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Tue, 19 Dec 2023 17:20:12 +0300 Subject: [PATCH 427/607] Refactor Indexer.Fetcher.Zkevm.BridgeL1 --- .../lib/indexer/fetcher/zkevm/bridge_l1.ex | 231 +++++++++--------- 1 file changed, 118 insertions(+), 113 deletions(-) diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex index 6f631774d5f3..a9ea191590bf 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex @@ -36,9 +36,11 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do # 32-byte signature of the event BridgeEvent(uint8 leafType, uint32 originNetwork, address originAddress, uint32 destinationNetwork, address destinationAddress, uint256 amount, bytes metadata, uint32 depositCount) @bridge_event "0x501781209a1f8899323b96b4ef08b168df93e0a90c673d1e4cce39366cb62f9b" + @bridge_event_params [{:uint, 8}, {:uint, 32}, :address, {:uint, 32}, :address, {:uint, 256}, :bytes, {:uint, 32}] # 32-byte signature of the event ClaimEvent(uint32 index, uint32 originNetwork, address originAddress, address destinationAddress, uint256 amount) @claim_event "0x25308c93ceeed162da955b3f7ce3e3f93606579e40fb92029faa9efe27545983" + @claim_event_params [{:uint, 32}, {:uint, 32}, :address, :address, {:uint, 256}] @erc20_abi [ %{ @@ -206,11 +208,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do |> get_logs_all(bridge_contract, json_rpc_named_arguments) |> prepare_operations(json_rpc_named_arguments) - {:ok, _} = - Chain.import(%{ - zkevm_bridge_operations: %{params: operations}, - timeout: :infinity - }) + import_operations(operations) Helper.log_blocks_chunk_handling( chunk_start, @@ -259,6 +257,19 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do defp atomized_key("95d89b41"), do: :symbol defp atomized_key("313ce567"), do: :decimals + defp blocks_to_timestamps(deposit_events, json_rpc_named_arguments) do + deposit_events + |> get_blocks_by_events(json_rpc_named_arguments, 100_000_000) + |> Enum.reduce(%{}, fn block, acc -> + block_number = quantity_to_integer(Map.get(block, "number")) + {:ok, timestamp} = DateTime.from_unix(quantity_to_integer(Map.get(block, "timestamp"))) + Map.put(acc, block_number, timestamp) + end) + end + + defp extend_result(result, _key, value) when is_nil(value), do: result + defp extend_result(result, key, value) when is_atom(key), do: Map.put(result, key, value) + defp get_block_check_interval(json_rpc_named_arguments) do {last_safe_block, _} = get_safe_block(json_rpc_named_arguments) @@ -454,6 +465,21 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do {requests, responses} end + defp import_operations(operations) do + # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise + import_options = + if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do + %{ + zkevm_bridge_operations: %{params: operations}, + timeout: :infinity + } + else + %{} + end + + {:ok, _} = Chain.import(import_options) + end + defp json_rpc_named_arguments(rpc_url) do [ transport: EthereumJSONRPC.HTTP, @@ -470,87 +496,13 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do end defp prepare_operations(events, json_rpc_named_arguments) do - deposit_events = - events - |> Enum.filter(fn event -> Enum.at(event["topics"], 0) == @bridge_event end) + deposit_events = Enum.filter(events, fn event -> Enum.at(event["topics"], 0) == @bridge_event end) - timestamps = - deposit_events - |> get_blocks_by_events(json_rpc_named_arguments, 100_000_000) - |> Enum.reduce(%{}, fn block, acc -> - block_number = quantity_to_integer(Map.get(block, "number")) - {:ok, timestamp} = DateTime.from_unix(quantity_to_integer(Map.get(block, "timestamp"))) - Map.put(acc, block_number, timestamp) - end) + block_to_timestamp = blocks_to_timestamps(deposit_events, json_rpc_named_arguments) - bridge_event_params = [{:uint, 8}, {:uint, 32}, :address, {:uint, 32}, :address, {:uint, 256}, :bytes, {:uint, 32}] - claim_event_params = [{:uint, 32}, {:uint, 32}, :address, :address, {:uint, 256}] + token_address_to_id = token_addresses_to_ids(deposit_events, json_rpc_named_arguments) - token_data = - deposit_events - |> Enum.reduce(%MapSet{}, fn event, acc -> - [ - leaf_type, - origin_network, - origin_address, - _destination_network, - _destination_address, - _amount, - _metadata, - _deposit_count - ] = decode_data(event["data"], bridge_event_params) - - case token_address_by_origin_address(origin_address, origin_network, leaf_type) do - {nil, _} -> acc - {token_address, nil} -> MapSet.put(acc, token_address) - end - end) - |> MapSet.to_list() - |> get_token_data(json_rpc_named_arguments) - - token_addresses = Map.keys(token_data) - - tokens_existing = - from(t in BridgeL1Token, select: {t.address, t.id}, where: t.address in ^token_addresses) - |> Repo.all(timeout: :infinity) - |> Enum.reduce(%{}, fn {address, id}, acc -> Map.put(acc, Hash.to_string(address), id) end) - - tokens_to_insert = - token_data - |> Enum.reject(fn {address, _} -> Map.has_key?(tokens_existing, address) end) - |> Enum.map(fn {address, data} -> Map.put(data, :address, address) end) - - {:ok, inserts} = - Chain.import(%{ - zkevm_bridge_l1_tokens: %{params: tokens_to_insert}, - timeout: :infinity - }) - - tokens_inserted = Map.get(inserts, :insert_zkevm_bridge_l1_tokens, []) - - # we need to query uninserted tokens separately from DB as they - # could be inserted by BridgeL2 module at the same time (a race condition). - # this is an unlikely case but we handle it here as well - tokens_uninserted = - tokens_to_insert - |> Enum.reject(fn token -> - Enum.any?(tokens_inserted, fn inserted -> token.address == Hash.to_string(inserted.address) end) - end) - |> Enum.map(& &1.address) - - tokens_inserted_outside = - from(t in BridgeL1Token, select: {t.address, t.id}, where: t.address in ^tokens_uninserted) - |> Repo.all(timeout: :infinity) - |> Enum.reduce(%{}, fn {address, id}, acc -> Map.put(acc, Hash.to_string(address), id) end) - - address_to_id = - tokens_inserted - |> Enum.reduce(%{}, fn t, acc -> Map.put(acc, Hash.to_string(t.address), t.id) end) - |> Map.merge(tokens_existing) - |> Map.merge(tokens_inserted_outside) - - events - |> Enum.map(fn event -> + Enum.map(events, fn event -> {type, index, l1_token_id, l2_token_address, amount, block_number, block_timestamp} = if Enum.at(event["topics"], 0) == @bridge_event do [ @@ -562,19 +514,19 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do amount, _metadata, deposit_count - ] = decode_data(event["data"], bridge_event_params) + ] = decode_data(event["data"], @bridge_event_params) {l1_token_address, l2_token_address} = token_address_by_origin_address(origin_address, origin_network, leaf_type) - l1_token_id = Map.get(address_to_id, l1_token_address) + l1_token_id = Map.get(token_address_to_id, l1_token_address) block_number = quantity_to_integer(event["blockNumber"]) - block_timestamp = Map.get(timestamps, block_number) + block_timestamp = Map.get(block_to_timestamp, block_number) {:deposit, deposit_count, l1_token_id, l2_token_address, amount, block_number, block_timestamp} else [index, _origin_network, _origin_address, _destination_address, amount] = - decode_data(event["data"], claim_event_params) + decode_data(event["data"], @claim_event_params) {:withdrawal, index, nil, nil, amount, nil, nil} end @@ -586,32 +538,11 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do amount: amount } - result = - if not is_nil(l1_token_id) do - Map.put(result, :l1_token_id, l1_token_id) - else - result - end - - result = - if not is_nil(l2_token_address) do - Map.put(result, :l2_token_address, l2_token_address) - else - result - end - - result = - if not is_nil(block_number) do - Map.put(result, :block_number, block_number) - else - result - end - - if not is_nil(block_timestamp) do - Map.put(result, :block_timestamp, block_timestamp) - else - result - end + result + |> extend_result(:l1_token_id, l1_token_id) + |> extend_result(:l2_token_address, l2_token_address) + |> extend_result(:block_number, block_number) + |> extend_result(:block_timestamp, block_timestamp) end) end @@ -710,4 +641,78 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do _ -> {nil, nil} end end + + defp token_addresses_to_ids(deposit_events, json_rpc_named_arguments) do + token_data = + deposit_events + |> Enum.reduce(%MapSet{}, fn event, acc -> + [ + leaf_type, + origin_network, + origin_address, + _destination_network, + _destination_address, + _amount, + _metadata, + _deposit_count + ] = decode_data(event["data"], @bridge_event_params) + + case token_address_by_origin_address(origin_address, origin_network, leaf_type) do + {nil, _} -> acc + {token_address, nil} -> MapSet.put(acc, token_address) + end + end) + |> MapSet.to_list() + |> get_token_data(json_rpc_named_arguments) + + tokens_existing = + token_data + |> Map.keys() + |> token_addresses_to_ids_from_db() + + tokens_to_insert = + token_data + |> Enum.reject(fn {address, _} -> Map.has_key?(tokens_existing, address) end) + |> Enum.map(fn {address, data} -> Map.put(data, :address, address) end) + + # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise + import_options = + if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do + %{ + zkevm_bridge_l1_tokens: %{params: tokens_to_insert}, + timeout: :infinity + } + else + %{} + end + + {:ok, inserts} = Chain.import(import_options) + + tokens_inserted = Map.get(inserts, :insert_zkevm_bridge_l1_tokens, []) + + # we need to query uninserted tokens separately from DB as they + # could be inserted by BridgeL2 module at the same time (a race condition). + # this is an unlikely case but we handle it here as well + tokens_uninserted = + tokens_to_insert + |> Enum.reject(fn token -> + Enum.any?(tokens_inserted, fn inserted -> token.address == Hash.to_string(inserted.address) end) + end) + |> Enum.map(& &1.address) + + tokens_inserted_outside = token_addresses_to_ids_from_db(tokens_uninserted) + + tokens_inserted + |> Enum.reduce(%{}, fn t, acc -> Map.put(acc, Hash.to_string(t.address), t.id) end) + |> Map.merge(tokens_existing) + |> Map.merge(tokens_inserted_outside) + end + + defp token_addresses_to_ids_from_db(addresses) do + query = from(t in BridgeL1Token, select: {t.address, t.id}, where: t.address in ^addresses) + + query + |> Repo.all(timeout: :infinity) + |> Enum.reduce(%{}, fn {address, id}, acc -> Map.put(acc, Hash.to_string(address), id) end) + end end From 7c0b0b1ab352aa13c3ec4af4c7985eb8ad515642 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Fri, 22 Dec 2023 14:32:39 +0300 Subject: [PATCH 428/607] Add Indexer.Fetcher.Zkevm.BridgeL2 --- .../lib/indexer/block/realtime/fetcher.ex | 10 + .../lib/indexer/fetcher/zkevm/bridge.ex | 435 ++++++++++++++++++ .../lib/indexer/fetcher/zkevm/bridge_l1.ex | 421 +---------------- .../lib/indexer/fetcher/zkevm/bridge_l2.ex | 190 ++++++++ 4 files changed, 642 insertions(+), 414 deletions(-) create mode 100644 apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex create mode 100644 apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index 5ff9358563bf..4896dab7628c 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -37,6 +37,7 @@ defmodule Indexer.Block.Realtime.Fetcher do alias Indexer.Fetcher.{CoinBalance, CoinBalanceDailyUpdater} alias Indexer.Fetcher.PolygonEdge.{DepositExecute, Withdrawal} alias Indexer.Fetcher.Shibarium.L2, as: ShibariumBridgeL2 + alias Indexer.Fetcher.Zkevm.BridgeL2, as: ZkevmBridgeL2 alias Indexer.Prometheus alias Indexer.Transform.Addresses alias Timex.Duration @@ -292,6 +293,9 @@ defmodule Indexer.Block.Realtime.Fetcher do # we need to remove all rows from `shibarium_bridge` table previously written starting from reorg block number remove_shibarium_assets_by_number(block_number_to_fetch) + # we need to remove all rows from `zkevm_bridge` table previously written starting from reorg block number + remove_polygon_zkevm_assets_by_number(block_number_to_fetch) + # give previous fetch attempt (for same block number) a chance to finish # before fetching again, to reduce block consensus mistakes :timer.sleep(@reorg_delay) @@ -311,6 +315,12 @@ defmodule Indexer.Block.Realtime.Fetcher do end end + defp remove_polygon_zkevm_assets_by_number(block_number_to_fetch) do + if Application.get_env(:explorer, :chain_type) == "polygon_zkevm" do + ZkevmBridgeL2.reorg_handle(block_number_to_fetch) + end + end + defp remove_shibarium_assets_by_number(block_number_to_fetch) do if Application.get_env(:explorer, :chain_type) == "shibarium" do ShibariumBridgeL2.reorg_handle(block_number_to_fetch) diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex new file mode 100644 index 000000000000..5799251a8297 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex @@ -0,0 +1,435 @@ +defmodule Indexer.Fetcher.Zkevm.Bridge do + @moduledoc """ + Contains common functions for Indexer.Fetcher.Zkevm.Bridge* modules. + """ + + require Logger + + import Ecto.Query + + import EthereumJSONRPC, + only: [ + integer_to_quantity: 1, + json_rpc: 2, + quantity_to_integer: 1, + request: 1 + ] + + import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] + + import Explorer.Helper, only: [decode_data: 2] + + alias EthereumJSONRPC.Block.ByNumber + alias EthereumJSONRPC.Blocks + alias Explorer.Chain.Hash + alias Explorer.Chain.Zkevm.BridgeL1Token + alias Explorer.{Chain, Repo} + alias Explorer.SmartContract.Reader + alias Indexer.Helper + + # 32-byte signature of the event BridgeEvent(uint8 leafType, uint32 originNetwork, address originAddress, uint32 destinationNetwork, address destinationAddress, uint256 amount, bytes metadata, uint32 depositCount) + @bridge_event "0x501781209a1f8899323b96b4ef08b168df93e0a90c673d1e4cce39366cb62f9b" + @bridge_event_params [{:uint, 8}, {:uint, 32}, :address, {:uint, 32}, :address, {:uint, 256}, :bytes, {:uint, 32}] + + # 32-byte signature of the event ClaimEvent(uint32 index, uint32 originNetwork, address originAddress, address destinationAddress, uint256 amount) + @claim_event "0x25308c93ceeed162da955b3f7ce3e3f93606579e40fb92029faa9efe27545983" + @claim_event_params [{:uint, 32}, {:uint, 32}, :address, :address, {:uint, 256}] + + @erc20_abi [ + %{ + "constant" => true, + "inputs" => [], + "name" => "symbol", + "outputs" => [%{"name" => "", "type" => "string"}], + "payable" => false, + "stateMutability" => "view", + "type" => "function" + }, + %{ + "constant" => true, + "inputs" => [], + "name" => "decimals", + "outputs" => [%{"name" => "", "type" => "uint8"}], + "payable" => false, + "stateMutability" => "view", + "type" => "function" + } + ] + + @spec get_logs_all({non_neg_integer(), non_neg_integer()}, binary(), list()) :: list() + def get_logs_all({chunk_start, chunk_end}, bridge_contract, json_rpc_named_arguments) do + {:ok, result} = + get_logs( + chunk_start, + chunk_end, + bridge_contract, + [[@bridge_event, @claim_event]], + json_rpc_named_arguments + ) + + result + end + + defp get_logs(from_block, to_block, address, topics, json_rpc_named_arguments, retries \\ 100_000_000) do + processed_from_block = if is_integer(from_block), do: integer_to_quantity(from_block), else: from_block + processed_to_block = if is_integer(to_block), do: integer_to_quantity(to_block), else: to_block + + req = + request(%{ + id: 0, + method: "eth_getLogs", + params: [ + %{ + :fromBlock => processed_from_block, + :toBlock => processed_to_block, + :address => address, + :topics => topics + } + ] + }) + + error_message = &"Cannot fetch logs for the block range #{from_block}..#{to_block}. Error: #{inspect(&1)}" + + Helper.repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) + end + + @spec import_operations(list()) :: no_return() + def import_operations(operations) do + # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise + import_options = + if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do + %{ + zkevm_bridge_operations: %{params: operations}, + timeout: :infinity + } + else + %{} + end + + {:ok, _} = Chain.import(import_options) + end + + @spec json_rpc_named_arguments(binary()) :: list() + def json_rpc_named_arguments(rpc_url) do + [ + transport: EthereumJSONRPC.HTTP, + transport_options: [ + http: EthereumJSONRPC.HTTP.HTTPoison, + url: rpc_url, + http_options: [ + recv_timeout: :timer.minutes(10), + timeout: :timer.minutes(10), + hackney: [pool: :ethereum_jsonrpc] + ] + ] + ] + end + + @spec prepare_operations(list(), list(), list()) :: list() + def prepare_operations(events, json_rpc_named_arguments, json_rpc_named_arguments_l1) do + deposit_events = Enum.filter(events, fn event -> Enum.at(event["topics"], 0) == @bridge_event end) + + block_to_timestamp = blocks_to_timestamps(deposit_events, json_rpc_named_arguments) + + token_address_to_id = token_addresses_to_ids(deposit_events, json_rpc_named_arguments_l1) + + Enum.map(events, fn event -> + {type, index, l1_token_id, l2_token_address, amount, block_number, block_timestamp} = + if Enum.at(event["topics"], 0) == @bridge_event do + [ + leaf_type, + origin_network, + origin_address, + _destination_network, + _destination_address, + amount, + _metadata, + deposit_count + ] = decode_data(event["data"], @bridge_event_params) + + {l1_token_address, l2_token_address} = + token_address_by_origin_address(origin_address, origin_network, leaf_type) + + l1_token_id = Map.get(token_address_to_id, l1_token_address) + block_number = quantity_to_integer(event["blockNumber"]) + block_timestamp = Map.get(block_to_timestamp, block_number) + + {:deposit, deposit_count, l1_token_id, l2_token_address, amount, block_number, block_timestamp} + else + [index, _origin_network, _origin_address, _destination_address, amount] = + decode_data(event["data"], @claim_event_params) + + {:withdrawal, index, nil, nil, amount, nil, nil} + end + + result = %{ + type: type, + index: index, + amount: amount + } + + transaction_hash_field = + if json_rpc_named_arguments == json_rpc_named_arguments_l1 do + :l1_transaction_hash + else + :l2_transaction_hash + end + + result + |> extend_result(transaction_hash_field, event["transactionHash"]) + |> extend_result(:l1_token_id, l1_token_id) + |> extend_result(:l2_token_address, l2_token_address) + |> extend_result(:block_number, block_number) + |> extend_result(:block_timestamp, block_timestamp) + end) + end + + defp blocks_to_timestamps(deposit_events, json_rpc_named_arguments) do + deposit_events + |> get_blocks_by_events(json_rpc_named_arguments, 100_000_000) + |> Enum.reduce(%{}, fn block, acc -> + block_number = quantity_to_integer(Map.get(block, "number")) + {:ok, timestamp} = DateTime.from_unix(quantity_to_integer(Map.get(block, "timestamp"))) + Map.put(acc, block_number, timestamp) + end) + end + + defp get_blocks_by_events(events, json_rpc_named_arguments, retries) do + request = + events + |> Enum.reduce(%{}, fn event, acc -> + Map.put(acc, event["blockNumber"], 0) + end) + |> Stream.map(fn {block_number, _} -> %{number: block_number} end) + |> Stream.with_index() + |> Enum.into(%{}, fn {params, id} -> {id, params} end) + |> Blocks.requests(&ByNumber.request(&1, false, false)) + + error_message = &"Cannot fetch blocks with batch request. Error: #{inspect(&1)}. Request: #{inspect(request)}" + + case Helper.repeated_call(&json_rpc/2, [request, json_rpc_named_arguments], error_message, retries) do + {:ok, results} -> Enum.map(results, fn %{result: result} -> result end) + {:error, _} -> [] + end + end + + defp token_addresses_to_ids(deposit_events, json_rpc_named_arguments) do + token_data = + deposit_events + |> Enum.reduce(%MapSet{}, fn event, acc -> + [ + leaf_type, + origin_network, + origin_address, + _destination_network, + _destination_address, + _amount, + _metadata, + _deposit_count + ] = decode_data(event["data"], @bridge_event_params) + + case token_address_by_origin_address(origin_address, origin_network, leaf_type) do + {nil, _} -> acc + {token_address, nil} -> MapSet.put(acc, token_address) + end + end) + |> MapSet.to_list() + |> get_token_data(json_rpc_named_arguments) + + tokens_existing = + token_data + |> Map.keys() + |> token_addresses_to_ids_from_db() + + tokens_to_insert = + token_data + |> Enum.reject(fn {address, _} -> Map.has_key?(tokens_existing, address) end) + |> Enum.map(fn {address, data} -> Map.put(data, :address, address) end) + + # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise + import_options = + if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do + %{ + zkevm_bridge_l1_tokens: %{params: tokens_to_insert}, + timeout: :infinity + } + else + %{} + end + + {:ok, inserts} = Chain.import(import_options) + + tokens_inserted = Map.get(inserts, :insert_zkevm_bridge_l1_tokens, []) + + # we need to query uninserted tokens from DB separately as they + # could be inserted by another module at the same time (a race condition). + # this is an unlikely case but we handle it here as well + tokens_uninserted = + tokens_to_insert + |> Enum.reject(fn token -> + Enum.any?(tokens_inserted, fn inserted -> token.address == Hash.to_string(inserted.address) end) + end) + |> Enum.map(& &1.address) + + tokens_inserted_outside = token_addresses_to_ids_from_db(tokens_uninserted) + + tokens_inserted + |> Enum.reduce(%{}, fn t, acc -> Map.put(acc, Hash.to_string(t.address), t.id) end) + |> Map.merge(tokens_existing) + |> Map.merge(tokens_inserted_outside) + end + + defp token_addresses_to_ids_from_db(addresses) do + query = from(t in BridgeL1Token, select: {t.address, t.id}, where: t.address in ^addresses) + + query + |> Repo.all(timeout: :infinity) + |> Enum.reduce(%{}, fn {address, id}, acc -> Map.put(acc, Hash.to_string(address), id) end) + end + + defp token_address_by_origin_address(origin_address, origin_network, leaf_type) do + with true <- leaf_type != 1 and origin_network <= 1, + token_address = "0x" <> Base.encode16(origin_address, case: :lower), + true <- token_address != burn_address_hash_string() do + if origin_network == 0 do + # this is L1 address + {token_address, nil} + else + # this is L2 address + {nil, token_address} + end + else + _ -> {nil, nil} + end + end + + defp get_token_data(token_addresses, json_rpc_named_arguments) do + # first, we're trying to read token data from the DB. + # if tokens are not in the DB, read them through RPC. + token_addresses + |> get_token_data_from_db() + |> get_token_data_from_rpc(json_rpc_named_arguments) + end + + defp get_token_data_from_db(token_addresses) do + # try to read token symbols and decimals from the database + query = + from( + t in BridgeL1Token, + where: t.address in ^token_addresses, + select: {t.address, t.decimals, t.symbol} + ) + + token_data = + query + |> Repo.all() + |> Enum.reduce(%{}, fn {address, decimals, symbol}, acc -> + token_address = String.downcase(Hash.to_string(address)) + Map.put(acc, token_address, %{symbol: symbol, decimals: decimals}) + end) + + token_addresses_for_rpc = + token_addresses + |> Enum.reject(fn address -> + Map.has_key?(token_data, String.downcase(address)) + end) + + {token_data, token_addresses_for_rpc} + end + + defp get_token_data_from_rpc({token_data, token_addresses}, json_rpc_named_arguments) do + {requests, responses} = get_token_data_request_symbol_decimals(token_addresses, json_rpc_named_arguments) + + requests + |> Enum.zip(responses) + |> Enum.reduce(token_data, fn {request, {status, response} = _resp}, token_data_acc -> + if status == :ok do + response = parse_response(response) + + address = String.downcase(request.contract_address) + + new_data = get_new_data(token_data_acc[address] || %{}, request, response) + + Map.put(token_data_acc, address, new_data) + else + token_data_acc + end + end) + end + + defp get_token_data_request_symbol_decimals(token_addresses, json_rpc_named_arguments) do + requests = + token_addresses + |> Enum.map(fn address -> + # we will call symbol() and decimals() public getters + Enum.map(["95d89b41", "313ce567"], fn method_id -> + %{ + contract_address: address, + method_id: method_id, + args: [] + } + end) + end) + |> List.flatten() + + {responses, error_messages} = read_contracts_with_retries(requests, @erc20_abi, json_rpc_named_arguments, 3) + + if !Enum.empty?(error_messages) or Enum.count(requests) != Enum.count(responses) do + Logger.warning( + "Cannot read symbol and decimals of an ERC-20 token contract. Error messages: #{Enum.join(error_messages, ", ")}. Addresses: #{Enum.join(token_addresses, ", ")}" + ) + end + + {requests, responses} + end + + defp read_contracts_with_retries(requests, abi, json_rpc_named_arguments, retries_left) when retries_left > 0 do + responses = Reader.query_contracts(requests, abi, json_rpc_named_arguments: json_rpc_named_arguments) + + error_messages = + Enum.reduce(responses, [], fn {status, error_message}, acc -> + acc ++ + if status == :error do + [error_message] + else + [] + end + end) + + if Enum.empty?(error_messages) do + {responses, []} + else + retries_left = retries_left - 1 + + if retries_left == 0 do + {responses, Enum.uniq(error_messages)} + else + :timer.sleep(1000) + read_contracts_with_retries(requests, abi, json_rpc_named_arguments, retries_left) + end + end + end + + defp get_new_data(data, request, response) do + if atomized_key(request.method_id) == :symbol do + Map.put(data, :symbol, response) + else + Map.put(data, :decimals, response) + end + end + + defp extend_result(result, _key, value) when is_nil(value), do: result + defp extend_result(result, key, value) when is_atom(key), do: Map.put(result, key, value) + + defp atomized_key("symbol"), do: :symbol + defp atomized_key("decimals"), do: :decimals + defp atomized_key("95d89b41"), do: :symbol + defp atomized_key("313ce567"), do: :decimals + + defp parse_response(response) do + case response do + [item] -> item + items -> items + end + end +end diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex index a9ea191590bf..b497eaa95408 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex @@ -9,60 +9,19 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do require Logger import Ecto.Query + import Explorer.Helper, only: [parse_integer: 1] - import EthereumJSONRPC, - only: [ - integer_to_quantity: 1, - json_rpc: 2, - quantity_to_integer: 1, - request: 1 - ] + import Indexer.Fetcher.Zkevm.Bridge, + only: [get_logs_all: 3, import_operations: 1, json_rpc_named_arguments: 1, prepare_operations: 3] - import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] - - import Explorer.Helper, only: [parse_integer: 1, decode_data: 2] - - alias EthereumJSONRPC.Block.ByNumber - alias EthereumJSONRPC.Blocks - alias Explorer.Chain.Hash - alias Explorer.Chain.Zkevm.{Bridge, BridgeL1Token} - alias Explorer.{Chain, Repo} - alias Explorer.SmartContract.Reader + alias Explorer.Chain.Zkevm.Bridge + alias Explorer.Repo alias Indexer.{BoundQueue, Helper} @block_check_interval_range_size 100 @eth_get_logs_range_size 1000 @fetcher_name :zkevm_bridge_l1 - # 32-byte signature of the event BridgeEvent(uint8 leafType, uint32 originNetwork, address originAddress, uint32 destinationNetwork, address destinationAddress, uint256 amount, bytes metadata, uint32 depositCount) - @bridge_event "0x501781209a1f8899323b96b4ef08b168df93e0a90c673d1e4cce39366cb62f9b" - @bridge_event_params [{:uint, 8}, {:uint, 32}, :address, {:uint, 32}, :address, {:uint, 256}, :bytes, {:uint, 32}] - - # 32-byte signature of the event ClaimEvent(uint32 index, uint32 originNetwork, address originAddress, address destinationAddress, uint256 amount) - @claim_event "0x25308c93ceeed162da955b3f7ce3e3f93606579e40fb92029faa9efe27545983" - @claim_event_params [{:uint, 32}, {:uint, 32}, :address, :address, {:uint, 256}] - - @erc20_abi [ - %{ - "constant" => true, - "inputs" => [], - "name" => "symbol", - "outputs" => [%{"name" => "", "type" => "string"}], - "payable" => false, - "stateMutability" => "view", - "type" => "function" - }, - %{ - "constant" => true, - "inputs" => [], - "name" => "decimals", - "outputs" => [%{"name" => "", "type" => "uint8"}], - "payable" => false, - "stateMutability" => "view", - "type" => "function" - } - ] - def child_spec(start_link_arguments) do spec = %{ id: __MODULE__, @@ -206,7 +165,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do operations = {chunk_start, chunk_end} |> get_logs_all(bridge_contract, json_rpc_named_arguments) - |> prepare_operations(json_rpc_named_arguments) + |> prepare_operations(json_rpc_named_arguments, json_rpc_named_arguments) import_operations(operations) @@ -252,24 +211,6 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do {:noreply, state} end - defp atomized_key("symbol"), do: :symbol - defp atomized_key("decimals"), do: :decimals - defp atomized_key("95d89b41"), do: :symbol - defp atomized_key("313ce567"), do: :decimals - - defp blocks_to_timestamps(deposit_events, json_rpc_named_arguments) do - deposit_events - |> get_blocks_by_events(json_rpc_named_arguments, 100_000_000) - |> Enum.reduce(%{}, fn block, acc -> - block_number = quantity_to_integer(Map.get(block, "number")) - {:ok, timestamp} = DateTime.from_unix(quantity_to_integer(Map.get(block, "timestamp"))) - Map.put(acc, block_number, timestamp) - end) - end - - defp extend_result(result, _key, value) when is_nil(value), do: result - defp extend_result(result, key, value) when is_atom(key), do: Map.put(result, key, value) - defp get_block_check_interval(json_rpc_named_arguments) do {last_safe_block, _} = get_safe_block(json_rpc_named_arguments) @@ -290,25 +231,6 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do end end - defp get_blocks_by_events(events, json_rpc_named_arguments, retries) do - request = - events - |> Enum.reduce(%{}, fn event, acc -> - Map.put(acc, event["blockNumber"], 0) - end) - |> Stream.map(fn {block_number, _} -> %{number: block_number} end) - |> Stream.with_index() - |> Enum.into(%{}, fn {params, id} -> {id, params} end) - |> Blocks.requests(&ByNumber.request(&1, false, false)) - - error_message = &"Cannot fetch blocks with batch request. Error: #{inspect(&1)}. Request: #{inspect(request)}" - - case Helper.repeated_call(&json_rpc/2, [request, json_rpc_named_arguments], error_message, retries) do - {:ok, results} -> Enum.map(results, fn %{result: result} -> result end) - {:error, _} -> [] - end - end - defp get_last_l1_item do query = from(b in Bridge, @@ -323,42 +245,6 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do |> Kernel.||({0, nil}) end - defp get_logs(from_block, to_block, address, topics, json_rpc_named_arguments, retries \\ 100_000_000) do - processed_from_block = if is_integer(from_block), do: integer_to_quantity(from_block), else: from_block - processed_to_block = if is_integer(to_block), do: integer_to_quantity(to_block), else: to_block - - req = - request(%{ - id: 0, - method: "eth_getLogs", - params: [ - %{ - :fromBlock => processed_from_block, - :toBlock => processed_to_block, - :address => address, - :topics => topics - } - ] - }) - - error_message = &"Cannot fetch logs for the block range #{from_block}..#{to_block}. Error: #{inspect(&1)}" - - Helper.repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) - end - - defp get_logs_all({chunk_start, chunk_end}, bridge_contract, json_rpc_named_arguments) do - {:ok, result} = - get_logs( - chunk_start, - chunk_end, - bridge_contract, - [[@bridge_event, @claim_event]], - json_rpc_named_arguments - ) - - result - end - defp get_safe_block(json_rpc_named_arguments) do case Helper.get_block_number_by_tag("safe", json_rpc_named_arguments) do {:ok, safe_block} -> @@ -370,209 +256,6 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do end end - defp get_token_data(token_addresses, json_rpc_named_arguments) do - # first, we're trying to read token data from the DB. - # if tokens are not in the DB, read them through RPC. - token_addresses - |> get_token_data_from_db() - |> get_token_data_from_rpc(json_rpc_named_arguments) - end - - defp get_token_data_from_db(token_addresses) do - # try to read token symbols and decimals from the database - query = - from( - t in BridgeL1Token, - where: t.address in ^token_addresses, - select: {t.address, t.decimals, t.symbol} - ) - - token_data = - query - |> Repo.all() - |> Enum.reduce(%{}, fn {address, decimals, symbol}, acc -> - token_address = String.downcase(Hash.to_string(address)) - Map.put(acc, token_address, %{symbol: symbol, decimals: decimals}) - end) - - token_addresses_for_rpc = - token_addresses - |> Enum.reject(fn address -> - Map.has_key?(token_data, String.downcase(address)) - end) - - {token_data, token_addresses_for_rpc} - end - - defp get_token_data_from_rpc({token_data, token_addresses}, json_rpc_named_arguments) do - {requests, responses} = get_token_data_request_symbol_decimals(token_addresses, json_rpc_named_arguments) - - requests - |> Enum.zip(responses) - |> Enum.reduce(token_data, fn {request, {status, response} = _resp}, token_data_acc -> - if status == :ok do - response = parse_response(response) - - address = String.downcase(request.contract_address) - - new_data = get_new_data(token_data_acc[address] || %{}, request, response) - - Map.put(token_data_acc, address, new_data) - else - token_data_acc - end - end) - end - - defp parse_response(response) do - case response do - [item] -> item - items -> items - end - end - - defp get_new_data(data, request, response) do - if atomized_key(request.method_id) == :symbol do - Map.put(data, :symbol, response) - else - Map.put(data, :decimals, response) - end - end - - defp get_token_data_request_symbol_decimals(token_addresses, json_rpc_named_arguments) do - requests = - token_addresses - |> Enum.map(fn address -> - # we will call symbol() and decimals() public getters - Enum.map(["95d89b41", "313ce567"], fn method_id -> - %{ - contract_address: address, - method_id: method_id, - args: [] - } - end) - end) - |> List.flatten() - - {responses, error_messages} = read_contracts_with_retries(requests, @erc20_abi, json_rpc_named_arguments, 3) - - if !Enum.empty?(error_messages) or Enum.count(requests) != Enum.count(responses) do - Logger.warning( - "Cannot read symbol and decimals of an ERC-20 token contract. Error messages: #{Enum.join(error_messages, ", ")}. Addresses: #{Enum.join(token_addresses, ", ")}" - ) - end - - {requests, responses} - end - - defp import_operations(operations) do - # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise - import_options = - if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do - %{ - zkevm_bridge_operations: %{params: operations}, - timeout: :infinity - } - else - %{} - end - - {:ok, _} = Chain.import(import_options) - end - - defp json_rpc_named_arguments(rpc_url) do - [ - transport: EthereumJSONRPC.HTTP, - transport_options: [ - http: EthereumJSONRPC.HTTP.HTTPoison, - url: rpc_url, - http_options: [ - recv_timeout: :timer.minutes(10), - timeout: :timer.minutes(10), - hackney: [pool: :ethereum_jsonrpc] - ] - ] - ] - end - - defp prepare_operations(events, json_rpc_named_arguments) do - deposit_events = Enum.filter(events, fn event -> Enum.at(event["topics"], 0) == @bridge_event end) - - block_to_timestamp = blocks_to_timestamps(deposit_events, json_rpc_named_arguments) - - token_address_to_id = token_addresses_to_ids(deposit_events, json_rpc_named_arguments) - - Enum.map(events, fn event -> - {type, index, l1_token_id, l2_token_address, amount, block_number, block_timestamp} = - if Enum.at(event["topics"], 0) == @bridge_event do - [ - leaf_type, - origin_network, - origin_address, - _destination_network, - _destination_address, - amount, - _metadata, - deposit_count - ] = decode_data(event["data"], @bridge_event_params) - - {l1_token_address, l2_token_address} = - token_address_by_origin_address(origin_address, origin_network, leaf_type) - - l1_token_id = Map.get(token_address_to_id, l1_token_address) - block_number = quantity_to_integer(event["blockNumber"]) - block_timestamp = Map.get(block_to_timestamp, block_number) - - {:deposit, deposit_count, l1_token_id, l2_token_address, amount, block_number, block_timestamp} - else - [index, _origin_network, _origin_address, _destination_address, amount] = - decode_data(event["data"], @claim_event_params) - - {:withdrawal, index, nil, nil, amount, nil, nil} - end - - result = %{ - type: type, - index: index, - l1_transaction_hash: event["transactionHash"], - amount: amount - } - - result - |> extend_result(:l1_token_id, l1_token_id) - |> extend_result(:l2_token_address, l2_token_address) - |> extend_result(:block_number, block_number) - |> extend_result(:block_timestamp, block_timestamp) - end) - end - - defp read_contracts_with_retries(requests, abi, json_rpc_named_arguments, retries_left) when retries_left > 0 do - responses = Reader.query_contracts(requests, abi, json_rpc_named_arguments: json_rpc_named_arguments) - - error_messages = - Enum.reduce(responses, [], fn {status, error_message}, acc -> - acc ++ - if status == :error do - [error_message] - else - [] - end - end) - - if Enum.empty?(error_messages) do - {responses, []} - else - retries_left = retries_left - 1 - - if retries_left == 0 do - {responses, Enum.uniq(error_messages)} - else - :timer.sleep(1000) - read_contracts_with_retries(requests, abi, json_rpc_named_arguments, retries_left) - end - end - end - defp reorg_block_pop do table_name = reorg_table_name(@fetcher_name) @@ -594,7 +277,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do defp reorg_handle(reorg_block) do {deleted_count, _} = - Repo.delete_all(from(b in Bridge, where: b.type == :deposit and b.l1_block_number >= ^reorg_block)) + Repo.delete_all(from(b in Bridge, where: b.type == :deposit and b.block_number >= ^reorg_block)) if deleted_count > 0 do Logger.warning( @@ -625,94 +308,4 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do defp reorg_table_name(fetcher_name) do :"#{fetcher_name}#{:_reorgs}" end - - defp token_address_by_origin_address(origin_address, origin_network, leaf_type) do - with true <- leaf_type != 1 and origin_network <= 1, - token_address = "0x" <> Base.encode16(origin_address, case: :lower), - true <- token_address != burn_address_hash_string() do - if origin_network == 0 do - # this is L1 address - {token_address, nil} - else - # this is L2 address - {nil, token_address} - end - else - _ -> {nil, nil} - end - end - - defp token_addresses_to_ids(deposit_events, json_rpc_named_arguments) do - token_data = - deposit_events - |> Enum.reduce(%MapSet{}, fn event, acc -> - [ - leaf_type, - origin_network, - origin_address, - _destination_network, - _destination_address, - _amount, - _metadata, - _deposit_count - ] = decode_data(event["data"], @bridge_event_params) - - case token_address_by_origin_address(origin_address, origin_network, leaf_type) do - {nil, _} -> acc - {token_address, nil} -> MapSet.put(acc, token_address) - end - end) - |> MapSet.to_list() - |> get_token_data(json_rpc_named_arguments) - - tokens_existing = - token_data - |> Map.keys() - |> token_addresses_to_ids_from_db() - - tokens_to_insert = - token_data - |> Enum.reject(fn {address, _} -> Map.has_key?(tokens_existing, address) end) - |> Enum.map(fn {address, data} -> Map.put(data, :address, address) end) - - # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise - import_options = - if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do - %{ - zkevm_bridge_l1_tokens: %{params: tokens_to_insert}, - timeout: :infinity - } - else - %{} - end - - {:ok, inserts} = Chain.import(import_options) - - tokens_inserted = Map.get(inserts, :insert_zkevm_bridge_l1_tokens, []) - - # we need to query uninserted tokens separately from DB as they - # could be inserted by BridgeL2 module at the same time (a race condition). - # this is an unlikely case but we handle it here as well - tokens_uninserted = - tokens_to_insert - |> Enum.reject(fn token -> - Enum.any?(tokens_inserted, fn inserted -> token.address == Hash.to_string(inserted.address) end) - end) - |> Enum.map(& &1.address) - - tokens_inserted_outside = token_addresses_to_ids_from_db(tokens_uninserted) - - tokens_inserted - |> Enum.reduce(%{}, fn t, acc -> Map.put(acc, Hash.to_string(t.address), t.id) end) - |> Map.merge(tokens_existing) - |> Map.merge(tokens_inserted_outside) - end - - defp token_addresses_to_ids_from_db(addresses) do - query = from(t in BridgeL1Token, select: {t.address, t.id}, where: t.address in ^addresses) - - query - |> Repo.all(timeout: :infinity) - |> Enum.reduce(%{}, fn {address, id}, acc -> Map.put(acc, Hash.to_string(address), id) end) - end end diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex new file mode 100644 index 000000000000..d2be5dff6f25 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex @@ -0,0 +1,190 @@ +defmodule Indexer.Fetcher.Zkevm.BridgeL2 do + @moduledoc """ + Fills zkevm_bridge DB table. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + import Ecto.Query + import Explorer.Helper, only: [parse_integer: 1] + + import Indexer.Fetcher.Zkevm.Bridge, + only: [get_logs_all: 3, import_operations: 1, json_rpc_named_arguments: 1, prepare_operations: 3] + + alias Explorer.Chain.Zkevm.Bridge + alias Explorer.Repo + alias Indexer.Helper + + @eth_get_logs_range_size 1000 + @fetcher_name :zkevm_bridge_l2 + + def child_spec(start_link_arguments) do + spec = %{ + id: __MODULE__, + start: {__MODULE__, :start_link, start_link_arguments}, + restart: :transient, + type: :worker + } + + Supervisor.child_spec(spec, []) + end + + def start_link(args, gen_server_options \\ []) do + GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__)) + end + + @impl GenServer + def init(args) do + json_rpc_named_arguments = args[:json_rpc_named_arguments] + {:ok, %{}, {:continue, json_rpc_named_arguments}} + end + + @impl GenServer + def handle_continue(json_rpc_named_arguments, _state) do + Logger.metadata(fetcher: @fetcher_name) + # two seconds pause needed to avoid exceeding Supervisor restart intensity when DB issues + Process.send_after(self(), :init_with_delay, 2000) + {:noreply, %{json_rpc_named_arguments: json_rpc_named_arguments}} + end + + @impl GenServer + def handle_info(:init_with_delay, %{json_rpc_named_arguments: json_rpc_named_arguments} = state) do + env = Application.get_all_env(:indexer)[__MODULE__] + + with {:start_block_undefined, false} <- {:start_block_undefined, is_nil(env[:start_block])}, + rpc_l1 = Application.get_all_env(:indexer)[Indexer.Fetcher.Zkevm.BridgeL1][:rpc], + {:rpc_l1_undefined, false} <- {:rpc_l1_undefined, is_nil(rpc_l1)}, + {:bridge_contract_address_is_valid, true} <- + {:bridge_contract_address_is_valid, Helper.is_address_correct?(env[:bridge_contract])}, + start_block = parse_integer(env[:start_block]), + false <- is_nil(start_block), + true <- start_block > 0, + {last_l2_block_number, last_l2_transaction_hash} = get_last_l2_item(), + {:ok, latest_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000), + {:start_block_valid, true} <- + {:start_block_valid, + (start_block <= last_l2_block_number || last_l2_block_number == 0) && start_block <= latest_block}, + {:ok, last_l2_tx} <- Helper.get_transaction_by_hash(last_l2_transaction_hash, json_rpc_named_arguments), + {:l2_tx_not_found, false} <- {:l2_tx_not_found, !is_nil(last_l2_transaction_hash) && is_nil(last_l2_tx)} do + Process.send(self(), :continue, []) + + {:noreply, + %{ + bridge_contract: env[:bridge_contract], + json_rpc_named_arguments: json_rpc_named_arguments, + json_rpc_named_arguments_l1: json_rpc_named_arguments(rpc_l1), + end_block: latest_block, + start_block: max(start_block, last_l2_block_number) + }} + else + {:start_block_undefined, true} -> + # the process shouldn't start if the start block is not defined + {:stop, :normal, state} + + {:rpc_l1_undefined, true} -> + Logger.error("L1 RPC URL is not defined.") + {:stop, :normal, state} + + {:bridge_contract_address_is_valid, false} -> + Logger.error("PolygonZkEVMBridge contract address is invalid or not defined.") + {:stop, :normal, state} + + {:start_block_valid, false} -> + Logger.error("Invalid L2 Start Block value. Please, check the value and zkevm_bridge table.") + {:stop, :normal, state} + + {:error, error_data} -> + Logger.error( + "Cannot get last L2 transaction from RPC by its hash or latest block due to RPC error: #{inspect(error_data)}" + ) + + {:stop, :normal, state} + + {:l2_tx_not_found, true} -> + Logger.error( + "Cannot find last L2 transaction from RPC by its hash. Probably, there was a reorg on L2 chain. Please, check zkevm_bridge table." + ) + + {:stop, :normal, state} + + _ -> + Logger.error("L2 Start Block is invalid or zero.") + {:stop, :normal, state} + end + end + + @impl GenServer + def handle_info( + :continue, + %{ + bridge_contract: bridge_contract, + start_block: start_block, + end_block: end_block, + json_rpc_named_arguments: json_rpc_named_arguments, + json_rpc_named_arguments_l1: json_rpc_named_arguments_l1 + } = state + ) do + start_block..end_block + |> Enum.chunk_every(@eth_get_logs_range_size) + |> Enum.each(fn current_chunk -> + chunk_start = List.first(current_chunk) + chunk_end = List.last(current_chunk) + + if chunk_start <= chunk_end do + Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, "L2") + + operations = + {chunk_start, chunk_end} + |> get_logs_all(bridge_contract, json_rpc_named_arguments) + |> prepare_operations(json_rpc_named_arguments, json_rpc_named_arguments_l1) + + import_operations(operations) + + Helper.log_blocks_chunk_handling( + chunk_start, + chunk_end, + start_block, + end_block, + "#{Enum.count(operations)} L2 operation(s)", + "L2" + ) + end + end) + + {:stop, :normal, state} + end + + @impl GenServer + def handle_info({ref, _result}, state) do + Process.demonitor(ref, [:flush]) + {:noreply, state} + end + + def reorg_handle(reorg_block) do + {deleted_count, _} = + Repo.delete_all(from(b in Bridge, where: b.type == :withdrawal and b.block_number >= ^reorg_block)) + + if deleted_count > 0 do + Logger.warning( + "As L2 reorg was detected, some withdrawals with block_number >= #{reorg_block} were removed from zkevm_bridge table. Number of removed rows: #{deleted_count}." + ) + end + end + + defp get_last_l2_item do + query = + from(b in Bridge, + select: {b.block_number, b.l2_transaction_hash}, + where: b.type == :withdrawal and not is_nil(b.block_number), + order_by: [desc: b.index], + limit: 1 + ) + + query + |> Repo.one() + |> Kernel.||({0, nil}) + end +end From 0d041964733d86923545cea5b27549553ddf3a77 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Mon, 25 Dec 2023 15:35:41 +0300 Subject: [PATCH 429/607] Add Indexer.Transform.Zkevm.Bridge --- apps/indexer/lib/indexer/block/fetcher.ex | 21 +++-- .../lib/indexer/fetcher/zkevm/bridge.ex | 46 +++++++---- apps/indexer/lib/indexer/helper.ex | 12 +++ .../lib/indexer/transform/addresses.ex | 10 +++ .../lib/indexer/transform/zkevm/bridge.ex | 77 +++++++++++++++++++ 5 files changed, 148 insertions(+), 18 deletions(-) create mode 100644 apps/indexer/lib/indexer/transform/zkevm/bridge.ex diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 9be25d9837ab..02e83dad42a5 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -47,6 +47,7 @@ defmodule Indexer.Block.Fetcher do alias Indexer.Transform.Shibarium.Bridge, as: ShibariumBridge alias Indexer.Transform.Blocks, as: TransformBlocks + alias Indexer.Transform.Zkevm.Bridge, as: ZkevmBridge @type address_hash_to_fetched_balance_block_number :: %{String.t() => Block.block_number()} @@ -158,6 +159,11 @@ defmodule Indexer.Block.Fetcher do do: ShibariumBridge.parse(blocks, transactions_with_receipts, logs), else: [] ), + zkevm_bridge_operations = + if(callback_module == Indexer.Block.Realtime.Fetcher, + do: ZkevmBridge.parse(blocks, logs), + else: [] + ), %FetchedBeneficiaries{params_set: beneficiary_params_set, errors: beneficiaries_errors} = fetch_beneficiaries(blocks, transactions_with_receipts, json_rpc_named_arguments), addresses = @@ -170,7 +176,8 @@ defmodule Indexer.Block.Fetcher do token_transfers: token_transfers, transactions: transactions_with_receipts, transaction_actions: transaction_actions, - withdrawals: withdrawals_params + withdrawals: withdrawals_params, + zkevm_bridge_operations: zkevm_bridge_operations }), coin_balances_params_set = %{ @@ -206,16 +213,20 @@ defmodule Indexer.Block.Fetcher do }, import_options = (case Application.get_env(:explorer, :chain_type) do + "ethereum" -> + basic_import_options + |> Map.put_new(:beacon_blob_transactions, %{ + params: transactions_with_receipts |> Enum.filter(&Map.has_key?(&1, :max_fee_per_blob_gas)) + }) + "polygon_edge" -> basic_import_options |> Map.put_new(:polygon_edge_withdrawals, %{params: polygon_edge_withdrawals}) |> Map.put_new(:polygon_edge_deposit_executes, %{params: polygon_edge_deposit_executes}) - "ethereum" -> + "polygon_zkevm" -> basic_import_options - |> Map.put_new(:beacon_blob_transactions, %{ - params: transactions_with_receipts |> Enum.filter(&Map.has_key?(&1, :max_fee_per_blob_gas)) - }) + |> Map.put_new(:zkevm_bridge_operations, %{params: zkevm_bridge_operations}) "shibarium" -> basic_import_options diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex index 5799251a8297..ecd32b3f9dc7 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex @@ -20,12 +20,13 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do import Explorer.Helper, only: [decode_data: 2] alias EthereumJSONRPC.Block.ByNumber - alias EthereumJSONRPC.Blocks + alias EthereumJSONRPC.{Blocks, Logs} alias Explorer.Chain.Hash alias Explorer.Chain.Zkevm.BridgeL1Token alias Explorer.{Chain, Repo} alias Explorer.SmartContract.Reader alias Indexer.Helper + alias Indexer.Transform.Addresses # 32-byte signature of the event BridgeEvent(uint8 leafType, uint32 originNetwork, address originAddress, uint32 destinationNetwork, address destinationAddress, uint256 amount, bytes metadata, uint32 depositCount) @bridge_event "0x501781209a1f8899323b96b4ef08b168df93e0a90c673d1e4cce39366cb62f9b" @@ -56,6 +57,14 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do } ] + @spec filter_bridge_events(list(), binary()) :: list() + def filter_bridge_events(events, bridge_contract) do + Enum.filter(events, fn event -> + String.downcase(event.address_hash) == bridge_contract and + Enum.member?([@bridge_event, @claim_event], Helper.log_topic_to_string(event.first_topic)) + end) + end + @spec get_logs_all({non_neg_integer(), non_neg_integer()}, binary(), list()) :: list() def get_logs_all({chunk_start, chunk_end}, bridge_contract, json_rpc_named_arguments) do {:ok, result} = @@ -67,7 +76,7 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do json_rpc_named_arguments ) - result + Logs.elixir_to_params(result) end defp get_logs(from_block, to_block, address, topics, json_rpc_named_arguments, retries \\ 100_000_000) do @@ -98,7 +107,13 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise import_options = if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do + addresses = + Addresses.extract_addresses(%{ + zkevm_bridge_operations: operations + }) + %{ + addresses: %{params: addresses, on_conflict: :nothing}, zkevm_bridge_operations: %{params: operations}, timeout: :infinity } @@ -125,17 +140,22 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do ] end - @spec prepare_operations(list(), list(), list()) :: list() - def prepare_operations(events, json_rpc_named_arguments, json_rpc_named_arguments_l1) do - deposit_events = Enum.filter(events, fn event -> Enum.at(event["topics"], 0) == @bridge_event end) + @spec prepare_operations(list(), list() | nil, list(), map() | nil) :: list() + def prepare_operations(events, json_rpc_named_arguments, json_rpc_named_arguments_l1, block_to_timestamp \\ nil) do + deposit_events = Enum.filter(events, fn event -> event.first_topic == @bridge_event end) - block_to_timestamp = blocks_to_timestamps(deposit_events, json_rpc_named_arguments) + block_to_timestamp = + if is_nil(block_to_timestamp) do + blocks_to_timestamps(deposit_events, json_rpc_named_arguments) + else + block_to_timestamp + end token_address_to_id = token_addresses_to_ids(deposit_events, json_rpc_named_arguments_l1) Enum.map(events, fn event -> {type, index, l1_token_id, l2_token_address, amount, block_number, block_timestamp} = - if Enum.at(event["topics"], 0) == @bridge_event do + if event.first_topic == @bridge_event do [ leaf_type, origin_network, @@ -145,19 +165,19 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do amount, _metadata, deposit_count - ] = decode_data(event["data"], @bridge_event_params) + ] = decode_data(event.data, @bridge_event_params) {l1_token_address, l2_token_address} = token_address_by_origin_address(origin_address, origin_network, leaf_type) l1_token_id = Map.get(token_address_to_id, l1_token_address) - block_number = quantity_to_integer(event["blockNumber"]) + block_number = quantity_to_integer(event.block_number) block_timestamp = Map.get(block_to_timestamp, block_number) {:deposit, deposit_count, l1_token_id, l2_token_address, amount, block_number, block_timestamp} else [index, _origin_network, _origin_address, _destination_address, amount] = - decode_data(event["data"], @claim_event_params) + decode_data(event.data, @claim_event_params) {:withdrawal, index, nil, nil, amount, nil, nil} end @@ -176,7 +196,7 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do end result - |> extend_result(transaction_hash_field, event["transactionHash"]) + |> extend_result(transaction_hash_field, event.transaction_hash) |> extend_result(:l1_token_id, l1_token_id) |> extend_result(:l2_token_address, l2_token_address) |> extend_result(:block_number, block_number) @@ -198,7 +218,7 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do request = events |> Enum.reduce(%{}, fn event, acc -> - Map.put(acc, event["blockNumber"], 0) + Map.put(acc, event.block_number, 0) end) |> Stream.map(fn {block_number, _} -> %{number: block_number} end) |> Stream.with_index() @@ -226,7 +246,7 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do _amount, _metadata, _deposit_count - ] = decode_data(event["data"], @bridge_event_params) + ] = decode_data(event.data, @bridge_event_params) case token_address_by_origin_address(origin_address, origin_network, leaf_type) do {nil, _} -> acc diff --git a/apps/indexer/lib/indexer/helper.ex b/apps/indexer/lib/indexer/helper.ex index 7b980728bd6f..e771f0d66000 100644 --- a/apps/indexer/lib/indexer/helper.ex +++ b/apps/indexer/lib/indexer/helper.ex @@ -257,6 +257,18 @@ defmodule Indexer.Helper do end end + @doc """ + Converts a log topic from Hash.Full representation to string one. + """ + @spec log_topic_to_string(any()) :: binary() | nil + def log_topic_to_string(topic) do + if is_binary(topic) or is_nil(topic) do + topic + else + Hash.to_string(topic) + end + end + def repeated_call(func, args, error_message, retries_left) do case apply(func, args) do {:ok, _} = res -> diff --git a/apps/indexer/lib/indexer/transform/addresses.ex b/apps/indexer/lib/indexer/transform/addresses.ex index b46b0b4dc3a7..aae8fbbb8d49 100644 --- a/apps/indexer/lib/indexer/transform/addresses.ex +++ b/apps/indexer/lib/indexer/transform/addresses.ex @@ -148,6 +148,11 @@ defmodule Indexer.Transform.Addresses do %{from: :block_number, to: :fetched_coin_balance_block_number}, %{from: :address_hash, to: :hash} ] + ], + zkevm_bridge_operations: [ + [ + %{from: :l2_token_address, to: :hash} + ] ] } @@ -455,6 +460,11 @@ defmodule Indexer.Transform.Addresses do required(:address_hash) => String.t(), required(:block_number) => non_neg_integer() } + ], + optional(:zkevm_bridge_operations) => [ + %{ + optional(:l2_token_address) => String.t() + } ] }) :: [params] def extract_addresses(fetched_data, options \\ []) when is_map(fetched_data) and is_list(options) do diff --git a/apps/indexer/lib/indexer/transform/zkevm/bridge.ex b/apps/indexer/lib/indexer/transform/zkevm/bridge.ex new file mode 100644 index 000000000000..77def32bdd8c --- /dev/null +++ b/apps/indexer/lib/indexer/transform/zkevm/bridge.ex @@ -0,0 +1,77 @@ +defmodule Indexer.Transform.Zkevm.Bridge do + @moduledoc """ + Helper functions for transforming data for Polygon zkEVM Bridge operations. + """ + + require Logger + + import Indexer.Fetcher.Zkevm.Bridge, + only: [filter_bridge_events: 2, json_rpc_named_arguments: 1, prepare_operations: 4] + + alias Indexer.Fetcher.Zkevm.{BridgeL1, BridgeL2} + alias Indexer.Helper + + @doc """ + Returns a list of operations given a list of blocks and logs. + """ + @spec parse(list(), list()) :: list() + def parse(blocks, logs) do + prev_metadata = Logger.metadata() + Logger.metadata(fetcher: :zkevm_bridge_l2_realtime) + + items = + with false <- is_nil(Application.get_env(:indexer, BridgeL2)[:start_block]), + false <- System.get_env("CHAIN_TYPE") != "polygon_zkevm", + rpc_l1 = Application.get_all_env(:indexer)[BridgeL1][:rpc], + {:rpc_l1_undefined, false} <- {:rpc_l1_undefined, is_nil(rpc_l1)}, + bridge_contract = Application.get_env(:indexer, BridgeL2)[:bridge_contract], + {:bridge_contract_address_is_valid, true} <- + {:bridge_contract_address_is_valid, Helper.is_address_correct?(bridge_contract)} do + bridge_contract = String.downcase(bridge_contract) + + block_numbers = Enum.map(blocks, fn block -> block.number end) + start_block = Enum.min(block_numbers) + end_block = Enum.max(block_numbers) + + Helper.log_blocks_chunk_handling(start_block, end_block, start_block, end_block, nil, "L2") + + json_rpc_named_arguments_l1 = json_rpc_named_arguments(rpc_l1) + + block_to_timestamp = Enum.reduce(blocks, %{}, fn block, acc -> Map.put(acc, block.number, block.timestamp) end) + + items = + logs + |> filter_bridge_events(bridge_contract) + |> prepare_operations(nil, json_rpc_named_arguments_l1, block_to_timestamp) + + Helper.log_blocks_chunk_handling( + start_block, + end_block, + start_block, + end_block, + "#{Enum.count(items)} L2 operation(s)", + "L2" + ) + + items + else + true -> + [] + + {:rpc_l1_undefined, true} -> + Logger.error("L1 RPC URL is not defined. Cannot use #{__MODULE__} for parsing logs.") + [] + + {:bridge_contract_address_is_valid, false} -> + Logger.error( + "PolygonZkEVMBridge contract address is invalid or not defined. Cannot use #{__MODULE__} for parsing logs." + ) + + [] + end + + Logger.reset_metadata(prev_metadata) + + items + end +end From 426b85bf4ade694c693a6527ec54a6426876b470 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Mon, 25 Dec 2023 16:05:21 +0300 Subject: [PATCH 430/607] Fix operation type --- .../lib/indexer/fetcher/zkevm/bridge.ex | 36 +++++++++++++------ .../lib/indexer/fetcher/zkevm/bridge_l1.ex | 10 ++++-- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex index ecd32b3f9dc7..205e739bfa33 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex @@ -142,16 +142,18 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do @spec prepare_operations(list(), list() | nil, list(), map() | nil) :: list() def prepare_operations(events, json_rpc_named_arguments, json_rpc_named_arguments_l1, block_to_timestamp \\ nil) do - deposit_events = Enum.filter(events, fn event -> event.first_topic == @bridge_event end) + is_l1 = (json_rpc_named_arguments == json_rpc_named_arguments_l1) + + bridge_events = Enum.filter(events, fn event -> event.first_topic == @bridge_event end) block_to_timestamp = if is_nil(block_to_timestamp) do - blocks_to_timestamps(deposit_events, json_rpc_named_arguments) + blocks_to_timestamps(bridge_events, json_rpc_named_arguments) else block_to_timestamp end - token_address_to_id = token_addresses_to_ids(deposit_events, json_rpc_named_arguments_l1) + token_address_to_id = token_addresses_to_ids(bridge_events, json_rpc_named_arguments_l1) Enum.map(events, fn event -> {type, index, l1_token_id, l2_token_address, amount, block_number, block_timestamp} = @@ -174,12 +176,26 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do block_number = quantity_to_integer(event.block_number) block_timestamp = Map.get(block_to_timestamp, block_number) - {:deposit, deposit_count, l1_token_id, l2_token_address, amount, block_number, block_timestamp} + type = + if is_l1 do + :deposit + else + :withdrawal + end + + {type, deposit_count, l1_token_id, l2_token_address, amount, block_number, block_timestamp} else [index, _origin_network, _origin_address, _destination_address, amount] = decode_data(event.data, @claim_event_params) - {:withdrawal, index, nil, nil, amount, nil, nil} + type = + if is_l1 do + :withdrawal + else + :deposit + end + + {type, index, nil, nil, amount, nil, nil} end result = %{ @@ -189,7 +205,7 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do } transaction_hash_field = - if json_rpc_named_arguments == json_rpc_named_arguments_l1 do + if is_l1 do :l1_transaction_hash else :l2_transaction_hash @@ -204,8 +220,8 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do end) end - defp blocks_to_timestamps(deposit_events, json_rpc_named_arguments) do - deposit_events + defp blocks_to_timestamps(events, json_rpc_named_arguments) do + events |> get_blocks_by_events(json_rpc_named_arguments, 100_000_000) |> Enum.reduce(%{}, fn block, acc -> block_number = quantity_to_integer(Map.get(block, "number")) @@ -233,9 +249,9 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do end end - defp token_addresses_to_ids(deposit_events, json_rpc_named_arguments) do + defp token_addresses_to_ids(events, json_rpc_named_arguments) do token_data = - deposit_events + events |> Enum.reduce(%MapSet{}, fn event, acc -> [ leaf_type, diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex index b497eaa95408..d748ad67d79a 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex @@ -64,9 +64,11 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do {last_l1_block_number, last_l1_transaction_hash} = get_last_l1_item(), json_rpc_named_arguments = json_rpc_named_arguments(rpc), {:ok, block_check_interval, safe_block} <- get_block_check_interval(json_rpc_named_arguments), - {:start_block_valid, true} <- + {:start_block_valid, true, _, _} <- {:start_block_valid, - (start_block <= last_l1_block_number || last_l1_block_number == 0) && start_block <= safe_block}, + (start_block <= last_l1_block_number || last_l1_block_number == 0) && start_block <= safe_block, + last_l1_block_number, + safe_block}, {:ok, last_l1_tx} <- Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)} do Process.send(self(), :reorg_monitor, []) @@ -94,8 +96,10 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do Logger.error("PolygonZkEVMBridge contract address is invalid or not defined.") {:stop, :normal, %{}} - {:start_block_valid, false} -> + {:start_block_valid, false, last_l1_block_number, safe_block} -> Logger.error("Invalid L1 Start Block value. Please, check the value and zkevm_bridge table.") + Logger.error("last_l1_block_number = #{inspect(last_l1_block_number)}") + Logger.error("safe_block = #{inspect(safe_block)}") {:stop, :normal, %{}} {:error, error_data} -> From cb29b6d3412ceee61a28c5b981bb7faee8f0c21a Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Tue, 26 Dec 2023 14:25:08 +0300 Subject: [PATCH 431/607] Fix Explorer.Chain.Import.Runner.Zkevm.BridgeOperations --- .../import/runner/zkevm/bridge_operations.ex | 66 +++++++++++++++++-- .../20231010093238_add_bridge_tables.exs | 10 +-- 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex b/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex index 4b6dcca7ef7f..eefb9024b09e 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex @@ -86,13 +86,67 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeOperations do set: [ # Don't update `type` as it is part of the composite primary key and used for the conflict target # Don't update `index` as it is part of the composite primary key and used for the conflict target - l1_transaction_hash: fragment("EXCLUDED.l1_transaction_hash"), - l2_transaction_hash: fragment("EXCLUDED.l2_transaction_hash"), - l1_token_id: fragment("EXCLUDED.l1_token_id"), - l2_token_address: fragment("EXCLUDED.l2_token_address"), + l1_transaction_hash: fragment( + """ + CASE WHEN EXCLUDED.l1_transaction_hash IS NOT NULL THEN + EXCLUDED.l1_transaction_hash + ELSE + ? + END + """, + op.l1_transaction_hash + ), + l2_transaction_hash: fragment( + """ + CASE WHEN EXCLUDED.l2_transaction_hash IS NOT NULL THEN + EXCLUDED.l2_transaction_hash + ELSE + ? + END + """, + op.l2_transaction_hash + ), + l1_token_id: fragment( + """ + CASE WHEN EXCLUDED.l1_token_id IS NOT NULL THEN + EXCLUDED.l1_token_id + ELSE + ? + END + """, + op.l1_token_id + ), + l2_token_address: fragment( + """ + CASE WHEN EXCLUDED.l2_token_address IS NOT NULL THEN + EXCLUDED.l2_token_address + ELSE + ? + END + """, + op.l2_token_address + ), amount: fragment("EXCLUDED.amount"), - block_number: fragment("EXCLUDED.block_number"), - block_timestamp: fragment("EXCLUDED.block_timestamp"), + block_number: fragment( + """ + CASE WHEN EXCLUDED.block_number IS NOT NULL THEN + EXCLUDED.block_number + ELSE + ? + END + """, + op.block_number + ), + block_timestamp: fragment( + """ + CASE WHEN EXCLUDED.block_timestamp IS NOT NULL THEN + EXCLUDED.block_timestamp + ELSE + ? + END + """, + op.block_timestamp + ), inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", op.inserted_at), updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", op.updated_at) ] diff --git a/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs b/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs index fa32ae967776..2e9fb13ea60e 100644 --- a/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs +++ b/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs @@ -20,8 +20,8 @@ defmodule Explorer.Repo.PolygonZkevm.Migrations.AddBridgeTables do create table(:zkevm_bridge, primary_key: false) do add(:type, :zkevm_bridge_op_type, null: false, primary_key: true) add(:index, :integer, null: false, primary_key: true) - add(:l1_transaction_hash, :bytea, null: true, default: nil) - add(:l2_transaction_hash, :bytea, null: true, default: nil) + add(:l1_transaction_hash, :bytea, null: true) + add(:l2_transaction_hash, :bytea, null: true) add( :l1_token_id, @@ -29,10 +29,10 @@ defmodule Explorer.Repo.PolygonZkevm.Migrations.AddBridgeTables do null: true ) - add(:l2_token_address, :bytea, null: true, default: nil) + add(:l2_token_address, :bytea, null: true) add(:amount, :numeric, precision: 100, null: false) - add(:block_number, :bigint, null: true, default: nil) - add(:block_timestamp, :"timestamp without time zone", null: true, default: nil) + add(:block_number, :bigint, null: true) + add(:block_timestamp, :"timestamp without time zone", null: true) timestamps(null: false, type: :utc_datetime_usec) end end From a93a31294e4c6bc1886755413637667478dcfbb4 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Wed, 27 Dec 2023 15:48:24 +0300 Subject: [PATCH 432/607] Add API v2 for Polygon zkEVM bridge operations --- .../lib/block_scout_web/api_router.ex | 4 + .../lib/block_scout_web/chain.ex | 2 +- .../controllers/api/v2/zkevm_controller.ex | 68 +++++++++++++++ .../views/api/v2/zkevm_view.ex | 57 +++++++++++++ .../import/runner/zkevm/bridge_operations.ex | 66 ++------------- .../lib/explorer/chain/zkevm/reader.ex | 84 ++++++++++++++++++- .../lib/indexer/fetcher/zkevm/bridge.ex | 34 ++++---- .../lib/indexer/fetcher/zkevm/bridge_l1.ex | 3 +- 8 files changed, 234 insertions(+), 84 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index ac6f2d098dfe..1116688a64d7 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -316,6 +316,10 @@ defmodule BlockScoutWeb.ApiRouter do get("/batches", V2.ZkevmController, :batches) get("/batches/count", V2.ZkevmController, :batches_count) get("/batches/:batch_number", V2.ZkevmController, :batch) + get("/deposits", V2.ZkevmController, :deposits) + get("/deposits/count", V2.ZkevmController, :deposits_count) + get("/withdrawals", V2.ZkevmController, :withdrawals) + get("/withdrawals/count", V2.ZkevmController, :withdrawals_count) end end diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index c1b9743c94d0..d4d6f3f20f82 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -612,7 +612,7 @@ defmodule BlockScoutWeb.Chain do } end - defp paging_params(%Withdrawal{index: index}) do + defp paging_params(%{index: index}) do %{"index" => index} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/zkevm_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/zkevm_controller.ex index cd45dab110b7..961933de883e 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/zkevm_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/zkevm_controller.ex @@ -109,4 +109,72 @@ defmodule BlockScoutWeb.API.V2.ZkevmController do {:error, :not_found} -> 0 end end + + @doc """ + Function to handle GET requests to `/api/v2/zkevm/deposits` endpoint. + """ + @spec deposits(Plug.Conn.t(), map()) :: Plug.Conn.t() + def deposits(conn, params) do + {deposits, next_page} = + params + |> paging_options() + |> Keyword.put(:api?, true) + |> Reader.deposits() + |> split_list_by_page() + + next_page_params = next_page_params(next_page, deposits, params) + + conn + |> put_status(200) + |> render(:polygon_zkevm_bridge_items, %{ + items: deposits, + next_page_params: next_page_params + }) + end + + @doc """ + Function to handle GET requests to `/api/v2/zkevm/deposits/count` endpoint. + """ + @spec deposits_count(Plug.Conn.t(), map()) :: Plug.Conn.t() + def deposits_count(conn, _params) do + count = Reader.deposits_count(api?: true) + + conn + |> put_status(200) + |> render(:polygon_zkevm_bridge_items_count, %{count: count}) + end + + @doc """ + Function to handle GET requests to `/api/v2/zkevm/withdrawals` endpoint. + """ + @spec withdrawals(Plug.Conn.t(), map()) :: Plug.Conn.t() + def withdrawals(conn, params) do + {withdrawals, next_page} = + params + |> paging_options() + |> Keyword.put(:api?, true) + |> Reader.withdrawals() + |> split_list_by_page() + + next_page_params = next_page_params(next_page, withdrawals, params) + + conn + |> put_status(200) + |> render(:polygon_zkevm_bridge_items, %{ + items: withdrawals, + next_page_params: next_page_params + }) + end + + @doc """ + Function to handle GET requests to `/api/v2/zkevm/withdrawals/count` endpoint. + """ + @spec withdrawals_count(Plug.Conn.t(), map()) :: Plug.Conn.t() + def withdrawals_count(conn, _params) do + count = Reader.withdrawals_count(api?: true) + + conn + |> put_status(200) + |> render(:polygon_zkevm_bridge_items_count, %{count: count}) + end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/zkevm_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/zkevm_view.ex index a4b0eb2b0c50..e46af83bbca3 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/zkevm_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/zkevm_view.ex @@ -68,6 +68,56 @@ defmodule BlockScoutWeb.API.V2.ZkevmView do number end + @doc """ + Function to render GET requests to `/api/v2/zkevm/deposits` and `/api/v2/zkevm/withdrawals` endpoints. + """ + def render("polygon_zkevm_bridge_items.json", %{ + items: items, + next_page_params: next_page_params + }) do + env = Application.get_all_env(:indexer)[Indexer.Fetcher.Zkevm.BridgeL1] + + %{ + items: + Enum.map(items, fn item -> + l1_token = if is_nil(Map.get(item, :l1_token)), do: %{}, else: Map.get(item, :l1_token) + l2_token = if is_nil(Map.get(item, :l2_token)), do: %{}, else: Map.get(item, :l2_token) + + decimals = + cond do + not is_nil(Map.get(l1_token, :decimals)) -> Map.get(l1_token, :decimals) + not is_nil(Map.get(l2_token, :decimals)) -> Map.get(l2_token, :decimals) + true -> env[:native_decimals] + end + + symbol = + cond do + not is_nil(Map.get(l1_token, :symbol)) -> Map.get(l1_token, :symbol) + not is_nil(Map.get(l2_token, :symbol)) -> Map.get(l2_token, :symbol) + true -> env[:native_symbol] + end + + %{ + "block_number" => item.block_number, + "index" => item.index, + "l1_transaction_hash" => item.l1_transaction_hash, + "timestamp" => item.block_timestamp, + "l2_transaction_hash" => item.l2_transaction_hash, + "value" => fractional(Decimal.new(item.amount), Decimal.new(decimals)), + "symbol" => symbol + } + end), + next_page_params: next_page_params + } + end + + @doc """ + Function to render GET requests to `/api/v2/zkevm/deposits/count` and `/api/v2/zkevm/withdrawals/count` endpoints. + """ + def render("polygon_zkevm_bridge_items_count.json", %{count: count}) do + count + end + defp batch_status(batch) do sequence_id = Map.get(batch, :sequence_id) verify_id = Map.get(batch, :verify_id) @@ -79,6 +129,13 @@ defmodule BlockScoutWeb.API.V2.ZkevmView do end end + defp fractional(%Decimal{} = amount, %Decimal{} = decimals) do + amount.sign + |> Decimal.new(amount.coef, amount.exp - Decimal.to_integer(decimals)) + |> Decimal.normalize() + |> Decimal.to_string(:normal) + end + defp render_zkevm_batches(batches) do Enum.map(batches, fn batch -> sequence_tx_hash = diff --git a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex b/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex index eefb9024b09e..96dcbcfc4a57 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex @@ -86,67 +86,13 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeOperations do set: [ # Don't update `type` as it is part of the composite primary key and used for the conflict target # Don't update `index` as it is part of the composite primary key and used for the conflict target - l1_transaction_hash: fragment( - """ - CASE WHEN EXCLUDED.l1_transaction_hash IS NOT NULL THEN - EXCLUDED.l1_transaction_hash - ELSE - ? - END - """, - op.l1_transaction_hash - ), - l2_transaction_hash: fragment( - """ - CASE WHEN EXCLUDED.l2_transaction_hash IS NOT NULL THEN - EXCLUDED.l2_transaction_hash - ELSE - ? - END - """, - op.l2_transaction_hash - ), - l1_token_id: fragment( - """ - CASE WHEN EXCLUDED.l1_token_id IS NOT NULL THEN - EXCLUDED.l1_token_id - ELSE - ? - END - """, - op.l1_token_id - ), - l2_token_address: fragment( - """ - CASE WHEN EXCLUDED.l2_token_address IS NOT NULL THEN - EXCLUDED.l2_token_address - ELSE - ? - END - """, - op.l2_token_address - ), + l1_transaction_hash: fragment("COALESCE(EXCLUDED.l1_transaction_hash, ?)", op.l1_transaction_hash), + l2_transaction_hash: fragment("COALESCE(EXCLUDED.l2_transaction_hash, ?)", op.l2_transaction_hash), + l1_token_id: fragment("COALESCE(EXCLUDED.l1_token_id, ?)", op.l1_token_id), + l2_token_address: fragment("COALESCE(EXCLUDED.l2_token_address, ?)", op.l2_token_address), amount: fragment("EXCLUDED.amount"), - block_number: fragment( - """ - CASE WHEN EXCLUDED.block_number IS NOT NULL THEN - EXCLUDED.block_number - ELSE - ? - END - """, - op.block_number - ), - block_timestamp: fragment( - """ - CASE WHEN EXCLUDED.block_timestamp IS NOT NULL THEN - EXCLUDED.block_timestamp - ELSE - ? - END - """, - op.block_timestamp - ), + block_number: fragment("COALESCE(EXCLUDED.block_number, ?)", op.block_number), + block_timestamp: fragment("COALESCE(EXCLUDED.block_timestamp, ?)", op.block_timestamp), inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", op.inserted_at), updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", op.updated_at) ] diff --git a/apps/explorer/lib/explorer/chain/zkevm/reader.ex b/apps/explorer/lib/explorer/chain/zkevm/reader.ex index 49f69eaa0d46..e1057686c738 100644 --- a/apps/explorer/lib/explorer/chain/zkevm/reader.ex +++ b/apps/explorer/lib/explorer/chain/zkevm/reader.ex @@ -12,7 +12,7 @@ defmodule Explorer.Chain.Zkevm.Reader do import Explorer.Chain, only: [select_repo: 1] - alias Explorer.Chain.Zkevm.{BatchTransaction, LifecycleTransaction, TransactionBatch} + alias Explorer.Chain.Zkevm.{BatchTransaction, Bridge, LifecycleTransaction, TransactionBatch} alias Explorer.{Chain, PagingOptions, Repo} @doc """ @@ -141,9 +141,91 @@ defmodule Explorer.Chain.Zkevm.Reader do last_id + 1 end + @doc """ + Retrieves a list of Polygon zkEVM deposits (completed and unclaimed) + sorted in descending order of the index. + """ + @spec deposits(list()) :: list() + def deposits(options \\ []) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + + base_query = + from( + b in Bridge, + left_join: t1 in assoc(b, :l1_token), + left_join: t2 in assoc(b, :l2_token), + where: b.type == :deposit and not is_nil(b.l1_transaction_hash), + preload: [l1_token: t1, l2_token: t2], + order_by: [desc: b.index] + ) + + base_query + |> page_deposits_or_withdrawals(paging_options) + |> limit(^paging_options.page_size) + |> select_repo(options).all() + end + + @doc """ + Returns a total number of Polygon zkEVM deposits (completed and unclaimed). + """ + @spec deposits_count(list()) :: term() | nil + def deposits_count(options \\ []) do + query = + from( + b in Bridge, + where: b.type == :deposit and not is_nil(b.l1_transaction_hash) + ) + + select_repo(options).aggregate(query, :count, timeout: :infinity) + end + + @doc """ + Retrieves a list of Polygon zkEVM withdrawals (completed and unclaimed) + sorted in descending order of the index. + """ + @spec withdrawals(list()) :: list() + def withdrawals(options \\ []) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + + base_query = + from( + b in Bridge, + left_join: t1 in assoc(b, :l1_token), + left_join: t2 in assoc(b, :l2_token), + where: b.type == :withdrawal and not is_nil(b.l2_transaction_hash), + preload: [l1_token: t1, l2_token: t2], + order_by: [desc: b.index] + ) + + base_query + |> page_deposits_or_withdrawals(paging_options) + |> limit(^paging_options.page_size) + |> select_repo(options).all() + end + + @doc """ + Returns a total number of Polygon zkEVM withdrawals (completed and unclaimed). + """ + @spec withdrawals_count(list()) :: term() | nil + def withdrawals_count(options \\ []) do + query = + from( + b in Bridge, + where: b.type == :withdrawal and not is_nil(b.l2_transaction_hash) + ) + + select_repo(options).aggregate(query, :count, timeout: :infinity) + end + defp page_batches(query, %PagingOptions{key: nil}), do: query defp page_batches(query, %PagingOptions{key: {number}}) do from(tb in query, where: tb.number < ^number) end + + defp page_deposits_or_withdrawals(query, %PagingOptions{key: nil}), do: query + + defp page_deposits_or_withdrawals(query, %PagingOptions{key: {index}}) do + from(b in query, where: b.index < ^index) + end end diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex index 205e739bfa33..5695fb8cf0dd 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex @@ -142,8 +142,6 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do @spec prepare_operations(list(), list() | nil, list(), map() | nil) :: list() def prepare_operations(events, json_rpc_named_arguments, json_rpc_named_arguments_l1, block_to_timestamp \\ nil) do - is_l1 = (json_rpc_named_arguments == json_rpc_named_arguments_l1) - bridge_events = Enum.filter(events, fn event -> event.first_topic == @bridge_event end) block_to_timestamp = @@ -156,7 +154,7 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do token_address_to_id = token_addresses_to_ids(bridge_events, json_rpc_named_arguments_l1) Enum.map(events, fn event -> - {type, index, l1_token_id, l2_token_address, amount, block_number, block_timestamp} = + {index, l1_token_id, l2_token_address, amount, block_number, block_timestamp} = if event.first_topic == @bridge_event do [ leaf_type, @@ -176,30 +174,18 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do block_number = quantity_to_integer(event.block_number) block_timestamp = Map.get(block_to_timestamp, block_number) - type = - if is_l1 do - :deposit - else - :withdrawal - end - - {type, deposit_count, l1_token_id, l2_token_address, amount, block_number, block_timestamp} + {deposit_count, l1_token_id, l2_token_address, amount, block_number, block_timestamp} else [index, _origin_network, _origin_address, _destination_address, amount] = decode_data(event.data, @claim_event_params) - type = - if is_l1 do - :withdrawal - else - :deposit - end - - {type, index, nil, nil, amount, nil, nil} + {index, nil, nil, amount, nil, nil} end + is_l1 = json_rpc_named_arguments == json_rpc_named_arguments_l1 + result = %{ - type: type, + type: operation_type(event.first_topic, is_l1), index: index, amount: amount } @@ -249,6 +235,14 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do end end + defp operation_type(first_topic, is_l1) do + if first_topic == @bridge_event do + if is_l1, do: :deposit, else: :withdrawal + else + if is_l1, do: :withdrawal, else: :deposit + end + end + defp token_addresses_to_ids(events, json_rpc_named_arguments) do token_data = events diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex index d748ad67d79a..40ac67d689f6 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex @@ -67,8 +67,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do {:start_block_valid, true, _, _} <- {:start_block_valid, (start_block <= last_l1_block_number || last_l1_block_number == 0) && start_block <= safe_block, - last_l1_block_number, - safe_block}, + last_l1_block_number, safe_block}, {:ok, last_l1_tx} <- Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)} do Process.send(self(), :reorg_monitor, []) From 71ef381240cd8235f9d0b14b8aeb384a00c21756 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Thu, 4 Jan 2024 10:23:59 +0300 Subject: [PATCH 433/607] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2681cc373a66..d24651e14173 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,6 +117,7 @@ - [#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 +- [#9098](https://github.com/blockscout/blockscout/pull/9098) - Polygon zkEVM Bridge indexer and API v2 extension ### Fixes From 98099d857a50661caa0c9ee4556075f5d55d5756 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Thu, 4 Jan 2024 10:28:45 +0300 Subject: [PATCH 434/607] Update changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d24651e14173..a01f0e38d9dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -371,7 +371,6 @@ - [#8543](https://github.com/blockscout/blockscout/pull/8543) - Fix polygon tracer - [#8386](https://github.com/blockscout/blockscout/pull/8386) - Add `owner_address_hash` to the `token_instances` - [#8530](https://github.com/blockscout/blockscout/pull/8530) - Add `block_type` to search results -- [#7584](https://github.com/blockscout/blockscout/pull/7584) - Add Polygon zkEVM batches fetcher - [#8180](https://github.com/blockscout/blockscout/pull/8180) - Deposits and Withdrawals for Polygon Edge - [#7996](https://github.com/blockscout/blockscout/pull/7996) - Add CoinBalance fetcher init query limit - [#8658](https://github.com/blockscout/blockscout/pull/8658) - Remove block consensus on import fail From 5931ec88aa9e461f2d4f0397b428ec16ef6854c1 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Thu, 4 Jan 2024 10:32:53 +0300 Subject: [PATCH 435/607] Add envs to common-blockscout.env --- docker-compose/envs/common-blockscout.env | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index a1e813ce7436..1f78a8dde353 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -172,6 +172,13 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false # INDEXER_POLYGON_ZKEVM_BATCHES_ENABLED= # INDEXER_POLYGON_ZKEVM_BATCHES_CHUNK_SIZE= # INDEXER_POLYGON_ZKEVM_BATCHES_RECHECK_INTERVAL= +# INDEXER_POLYGON_ZKEVM_L1_RPC= +# INDEXER_POLYGON_ZKEVM_L1_BRIDGE_START_BLOCK= +# INDEXER_POLYGON_ZKEVM_L1_BRIDGE_CONTRACT= +# INDEXER_POLYGON_ZKEVM_L1_BRIDGE_NATIVE_SYMBOL= +# INDEXER_POLYGON_ZKEVM_L1_BRIDGE_NATIVE_DECIMALS= +# INDEXER_POLYGON_ZKEVM_L2_BRIDGE_START_BLOCK= +# INDEXER_POLYGON_ZKEVM_L2_BRIDGE_CONTRACT= # INDEXER_REALTIME_FETCHER_MAX_GAP= # INDEXER_FETCHER_INIT_QUERY_LIMIT= # INDEXER_TOKEN_BALANCES_FETCHER_INIT_QUERY_LIMIT= From d6a34dabf984258b77a734a5a277862c9fdba897 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Thu, 4 Jan 2024 10:39:53 +0300 Subject: [PATCH 436/607] Rename `uninserted` to `not inserted` --- apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex index 5695fb8cf0dd..1b7be9dac121 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex @@ -291,17 +291,17 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do tokens_inserted = Map.get(inserts, :insert_zkevm_bridge_l1_tokens, []) - # we need to query uninserted tokens from DB separately as they + # we need to query not inserted tokens from DB separately as they # could be inserted by another module at the same time (a race condition). # this is an unlikely case but we handle it here as well - tokens_uninserted = + tokens_not_inserted = tokens_to_insert |> Enum.reject(fn token -> Enum.any?(tokens_inserted, fn inserted -> token.address == Hash.to_string(inserted.address) end) end) |> Enum.map(& &1.address) - tokens_inserted_outside = token_addresses_to_ids_from_db(tokens_uninserted) + tokens_inserted_outside = token_addresses_to_ids_from_db(tokens_not_inserted) tokens_inserted |> Enum.reduce(%{}, fn t, acc -> Map.put(acc, Hash.to_string(t.address), t.id) end) From 85ff5452d5fbf4fb30b83c1a0c8b8f50319b2dec Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Fri, 12 Jan 2024 10:38:55 +0300 Subject: [PATCH 437/607] Small refactoring --- .../lib/explorer/chain/zkevm/reader.ex | 40 +++++++++++++++++++ .../lib/indexer/fetcher/zkevm/bridge_l1.ex | 18 +-------- .../lib/indexer/fetcher/zkevm/bridge_l2.ex | 18 +-------- 3 files changed, 44 insertions(+), 32 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/zkevm/reader.ex b/apps/explorer/lib/explorer/chain/zkevm/reader.ex index e1057686c738..097f9498ce13 100644 --- a/apps/explorer/lib/explorer/chain/zkevm/reader.ex +++ b/apps/explorer/lib/explorer/chain/zkevm/reader.ex @@ -87,6 +87,46 @@ defmodule Explorer.Chain.Zkevm.Reader do select_repo(options).all(query) end + @doc """ + Gets last known L1 item (deposit) from zkevm_bridge table. + Returns block number and L1 transaction hash bound to that deposit. + If not found, returns zero block number and nil as the transaction hash. + """ + @spec last_l1_item() :: {non_neg_integer(), binary() | nil} + def last_l1_item do + query = + from(b in Bridge, + select: {b.block_number, b.l1_transaction_hash}, + where: b.type == :deposit and not is_nil(b.block_number), + order_by: [desc: b.index], + limit: 1 + ) + + query + |> Repo.one() + |> Kernel.||({0, nil}) + end + + @doc """ + Gets last known L2 item (withdrawal) from zkevm_bridge table. + Returns block number and L2 transaction hash bound to that withdrawal. + If not found, returns zero block number and nil as the transaction hash. + """ + @spec last_l2_item() :: {non_neg_integer(), binary() | nil} + def last_l2_item do + query = + from(b in Bridge, + select: {b.block_number, b.l2_transaction_hash}, + where: b.type == :withdrawal and not is_nil(b.block_number), + order_by: [desc: b.index], + limit: 1 + ) + + query + |> Repo.one() + |> Kernel.||({0, nil}) + end + @doc """ Gets the number of the latest batch with defined verify_id from `zkevm_transaction_batches` table. Returns 0 if not found. diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex index 40ac67d689f6..8eda5f3932f4 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex @@ -14,7 +14,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do import Indexer.Fetcher.Zkevm.Bridge, only: [get_logs_all: 3, import_operations: 1, json_rpc_named_arguments: 1, prepare_operations: 3] - alias Explorer.Chain.Zkevm.Bridge + alias Explorer.Chain.Zkevm.{Bridge, Reader} alias Explorer.Repo alias Indexer.{BoundQueue, Helper} @@ -61,7 +61,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do start_block = parse_integer(env[:start_block]), false <- is_nil(start_block), true <- start_block > 0, - {last_l1_block_number, last_l1_transaction_hash} = get_last_l1_item(), + {last_l1_block_number, last_l1_transaction_hash} = Reader.last_l1_item(), json_rpc_named_arguments = json_rpc_named_arguments(rpc), {:ok, block_check_interval, safe_block} <- get_block_check_interval(json_rpc_named_arguments), {:start_block_valid, true, _, _} <- @@ -234,20 +234,6 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do end end - defp get_last_l1_item do - query = - from(b in Bridge, - select: {b.block_number, b.l1_transaction_hash}, - where: b.type == :deposit and not is_nil(b.block_number), - order_by: [desc: b.index], - limit: 1 - ) - - query - |> Repo.one() - |> Kernel.||({0, nil}) - end - defp get_safe_block(json_rpc_named_arguments) do case Helper.get_block_number_by_tag("safe", json_rpc_named_arguments) do {:ok, safe_block} -> diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex index d2be5dff6f25..02c411b9c1c7 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex @@ -14,7 +14,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL2 do import Indexer.Fetcher.Zkevm.Bridge, only: [get_logs_all: 3, import_operations: 1, json_rpc_named_arguments: 1, prepare_operations: 3] - alias Explorer.Chain.Zkevm.Bridge + alias Explorer.Chain.Zkevm.{Bridge, Reader} alias Explorer.Repo alias Indexer.Helper @@ -62,7 +62,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL2 do start_block = parse_integer(env[:start_block]), false <- is_nil(start_block), true <- start_block > 0, - {last_l2_block_number, last_l2_transaction_hash} = get_last_l2_item(), + {last_l2_block_number, last_l2_transaction_hash} = Reader.last_l2_item(), {:ok, latest_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000), {:start_block_valid, true} <- {:start_block_valid, @@ -173,18 +173,4 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL2 do ) end end - - defp get_last_l2_item do - query = - from(b in Bridge, - select: {b.block_number, b.l2_transaction_hash}, - where: b.type == :withdrawal and not is_nil(b.block_number), - order_by: [desc: b.index], - limit: 1 - ) - - query - |> Repo.one() - |> Kernel.||({0, nil}) - end end From 98c31ee462d90da314d23b44f0cc3420ee9b6b3e Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Fri, 12 Jan 2024 10:49:47 +0300 Subject: [PATCH 438/607] Remove redundant function --- apps/indexer/lib/indexer/helper.ex | 119 ----------------------------- 1 file changed, 119 deletions(-) diff --git a/apps/indexer/lib/indexer/helper.ex b/apps/indexer/lib/indexer/helper.ex index e771f0d66000..be0a2fba035c 100644 --- a/apps/indexer/lib/indexer/helper.ex +++ b/apps/indexer/lib/indexer/helper.ex @@ -44,67 +44,6 @@ defmodule Indexer.Helper do end end - @spec get_block_number_by_tag(binary(), list(), integer()) :: {:ok, non_neg_integer()} | {:error, atom()} - def get_block_number_by_tag(tag, json_rpc_named_arguments, retries \\ 3) do - error_message = &"Cannot fetch #{tag} block number. Error: #{inspect(&1)}" - repeated_call(&fetch_block_number_by_tag/2, [tag, json_rpc_named_arguments], error_message, retries) - end - - def get_transaction_by_hash(hash, json_rpc_named_arguments, retries_left \\ 3) - - def get_transaction_by_hash(hash, _json_rpc_named_arguments, _retries_left) when is_nil(hash), do: {:ok, nil} - - def get_transaction_by_hash(hash, json_rpc_named_arguments, retries) do - req = - request(%{ - id: 0, - method: "eth_getTransactionByHash", - params: [hash] - }) - - error_message = &"eth_getTransactionByHash failed. Error: #{inspect(&1)}" - - repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) - end - - def log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, items_count, layer) do - is_start = is_nil(items_count) - - {type, found} = - if is_start do - {"Start", ""} - else - {"Finish", " Found #{items_count}."} - end - - target_range = - if chunk_start != start_block or chunk_end != end_block do - progress = - if is_start do - "" - else - percentage = - (chunk_end - start_block + 1) - |> Decimal.div(end_block - start_block + 1) - |> Decimal.mult(100) - |> Decimal.round(2) - |> Decimal.to_string() - - " Progress: #{percentage}%" - end - - " Target range: #{start_block}..#{end_block}.#{progress}" - else - "" - end - - if chunk_start == chunk_end do - Logger.info("#{type} handling #{layer} block ##{chunk_start}.#{found}#{target_range}") - else - Logger.info("#{type} handling #{layer} block range #{chunk_start}..#{chunk_end}.#{found}#{target_range}") - end - end - @doc """ Fetches block number by its tag (e.g. `latest` or `safe`) using RPC request. Performs a specified number of retries (up to) if the first attempt returns error. @@ -256,62 +195,4 @@ defmodule Indexer.Helper do Hash.to_string(topic) end end - - @doc """ - Converts a log topic from Hash.Full representation to string one. - """ - @spec log_topic_to_string(any()) :: binary() | nil - def log_topic_to_string(topic) do - if is_binary(topic) or is_nil(topic) do - topic - else - Hash.to_string(topic) - end - end - - def repeated_call(func, args, error_message, retries_left) do - case apply(func, args) do - {:ok, _} = res -> - res - - {:error, message} = err -> - retries_left = retries_left - 1 - - if retries_left <= 0 do - Logger.error(error_message.(message)) - err - else - Logger.error("#{error_message.(message)} Retrying...") - :timer.sleep(3000) - repeated_call(func, args, error_message, retries_left) - end - end - end - - def get_block_timestamp_by_number(number, json_rpc_named_arguments, retries \\ 3) do - func = &get_block_timestamp_by_number_inner/2 - args = [number, json_rpc_named_arguments] - error_message = &"Cannot fetch block ##{number} or its timestamp. Error: #{inspect(&1)}" - repeated_call(func, args, error_message, retries) - end - - defp get_block_timestamp_by_number_inner(number, json_rpc_named_arguments) do - result = - %{id: 0, number: number} - |> ByNumber.request(false) - |> json_rpc(json_rpc_named_arguments) - - with {:ok, block} <- result, - false <- is_nil(block), - timestamp <- Map.get(block, "timestamp"), - false <- is_nil(timestamp) do - {:ok, quantity_to_integer(timestamp)} - else - {:error, message} -> - {:error, message} - - true -> - {:error, "RPC returned nil."} - end - end end From 7e6b81f9cc76dce67adddf2d3d60eff043809855 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:46:04 +0300 Subject: [PATCH 439/607] Fixes after rebase --- apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex | 3 ++- apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex | 2 +- apps/indexer/lib/indexer/transform/zkevm/bridge.ex | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex index 8eda5f3932f4..419bf6fb152f 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex @@ -57,7 +57,8 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do with {:start_block_undefined, false} <- {:start_block_undefined, is_nil(env[:start_block])}, rpc = env[:rpc], {:rpc_undefined, false} <- {:rpc_undefined, is_nil(rpc)}, - {:bridge_contract_address_is_valid, true} <- {:bridge_contract_address_is_valid, Helper.address_correct?(env[:bridge_contract])}, + {:bridge_contract_address_is_valid, true} <- + {:bridge_contract_address_is_valid, Helper.address_correct?(env[:bridge_contract])}, start_block = parse_integer(env[:start_block]), false <- is_nil(start_block), true <- start_block > 0, diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex index 02c411b9c1c7..aa1b55018cd9 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex @@ -58,7 +58,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL2 do rpc_l1 = Application.get_all_env(:indexer)[Indexer.Fetcher.Zkevm.BridgeL1][:rpc], {:rpc_l1_undefined, false} <- {:rpc_l1_undefined, is_nil(rpc_l1)}, {:bridge_contract_address_is_valid, true} <- - {:bridge_contract_address_is_valid, Helper.is_address_correct?(env[:bridge_contract])}, + {:bridge_contract_address_is_valid, Helper.address_correct?(env[:bridge_contract])}, start_block = parse_integer(env[:start_block]), false <- is_nil(start_block), true <- start_block > 0, diff --git a/apps/indexer/lib/indexer/transform/zkevm/bridge.ex b/apps/indexer/lib/indexer/transform/zkevm/bridge.ex index 77def32bdd8c..ae9f0f58e7ba 100644 --- a/apps/indexer/lib/indexer/transform/zkevm/bridge.ex +++ b/apps/indexer/lib/indexer/transform/zkevm/bridge.ex @@ -26,7 +26,7 @@ defmodule Indexer.Transform.Zkevm.Bridge do {:rpc_l1_undefined, false} <- {:rpc_l1_undefined, is_nil(rpc_l1)}, bridge_contract = Application.get_env(:indexer, BridgeL2)[:bridge_contract], {:bridge_contract_address_is_valid, true} <- - {:bridge_contract_address_is_valid, Helper.is_address_correct?(bridge_contract)} do + {:bridge_contract_address_is_valid, Helper.address_correct?(bridge_contract)} do bridge_contract = String.downcase(bridge_contract) block_numbers = Enum.map(blocks, fn block -> block.number end) From 811f69e65b19b7bf5a242cac20aa97e0cdb332d7 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Tue, 23 Jan 2024 10:29:56 +0300 Subject: [PATCH 440/607] Fixes for Chain.import related to CHAIN_TYPE --- .../lib/indexer/fetcher/zkevm/bridge.ex | 27 +++++++------------ .../fetcher/zkevm/transaction_batch.ex | 21 +++++---------- 2 files changed, 17 insertions(+), 31 deletions(-) diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex index 1b7be9dac121..ccdf81a8467d 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex @@ -104,24 +104,17 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do @spec import_operations(list()) :: no_return() def import_operations(operations) do - # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise - import_options = - if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do - addresses = - Addresses.extract_addresses(%{ - zkevm_bridge_operations: operations - }) - - %{ - addresses: %{params: addresses, on_conflict: :nothing}, - zkevm_bridge_operations: %{params: operations}, - timeout: :infinity - } - else - %{} - end + addresses = + Addresses.extract_addresses(%{ + zkevm_bridge_operations: operations + }) - {:ok, _} = Chain.import(import_options) + {:ok, _} = + Chain.import(%{ + addresses: %{params: addresses, on_conflict: :nothing}, + zkevm_bridge_operations: %{params: operations}, + timeout: :infinity + }) end @spec json_rpc_named_arguments(binary()) :: list() diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/transaction_batch.ex b/apps/indexer/lib/indexer/fetcher/zkevm/transaction_batch.ex index 25220dd7dbb0..40115826da8a 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/transaction_batch.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/transaction_batch.ex @@ -249,20 +249,13 @@ defmodule Indexer.Fetcher.Zkevm.TransactionBatch do {[batch | batches], l2_txs ++ l2_txs_append, l1_txs, next_id, hash_to_id} end) - # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise - import_options = - if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do - %{ - zkevm_lifecycle_transactions: %{params: l1_txs_to_import}, - zkevm_transaction_batches: %{params: batches_to_import}, - zkevm_batch_transactions: %{params: l2_txs_to_import}, - timeout: :infinity - } - else - %{} - end - - {:ok, _} = Chain.import(import_options) + {:ok, _} = + Chain.import(%{ + zkevm_lifecycle_transactions: %{params: l1_txs_to_import}, + zkevm_transaction_batches: %{params: batches_to_import}, + zkevm_batch_transactions: %{params: l2_txs_to_import}, + timeout: :infinity + }) confirmed_batches = Enum.filter(batches_to_import, fn batch -> not is_nil(batch.sequence_id) and batch.sequence_id > 0 end) From ae812aa63399abd369dfd0e8e6f4f34220c7b7a0 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:54:07 +0300 Subject: [PATCH 441/607] Small refactoring --- .../lib/indexer/fetcher/shibarium/l1.ex | 23 +------------ .../lib/indexer/fetcher/zkevm/bridge.ex | 24 ++------------ apps/indexer/lib/indexer/helper.ex | 32 +++++++++++++++++++ config/runtime.exs | 3 +- 4 files changed, 37 insertions(+), 45 deletions(-) diff --git a/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex b/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex index c34b8a5acdde..906acd268902 100644 --- a/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex +++ b/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex @@ -23,8 +23,6 @@ defmodule Indexer.Fetcher.Shibarium.L1 do import Indexer.Fetcher.Shibarium.Helper, only: [calc_operation_hash: 5, prepare_insert_items: 2, recalculate_cached_count: 0] - alias EthereumJSONRPC.Block.ByNumber - alias EthereumJSONRPC.Blocks alias Explorer.Chain.Shibarium.Bridge alias Explorer.{Chain, Repo} alias Indexer.{BoundQueue, Helper} @@ -348,25 +346,6 @@ defmodule Indexer.Fetcher.Shibarium.L1 do end end - defp get_blocks_by_events(events, json_rpc_named_arguments, retries) do - request = - events - |> Enum.reduce(%{}, fn event, acc -> - Map.put(acc, event["blockNumber"], 0) - end) - |> Stream.map(fn {block_number, _} -> %{number: block_number} end) - |> Stream.with_index() - |> Enum.into(%{}, fn {params, id} -> {id, params} end) - |> Blocks.requests(&ByNumber.request(&1, false, false)) - - error_message = &"Cannot fetch blocks with batch request. Error: #{inspect(&1)}. Request: #{inspect(request)}" - - case Helper.repeated_call(&json_rpc/2, [request, json_rpc_named_arguments], error_message, retries) do - {:ok, results} -> Enum.map(results, fn %{result: result} -> result end) - {:error, _} -> [] - end - end - defp get_last_l1_item do query = from(sb in Bridge, @@ -581,7 +560,7 @@ defmodule Indexer.Fetcher.Shibarium.L1 do timestamps = events |> filter_deposit_events() - |> get_blocks_by_events(json_rpc_named_arguments, 100_000_000) + |> Helper.get_blocks_by_events(json_rpc_named_arguments, 100_000_000) |> Enum.reduce(%{}, fn block, acc -> block_number = quantity_to_integer(Map.get(block, "number")) {:ok, timestamp} = DateTime.from_unix(quantity_to_integer(Map.get(block, "timestamp"))) diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex index ccdf81a8467d..580526b1111b 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex @@ -19,8 +19,7 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do import Explorer.Helper, only: [decode_data: 2] - alias EthereumJSONRPC.Block.ByNumber - alias EthereumJSONRPC.{Blocks, Logs} + alias EthereumJSONRPC.Logs alias Explorer.Chain.Hash alias Explorer.Chain.Zkevm.BridgeL1Token alias Explorer.{Chain, Repo} @@ -201,7 +200,7 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do defp blocks_to_timestamps(events, json_rpc_named_arguments) do events - |> get_blocks_by_events(json_rpc_named_arguments, 100_000_000) + |> Helper.get_blocks_by_events(json_rpc_named_arguments, 100_000_000) |> Enum.reduce(%{}, fn block, acc -> block_number = quantity_to_integer(Map.get(block, "number")) {:ok, timestamp} = DateTime.from_unix(quantity_to_integer(Map.get(block, "timestamp"))) @@ -209,25 +208,6 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do end) end - defp get_blocks_by_events(events, json_rpc_named_arguments, retries) do - request = - events - |> Enum.reduce(%{}, fn event, acc -> - Map.put(acc, event.block_number, 0) - end) - |> Stream.map(fn {block_number, _} -> %{number: block_number} end) - |> Stream.with_index() - |> Enum.into(%{}, fn {params, id} -> {id, params} end) - |> Blocks.requests(&ByNumber.request(&1, false, false)) - - error_message = &"Cannot fetch blocks with batch request. Error: #{inspect(&1)}. Request: #{inspect(request)}" - - case Helper.repeated_call(&json_rpc/2, [request, json_rpc_named_arguments], error_message, retries) do - {:ok, results} -> Enum.map(results, fn %{result: result} -> result end) - {:error, _} -> [] - end - end - defp operation_type(first_topic, is_l1) do if first_topic == @bridge_event do if is_l1, do: :deposit, else: :withdrawal diff --git a/apps/indexer/lib/indexer/helper.ex b/apps/indexer/lib/indexer/helper.ex index be0a2fba035c..07ee927c6b72 100644 --- a/apps/indexer/lib/indexer/helper.ex +++ b/apps/indexer/lib/indexer/helper.ex @@ -14,6 +14,7 @@ defmodule Indexer.Helper do ] alias EthereumJSONRPC.Block.ByNumber + alias EthereumJSONRPC.Blocks alias Explorer.Chain.Hash @spec address_correct?(binary()) :: boolean() @@ -151,6 +152,37 @@ defmodule Indexer.Helper do end end + @doc """ + Fetches blocks info from the given list of events (logs). + Performs a specified number of retries (up to) if the first attempt returns error. + """ + @spec get_blocks_by_events(list(), list(), non_neg_integer()) :: list() + def get_blocks_by_events(events, json_rpc_named_arguments, retries) do + request = + events + |> Enum.reduce(%{}, fn event, acc -> + block_number = + if is_map(event) do + event.block_number + else + event["blockNumber"] + end + + Map.put(acc, block_number, 0) + end) + |> Stream.map(fn {block_number, _} -> %{number: block_number} end) + |> Stream.with_index() + |> Enum.into(%{}, fn {params, id} -> {id, params} end) + |> Blocks.requests(&ByNumber.request(&1, false, false)) + + error_message = &"Cannot fetch blocks with batch request. Error: #{inspect(&1)}. Request: #{inspect(request)}" + + case repeated_call(&json_rpc/2, [request, json_rpc_named_arguments], error_message, retries) do + {:ok, results} -> Enum.map(results, fn %{result: result} -> result end) + {:error, _} -> [] + end + end + @doc """ Fetches block timestamp by its number using RPC request. Performs a specified number of retries (up to) if the first attempt returns error. diff --git a/config/runtime.exs b/config/runtime.exs index 05fc85397823..fc3cf7bad1ba 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -748,7 +748,8 @@ config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch, config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch.Supervisor, enabled: - ConfigHelper.chain_type() == "polygon_zkevm" && ConfigHelper.parse_bool_env_var("INDEXER_POLYGON_ZKEVM_BATCHES_ENABLED") + ConfigHelper.chain_type() == "polygon_zkevm" && + ConfigHelper.parse_bool_env_var("INDEXER_POLYGON_ZKEVM_BATCHES_ENABLED") Code.require_file("#{config_env()}.exs", "config/runtime") From a6d2a8018737a39aefaa45a56e2134951ba26694 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:53:55 +0300 Subject: [PATCH 442/607] Move L1 RPC requests from realtime block handler to a separate GenServer --- .../import/runner/zkevm/bridge_operations.ex | 4 +- .../lib/explorer/chain/zkevm/bridge.ex | 2 + .../20231010093238_add_bridge_tables.exs | 3 + apps/indexer/lib/indexer/block/fetcher.ex | 27 +++-- .../lib/indexer/block/realtime/fetcher.ex | 2 + .../lib/indexer/fetcher/zkevm/bridge.ex | 107 +++++++++--------- .../indexer/fetcher/zkevm/bridge_l1_tokens.ex | 78 +++++++++++++ apps/indexer/lib/indexer/supervisor.ex | 1 + config/runtime.exs | 1 + 9 files changed, 161 insertions(+), 64 deletions(-) create mode 100644 apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1_tokens.ex diff --git a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex b/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex index 96dcbcfc4a57..c7aaef5cb0a9 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex @@ -89,6 +89,7 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeOperations do l1_transaction_hash: fragment("COALESCE(EXCLUDED.l1_transaction_hash, ?)", op.l1_transaction_hash), l2_transaction_hash: fragment("COALESCE(EXCLUDED.l2_transaction_hash, ?)", op.l2_transaction_hash), l1_token_id: fragment("COALESCE(EXCLUDED.l1_token_id, ?)", op.l1_token_id), + l1_token_address: fragment("COALESCE(EXCLUDED.l1_token_address, ?)", op.l1_token_address), l2_token_address: fragment("COALESCE(EXCLUDED.l2_token_address, ?)", op.l2_token_address), amount: fragment("EXCLUDED.amount"), block_number: fragment("COALESCE(EXCLUDED.block_number, ?)", op.block_number), @@ -99,10 +100,11 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeOperations do ], where: fragment( - "(EXCLUDED.l1_transaction_hash, EXCLUDED.l2_transaction_hash, EXCLUDED.l1_token_id, EXCLUDED.l2_token_address, EXCLUDED.amount, EXCLUDED.block_number, EXCLUDED.block_timestamp) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?)", + "(EXCLUDED.l1_transaction_hash, EXCLUDED.l2_transaction_hash, EXCLUDED.l1_token_id, EXCLUDED.l1_token_address, EXCLUDED.l2_token_address, EXCLUDED.amount, EXCLUDED.block_number, EXCLUDED.block_timestamp) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?)", op.l1_transaction_hash, op.l2_transaction_hash, op.l1_token_id, + op.l1_token_address, op.l2_token_address, op.amount, op.block_number, diff --git a/apps/explorer/lib/explorer/chain/zkevm/bridge.ex b/apps/explorer/lib/explorer/chain/zkevm/bridge.ex index 8a2bf7855979..7a725275cbd4 100644 --- a/apps/explorer/lib/explorer/chain/zkevm/bridge.ex +++ b/apps/explorer/lib/explorer/chain/zkevm/bridge.ex @@ -17,6 +17,7 @@ defmodule Explorer.Chain.Zkevm.Bridge do l2_transaction_hash: Hash.t() | nil, l1_token: %Ecto.Association.NotLoaded{} | BridgeL1Token.t() | nil, l1_token_id: non_neg_integer() | nil, + l1_token_address: Hash.Address.t() | nil, l2_token: %Ecto.Association.NotLoaded{} | Token.t() | nil, l2_token_address: Hash.Address.t() | nil, amount: Decimal.t(), @@ -31,6 +32,7 @@ defmodule Explorer.Chain.Zkevm.Bridge do field(:l1_transaction_hash, Hash.Full) field(:l2_transaction_hash, Hash.Full) belongs_to(:l1_token, BridgeL1Token, foreign_key: :l1_token_id, references: :id, type: :integer) + field(:l1_token_address, Hash.Address) belongs_to(:l2_token, Token, foreign_key: :l2_token_address, references: :contract_address_hash, type: Hash.Address) field(:amount, :decimal) field(:block_number, :integer) diff --git a/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs b/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs index 2e9fb13ea60e..1eca46f86d23 100644 --- a/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs +++ b/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs @@ -29,11 +29,14 @@ defmodule Explorer.Repo.PolygonZkevm.Migrations.AddBridgeTables do null: true ) + add(:l1_token_address, :bytea, null: true) add(:l2_token_address, :bytea, null: true) add(:amount, :numeric, precision: 100, null: false) add(:block_number, :bigint, null: true) add(:block_timestamp, :"timestamp without time zone", null: true) timestamps(null: false, type: :utc_datetime_usec) end + + create(index(:zkevm_bridge, :l1_token_address)) end end diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 02e83dad42a5..efde267a944c 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -17,6 +17,7 @@ defmodule Indexer.Block.Fetcher do alias Explorer.Chain.Cache.{Accounts, BlockNumber, Transactions, Uncles} alias Indexer.Block.Fetcher.Receipts alias Indexer.Fetcher.TokenInstance.Realtime, as: TokenInstanceRealtime + alias Indexer.Fetcher.Zkevm.BridgeL1Tokens, as: ZkevmBridgeL1Tokens alias Indexer.Fetcher.{ Beacon.Blob, @@ -326,6 +327,19 @@ defmodule Indexer.Block.Fetcher do def async_import_token_instances(_), do: :ok + def async_import_blobs(%{blocks: blocks}) do + timestamps = + blocks + |> Enum.filter(fn block -> block |> Map.get(:blob_gas_used, 0) > 0 end) + |> Enum.map(&Map.get(&1, :timestamp)) + + if !Enum.empty?(timestamps) do + Blob.async_fetch(timestamps) + end + end + + def async_import_blobs(_), do: :ok + def async_import_block_rewards([]), do: :ok def async_import_block_rewards(errors) when is_list(errors) do @@ -408,18 +422,11 @@ defmodule Indexer.Block.Fetcher do def async_import_replaced_transactions(_), do: :ok - def async_import_blobs(%{blocks: blocks}) do - timestamps = - blocks - |> Enum.filter(fn block -> block |> Map.get(:blob_gas_used, 0) > 0 end) - |> Enum.map(&Map.get(&1, :timestamp)) - - if !Enum.empty?(timestamps) do - Blob.async_fetch(timestamps) - end + def async_import_zkevm_bridge_l1_tokens(%{zkevm_bridge_operations: operations}) do + ZkevmBridgeL1Tokens.async_fetch(operations) end - def async_import_blobs(_), do: :ok + def async_import_zkevm_bridge_l1_tokens(_), do: :ok defp block_reward_errors_to_block_numbers(block_reward_errors) when is_list(block_reward_errors) do Enum.map(block_reward_errors, &block_reward_error_to_block_number/1) diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index 4896dab7628c..13a44e75bbaf 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -22,6 +22,7 @@ defmodule Indexer.Block.Realtime.Fetcher do async_import_token_balances: 1, async_import_token_instances: 1, async_import_uncles: 1, + async_import_zkevm_bridge_l1_tokens: 1, fetch_and_import_range: 2 ] @@ -451,6 +452,7 @@ defmodule Indexer.Block.Realtime.Fetcher do async_import_uncles(imported) async_import_replaced_transactions(imported) async_import_blobs(imported) + async_import_zkevm_bridge_l1_tokens(imported) end defp balances( diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex index 580526b1111b..625f67646b36 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex @@ -134,44 +134,54 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do @spec prepare_operations(list(), list() | nil, list(), map() | nil) :: list() def prepare_operations(events, json_rpc_named_arguments, json_rpc_named_arguments_l1, block_to_timestamp \\ nil) do - bridge_events = Enum.filter(events, fn event -> event.first_topic == @bridge_event end) - - block_to_timestamp = + {block_to_timestamp, token_address_to_id} = if is_nil(block_to_timestamp) do - blocks_to_timestamps(bridge_events, json_rpc_named_arguments) + bridge_events = Enum.filter(events, fn event -> event.first_topic == @bridge_event end) + + l1_token_addresses = + bridge_events + |> Enum.reduce(%MapSet{}, fn event, acc -> + case bridge_event_parse(event) do + {{nil, _}, _, _} -> acc + {{token_address, nil}, _, _} -> MapSet.put(acc, token_address) + end + end) + |> MapSet.to_list() + + { + blocks_to_timestamps(bridge_events, json_rpc_named_arguments), + token_addresses_to_ids(l1_token_addresses, json_rpc_named_arguments_l1) + } else - block_to_timestamp + # this is called in realtime + {block_to_timestamp, %{}} end - token_address_to_id = token_addresses_to_ids(bridge_events, json_rpc_named_arguments_l1) - Enum.map(events, fn event -> - {index, l1_token_id, l2_token_address, amount, block_number, block_timestamp} = + {index, l1_token_id, l1_token_address, l2_token_address, amount, block_number, block_timestamp} = if event.first_topic == @bridge_event do - [ - leaf_type, - origin_network, - origin_address, - _destination_network, - _destination_address, + { + {l1_token_address, l2_token_address}, amount, - _metadata, deposit_count - ] = decode_data(event.data, @bridge_event_params) - - {l1_token_address, l2_token_address} = - token_address_by_origin_address(origin_address, origin_network, leaf_type) + } = bridge_event_parse(event) l1_token_id = Map.get(token_address_to_id, l1_token_address) block_number = quantity_to_integer(event.block_number) block_timestamp = Map.get(block_to_timestamp, block_number) - {deposit_count, l1_token_id, l2_token_address, amount, block_number, block_timestamp} + # credo:disable-for-lines:2 Credo.Check.Refactor.Nesting + l1_token_address = + if is_nil(l1_token_id) do + l1_token_address + end + + {deposit_count, l1_token_id, l1_token_address, l2_token_address, amount, block_number, block_timestamp} else [index, _origin_network, _origin_address, _destination_address, amount] = decode_data(event.data, @claim_event_params) - {index, nil, nil, amount, nil, nil} + {index, nil, nil, nil, amount, nil, nil} end is_l1 = json_rpc_named_arguments == json_rpc_named_arguments_l1 @@ -192,6 +202,7 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do result |> extend_result(transaction_hash_field, event.transaction_hash) |> extend_result(:l1_token_id, l1_token_id) + |> extend_result(:l1_token_address, l1_token_address) |> extend_result(:l2_token_address, l2_token_address) |> extend_result(:block_number, block_number) |> extend_result(:block_timestamp, block_timestamp) @@ -208,6 +219,21 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do end) end + defp bridge_event_parse(event) do + [ + leaf_type, + origin_network, + origin_address, + _destination_network, + _destination_address, + amount, + _metadata, + deposit_count + ] = decode_data(event.data, @bridge_event_params) + + {token_address_by_origin_address(origin_address, origin_network, leaf_type), amount, deposit_count} + end + defp operation_type(first_topic, is_l1) do if first_topic == @bridge_event do if is_l1, do: :deposit, else: :withdrawal @@ -216,27 +242,9 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do end end - defp token_addresses_to_ids(events, json_rpc_named_arguments) do + def token_addresses_to_ids(l1_token_addresses, json_rpc_named_arguments) do token_data = - events - |> Enum.reduce(%MapSet{}, fn event, acc -> - [ - leaf_type, - origin_network, - origin_address, - _destination_network, - _destination_address, - _amount, - _metadata, - _deposit_count - ] = decode_data(event.data, @bridge_event_params) - - case token_address_by_origin_address(origin_address, origin_network, leaf_type) do - {nil, _} -> acc - {token_address, nil} -> MapSet.put(acc, token_address) - end - end) - |> MapSet.to_list() + l1_token_addresses |> get_token_data(json_rpc_named_arguments) tokens_existing = @@ -249,18 +257,11 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do |> Enum.reject(fn {address, _} -> Map.has_key?(tokens_existing, address) end) |> Enum.map(fn {address, data} -> Map.put(data, :address, address) end) - # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise - import_options = - if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do - %{ - zkevm_bridge_l1_tokens: %{params: tokens_to_insert}, - timeout: :infinity - } - else - %{} - end - - {:ok, inserts} = Chain.import(import_options) + {:ok, inserts} = + Chain.import(%{ + zkevm_bridge_l1_tokens: %{params: tokens_to_insert}, + timeout: :infinity + }) tokens_inserted = Map.get(inserts, :insert_zkevm_bridge_l1_tokens, []) diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1_tokens.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1_tokens.ex new file mode 100644 index 000000000000..e341babf09aa --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1_tokens.ex @@ -0,0 +1,78 @@ +defmodule Indexer.Fetcher.Zkevm.BridgeL1Tokens do + @moduledoc """ + Fetches information about L1 tokens for zkEVM bridge. + """ + + use Indexer.Fetcher, restart: :permanent + use Spandex.Decorators + + import Ecto.Query + + alias Explorer.Repo + alias Indexer.BufferedTask + alias Indexer.Fetcher.Zkevm.{Bridge, BridgeL1} + + @behaviour BufferedTask + + @default_max_batch_size 1 + @default_max_concurrency 10 + + @doc false + def child_spec([init_options, gen_server_options]) do + rpc = Application.get_all_env(:indexer)[BridgeL1][:rpc] + json_rpc_named_arguments = Bridge.json_rpc_named_arguments(rpc) + + merged_init_opts = + defaults() + |> Keyword.merge(init_options) + |> Keyword.merge(state: json_rpc_named_arguments) + + Supervisor.child_spec({BufferedTask, [{__MODULE__, merged_init_opts}, gen_server_options]}, id: __MODULE__) + end + + @impl BufferedTask + def init(_, _, _) do + {0, []} + end + + @impl BufferedTask + def run(l1_token_addresses, json_rpc_named_arguments) when is_list(l1_token_addresses) do + l1_token_addresses + |> Bridge.token_addresses_to_ids(json_rpc_named_arguments) + |> Enum.each(fn {l1_token_address, l1_token_id} -> + Repo.update_all( + from(b in Explorer.Chain.Zkevm.Bridge, where: b.l1_token_address == ^l1_token_address), + set: [l1_token_id: l1_token_id, l1_token_address: nil] + ) + end) + end + + @doc """ + Fetches L1 token data asynchronously. + """ + def async_fetch(data) do + async_fetch(data, Application.get_env(:indexer, __MODULE__.Supervisor)[:enabled]) + end + + def async_fetch(_data, false), do: :ok + + def async_fetch(operations, _enabled) do + l1_token_addresses = + operations + |> Enum.reject(fn operation -> is_nil(operation.l1_token_address) end) + |> Enum.map(fn operation -> operation.l1_token_address end) + |> Enum.uniq() + + BufferedTask.buffer(__MODULE__, l1_token_addresses) + end + + defp defaults do + [ + flush_interval: 100, + max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency, + max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size, + poll: false, + task_supervisor: __MODULE__.TaskSupervisor + ] + end +end diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index 9ced4dee8954..10a9de790b8b 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -146,6 +146,7 @@ defmodule Indexer.Supervisor do ]), configure(Indexer.Fetcher.Shibarium.L1.Supervisor, [[memory_monitor: memory_monitor]]), configure(Indexer.Fetcher.Zkevm.BridgeL1.Supervisor, [[memory_monitor: memory_monitor]]), + configure(Indexer.Fetcher.Zkevm.BridgeL1Tokens.Supervisor, [[memory_monitor: memory_monitor]]), configure(Indexer.Fetcher.Zkevm.BridgeL2.Supervisor, [ [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), diff --git a/config/runtime.exs b/config/runtime.exs index fc3cf7bad1ba..625b8aa44f28 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -735,6 +735,7 @@ config :indexer, Indexer.Fetcher.Zkevm.BridgeL1, native_decimals: ConfigHelper.parse_integer_env_var("INDEXER_POLYGON_ZKEVM_L1_BRIDGE_NATIVE_DECIMALS", 18) config :indexer, Indexer.Fetcher.Zkevm.BridgeL1.Supervisor, enabled: ConfigHelper.chain_type() == "polygon_zkevm" +config :indexer, Indexer.Fetcher.Zkevm.BridgeL1Tokens.Supervisor, enabled: ConfigHelper.chain_type() == "polygon_zkevm" config :indexer, Indexer.Fetcher.Zkevm.BridgeL2, start_block: System.get_env("INDEXER_POLYGON_ZKEVM_L2_BRIDGE_START_BLOCK"), From 5ffb2eac880e963dbaa8894be1596623ebc49f06 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Thu, 1 Feb 2024 13:22:09 +0300 Subject: [PATCH 443/607] Refactoring --- .../lib/explorer/chain/zkevm/reader.ex | 64 +++++++++++++++-- apps/explorer/mix.exs | 2 +- .../lib/indexer/fetcher/zkevm/bridge.ex | 68 +++++-------------- 3 files changed, 75 insertions(+), 59 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/zkevm/reader.ex b/apps/explorer/lib/explorer/chain/zkevm/reader.ex index 097f9498ce13..6118ca4820e6 100644 --- a/apps/explorer/lib/explorer/chain/zkevm/reader.ex +++ b/apps/explorer/lib/explorer/chain/zkevm/reader.ex @@ -12,8 +12,9 @@ defmodule Explorer.Chain.Zkevm.Reader do import Explorer.Chain, only: [select_repo: 1] - alias Explorer.Chain.Zkevm.{BatchTransaction, Bridge, LifecycleTransaction, TransactionBatch} + alias Explorer.Chain.Zkevm.{BatchTransaction, Bridge, BridgeL1Token, LifecycleTransaction, TransactionBatch} alias Explorer.{Chain, PagingOptions, Repo} + alias Indexer.Helper @doc """ Reads a batch by its number from database. @@ -87,6 +88,39 @@ defmodule Explorer.Chain.Zkevm.Reader do select_repo(options).all(query) end + @doc """ + Tries to read L1 token data (address, symbol, decimals) for the given addresses + from the database. If the data for an address is not found in Explorer.Chain.Zkevm.BridgeL1Token, + the address is returned in the list inside the tuple (the second item of the tuple). + The first item of the returned tuple contains `L1 token address -> L1 token data` map. + """ + @spec get_token_data_from_db(list()) :: {map(), list()} + def get_token_data_from_db(token_addresses) do + # try to read token symbols and decimals from the database + query = + from( + t in BridgeL1Token, + where: t.address in ^token_addresses, + select: {t.address, t.decimals, t.symbol} + ) + + token_data = + query + |> Repo.all() + |> Enum.reduce(%{}, fn {address, decimals, symbol}, acc -> + token_address = Helper.address_hash_to_string(address, true) + Map.put(acc, token_address, %{symbol: symbol, decimals: decimals}) + end) + + token_addresses_for_rpc = + token_addresses + |> Enum.reject(fn address -> + Map.has_key?(token_data, Helper.address_hash_to_string(address, true)) + end) + + {token_data, token_addresses_for_rpc} + end + @doc """ Gets last known L1 item (deposit) from zkevm_bridge table. Returns block number and L1 transaction hash bound to that deposit. @@ -182,8 +216,24 @@ defmodule Explorer.Chain.Zkevm.Reader do end @doc """ - Retrieves a list of Polygon zkEVM deposits (completed and unclaimed) - sorted in descending order of the index. + Builds `L1 token address -> L1 token id` map for the given token addresses. + The info is taken from Explorer.Chain.Zkevm.BridgeL1Token. + If an address is not in the table, it won't be in the resulting map. + """ + @spec token_addresses_to_ids_from_db(list()) :: map() + def token_addresses_to_ids_from_db(addresses) do + query = from(t in BridgeL1Token, select: {t.address, t.id}, where: t.address in ^addresses) + + query + |> Repo.all(timeout: :infinity) + |> Enum.reduce(%{}, fn {address, id}, acc -> + Map.put(acc, Helper.address_hash_to_string(address), id) + end) + end + + @doc """ + Retrieves a list of Polygon zkEVM deposits (completed and unclaimed) + sorted in descending order of the index. """ @spec deposits(list()) :: list() def deposits(options \\ []) do @@ -206,7 +256,7 @@ defmodule Explorer.Chain.Zkevm.Reader do end @doc """ - Returns a total number of Polygon zkEVM deposits (completed and unclaimed). + Returns a total number of Polygon zkEVM deposits (completed and unclaimed). """ @spec deposits_count(list()) :: term() | nil def deposits_count(options \\ []) do @@ -220,8 +270,8 @@ defmodule Explorer.Chain.Zkevm.Reader do end @doc """ - Retrieves a list of Polygon zkEVM withdrawals (completed and unclaimed) - sorted in descending order of the index. + Retrieves a list of Polygon zkEVM withdrawals (completed and unclaimed) + sorted in descending order of the index. """ @spec withdrawals(list()) :: list() def withdrawals(options \\ []) do @@ -244,7 +294,7 @@ defmodule Explorer.Chain.Zkevm.Reader do end @doc """ - Returns a total number of Polygon zkEVM withdrawals (completed and unclaimed). + Returns a total number of Polygon zkEVM withdrawals (completed and unclaimed). """ @spec withdrawals_count(list()) :: term() | nil def withdrawals_count(options \\ []) do diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index b00731d3e74c..51cf73034e63 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -25,7 +25,7 @@ defmodule Explorer.Mixfile do ], start_permanent: Mix.env() == :prod, version: "6.1.0", - xref: [exclude: [BlockScoutWeb.WebRouter.Helpers]] + xref: [exclude: [BlockScoutWeb.WebRouter.Helpers, Indexer.Helper]] ] end diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex index 625f67646b36..0f9a60d5b7fc 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex @@ -5,8 +5,6 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do require Logger - import Ecto.Query - import EthereumJSONRPC, only: [ integer_to_quantity: 1, @@ -20,10 +18,9 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do import Explorer.Helper, only: [decode_data: 2] alias EthereumJSONRPC.Logs - alias Explorer.Chain.Hash - alias Explorer.Chain.Zkevm.BridgeL1Token - alias Explorer.{Chain, Repo} - alias Explorer.SmartContract.Reader + alias Explorer.Chain + alias Explorer.Chain.Zkevm.Reader + alias Explorer.SmartContract.Reader, as: SmartContractReader alias Indexer.Helper alias Indexer.Transform.Addresses @@ -35,6 +32,9 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do @claim_event "0x25308c93ceeed162da955b3f7ce3e3f93606579e40fb92029faa9efe27545983" @claim_event_params [{:uint, 32}, {:uint, 32}, :address, :address, {:uint, 256}] + @symbol_method_selector "95d89b41" + @decimals_method_selector "313ce567" + @erc20_abi [ %{ "constant" => true, @@ -59,7 +59,7 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do @spec filter_bridge_events(list(), binary()) :: list() def filter_bridge_events(events, bridge_contract) do Enum.filter(events, fn event -> - String.downcase(event.address_hash) == bridge_contract and + Helper.address_hash_to_string(event.address_hash, true) == bridge_contract and Enum.member?([@bridge_event, @claim_event], Helper.log_topic_to_string(event.first_topic)) end) end @@ -250,7 +250,7 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do tokens_existing = token_data |> Map.keys() - |> token_addresses_to_ids_from_db() + |> Reader.token_addresses_to_ids_from_db() tokens_to_insert = token_data @@ -271,26 +271,18 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do tokens_not_inserted = tokens_to_insert |> Enum.reject(fn token -> - Enum.any?(tokens_inserted, fn inserted -> token.address == Hash.to_string(inserted.address) end) + Enum.any?(tokens_inserted, fn inserted -> token.address == Helper.address_hash_to_string(inserted.address) end) end) |> Enum.map(& &1.address) - tokens_inserted_outside = token_addresses_to_ids_from_db(tokens_not_inserted) + tokens_inserted_outside = Reader.token_addresses_to_ids_from_db(tokens_not_inserted) tokens_inserted - |> Enum.reduce(%{}, fn t, acc -> Map.put(acc, Hash.to_string(t.address), t.id) end) + |> Enum.reduce(%{}, fn t, acc -> Map.put(acc, Helper.address_hash_to_string(t.address), t.id) end) |> Map.merge(tokens_existing) |> Map.merge(tokens_inserted_outside) end - defp token_addresses_to_ids_from_db(addresses) do - query = from(t in BridgeL1Token, select: {t.address, t.id}, where: t.address in ^addresses) - - query - |> Repo.all(timeout: :infinity) - |> Enum.reduce(%{}, fn {address, id}, acc -> Map.put(acc, Hash.to_string(address), id) end) - end - defp token_address_by_origin_address(origin_address, origin_network, leaf_type) do with true <- leaf_type != 1 and origin_network <= 1, token_address = "0x" <> Base.encode16(origin_address, case: :lower), @@ -311,36 +303,10 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do # first, we're trying to read token data from the DB. # if tokens are not in the DB, read them through RPC. token_addresses - |> get_token_data_from_db() + |> Reader.get_token_data_from_db() |> get_token_data_from_rpc(json_rpc_named_arguments) end - defp get_token_data_from_db(token_addresses) do - # try to read token symbols and decimals from the database - query = - from( - t in BridgeL1Token, - where: t.address in ^token_addresses, - select: {t.address, t.decimals, t.symbol} - ) - - token_data = - query - |> Repo.all() - |> Enum.reduce(%{}, fn {address, decimals, symbol}, acc -> - token_address = String.downcase(Hash.to_string(address)) - Map.put(acc, token_address, %{symbol: symbol, decimals: decimals}) - end) - - token_addresses_for_rpc = - token_addresses - |> Enum.reject(fn address -> - Map.has_key?(token_data, String.downcase(address)) - end) - - {token_data, token_addresses_for_rpc} - end - defp get_token_data_from_rpc({token_data, token_addresses}, json_rpc_named_arguments) do {requests, responses} = get_token_data_request_symbol_decimals(token_addresses, json_rpc_named_arguments) @@ -350,7 +316,7 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do if status == :ok do response = parse_response(response) - address = String.downcase(request.contract_address) + address = Helper.address_hash_to_string(request.contract_address, true) new_data = get_new_data(token_data_acc[address] || %{}, request, response) @@ -366,7 +332,7 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do token_addresses |> Enum.map(fn address -> # we will call symbol() and decimals() public getters - Enum.map(["95d89b41", "313ce567"], fn method_id -> + Enum.map([@symbol_method_selector, @decimals_method_selector], fn method_id -> %{ contract_address: address, method_id: method_id, @@ -388,7 +354,7 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do end defp read_contracts_with_retries(requests, abi, json_rpc_named_arguments, retries_left) when retries_left > 0 do - responses = Reader.query_contracts(requests, abi, json_rpc_named_arguments: json_rpc_named_arguments) + responses = SmartContractReader.query_contracts(requests, abi, json_rpc_named_arguments: json_rpc_named_arguments) error_messages = Enum.reduce(responses, [], fn {status, error_message}, acc -> @@ -427,8 +393,8 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do defp atomized_key("symbol"), do: :symbol defp atomized_key("decimals"), do: :decimals - defp atomized_key("95d89b41"), do: :symbol - defp atomized_key("313ce567"), do: :decimals + defp atomized_key(@symbol_method_selector), do: :symbol + defp atomized_key(@decimals_method_selector), do: :decimals defp parse_response(response) do case response do From 42314a620b5463960df2ce16264d9c8418992906 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Fri, 2 Feb 2024 10:22:00 +0300 Subject: [PATCH 444/607] Add doc comments for public functions --- apps/indexer/lib/indexer/block/fetcher.ex | 6 +++++ .../lib/indexer/fetcher/zkevm/bridge.ex | 26 +++++++++++++++++++ apps/indexer/lib/indexer/helper.ex | 10 +++++++ 3 files changed, 42 insertions(+) diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index efde267a944c..af31ceeec542 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -422,6 +422,12 @@ defmodule Indexer.Block.Fetcher do def async_import_replaced_transactions(_), do: :ok + @doc """ + Fills a buffer of L1 token addresses to handle it asyncronously in + the Indexer.Fetcher.Zkevm.BridgeL1Tokens module. The addresses are + taken from the `operations` list. + """ + @spec async_import_zkevm_bridge_l1_tokens(map()) :: :ok def async_import_zkevm_bridge_l1_tokens(%{zkevm_bridge_operations: operations}) do ZkevmBridgeL1Tokens.async_fetch(operations) end diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex index 0f9a60d5b7fc..72ea31d67d2d 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex @@ -56,6 +56,10 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do } ] + @doc """ + Filters the given list of events keeping only `BridgeEvent` and `ClaimEvent` ones + emitted by the bridge contract. + """ @spec filter_bridge_events(list(), binary()) :: list() def filter_bridge_events(events, bridge_contract) do Enum.filter(events, fn event -> @@ -64,6 +68,10 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do end) end + @doc """ + Fetches `BridgeEvent` and `ClaimEvent` events of the bridge contract from an RPC node + for the given range of blocks. + """ @spec get_logs_all({non_neg_integer(), non_neg_integer()}, binary(), list()) :: list() def get_logs_all({chunk_start, chunk_end}, bridge_contract, json_rpc_named_arguments) do {:ok, result} = @@ -101,6 +109,11 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do Helper.repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) end + @doc """ + Imports the given zkEVM bridge operations into database. + Used by Indexer.Fetcher.Zkevm.BridgeL1 and Indexer.Fetcher.Zkevm.BridgeL2 fetchers. + Doesn't return anything. + """ @spec import_operations(list()) :: no_return() def import_operations(operations) do addresses = @@ -116,6 +129,9 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do }) end + @doc """ + Forms JSON RPC named arguments for the given RPC URL. + """ @spec json_rpc_named_arguments(binary()) :: list() def json_rpc_named_arguments(rpc_url) do [ @@ -132,6 +148,10 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do ] end + @doc """ + Converts the list of zkEVM bridge events to the list of operations + preparing them for importing to the database. + """ @spec prepare_operations(list(), list() | nil, list(), map() | nil) :: list() def prepare_operations(events, json_rpc_named_arguments, json_rpc_named_arguments_l1, block_to_timestamp \\ nil) do {block_to_timestamp, token_address_to_id} = @@ -242,6 +262,12 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do end end + @doc """ + Fetches L1 token data for the given token addresses, + builds `L1 token address -> L1 token id` map for them, + and writes the data to the database. Returns the resulting map. + """ + @spec token_addresses_to_ids(list(), list()) :: map() def token_addresses_to_ids(l1_token_addresses, json_rpc_named_arguments) do token_data = l1_token_addresses diff --git a/apps/indexer/lib/indexer/helper.ex b/apps/indexer/lib/indexer/helper.ex index 07ee927c6b72..3b1cb18b61cf 100644 --- a/apps/indexer/lib/indexer/helper.ex +++ b/apps/indexer/lib/indexer/helper.ex @@ -17,6 +17,11 @@ defmodule Indexer.Helper do alias EthereumJSONRPC.Blocks alias Explorer.Chain.Hash + @doc """ + Checks whether the given Ethereum address looks correct. + The address should begin with 0x prefix and then contain 40 hexadecimal digits (can be in mixed case). + This function doesn't check if the address is checksummed. + """ @spec address_correct?(binary()) :: boolean() def address_correct?(address) when is_binary(address) do String.match?(address, ~r/^0x[[:xdigit:]]{40}$/i) @@ -26,6 +31,11 @@ defmodule Indexer.Helper do false end + @doc """ + Converts Explorer.Chain.Hash representation of the given address to a string + beginning with 0x prefix. If the given address is already a string, it is not modified. + The second argument forces the result to be downcased. + """ @spec address_hash_to_string(binary(), boolean()) :: binary() def address_hash_to_string(hash, downcase \\ false) From 2949f021899f0860aa47dbcb683c4d898754c3ea Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Fri, 2 Feb 2024 10:27:36 +0300 Subject: [PATCH 445/607] Fix spelling --- apps/indexer/lib/indexer/block/fetcher.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index af31ceeec542..fb8d24f5210b 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -423,7 +423,7 @@ defmodule Indexer.Block.Fetcher do def async_import_replaced_transactions(_), do: :ok @doc """ - Fills a buffer of L1 token addresses to handle it asyncronously in + Fills a buffer of L1 token addresses to handle it asynchronously in the Indexer.Fetcher.Zkevm.BridgeL1Tokens module. The addresses are taken from the `operations` list. """ From ce06032aba045883d5cab504e9094e54a61b864f Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Mon, 5 Feb 2024 15:14:11 +0300 Subject: [PATCH 446/607] Add Indexer.Fetcher.RollupL1ReorgMonitor module and remove duplicated code --- .../lib/explorer/chain/events/publisher.ex | 2 +- .../lib/explorer/chain/events/subscriber.ex | 2 +- .../lib/indexer/fetcher/polygon_edge.ex | 158 +----------------- .../indexer/fetcher/polygon_edge/deposit.ex | 13 +- .../fetcher/polygon_edge/deposit_execute.ex | 2 + .../fetcher/polygon_edge/withdrawal.ex | 2 + .../fetcher/polygon_edge/withdrawal_exit.ex | 13 +- .../fetcher/rollup_l1_reorg_monitor.ex | 141 ++++++++++++++++ .../lib/indexer/fetcher/shibarium/l1.ex | 77 +-------- .../lib/indexer/fetcher/zkevm/bridge.ex | 19 --- .../lib/indexer/fetcher/zkevm/bridge_l1.ex | 113 ++----------- .../indexer/fetcher/zkevm/bridge_l1_tokens.ex | 4 +- .../lib/indexer/fetcher/zkevm/bridge_l2.ex | 4 +- apps/indexer/lib/indexer/helper.ex | 60 +++++++ apps/indexer/lib/indexer/supervisor.ex | 3 +- .../lib/indexer/transform/zkevm/bridge.ex | 4 +- config/runtime.exs | 2 - 17 files changed, 248 insertions(+), 371 deletions(-) create mode 100644 apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex diff --git a/apps/explorer/lib/explorer/chain/events/publisher.ex b/apps/explorer/lib/explorer/chain/events/publisher.ex index 3dca04f31f73..87da3c8e9175 100644 --- a/apps/explorer/lib/explorer/chain/events/publisher.ex +++ b/apps/explorer/lib/explorer/chain/events/publisher.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.Events.Publisher do Publishes events related to the Chain context. """ - @allowed_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number polygon_edge_reorg_block token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches eth_bytecode_db_lookup_started smart_contract_was_not_verified)a + @allowed_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches eth_bytecode_db_lookup_started smart_contract_was_not_verified)a def broadcast(_data, false), do: :ok diff --git a/apps/explorer/lib/explorer/chain/events/subscriber.ex b/apps/explorer/lib/explorer/chain/events/subscriber.ex index f2aa49f61fe3..f073afb8eb29 100644 --- a/apps/explorer/lib/explorer/chain/events/subscriber.ex +++ b/apps/explorer/lib/explorer/chain/events/subscriber.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.Events.Subscriber do Subscribes to events related to the Chain context. """ - @allowed_broadcast_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number polygon_edge_reorg_block token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches eth_bytecode_db_lookup_started smart_contract_was_not_verified)a + @allowed_broadcast_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches eth_bytecode_db_lookup_started smart_contract_was_not_verified)a @allowed_broadcast_types ~w(catchup realtime on_demand contract_verification_result)a diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge.ex index 387894825948..ee1fe71ddb62 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge.ex @@ -3,6 +3,8 @@ defmodule Indexer.Fetcher.PolygonEdge do Contains common functions for PolygonEdge.* fetchers. """ + # todo: this module is deprecated and should be removed + use GenServer use Indexer.Fetcher @@ -15,13 +17,11 @@ defmodule Indexer.Fetcher.PolygonEdge do import Explorer.Helper, only: [parse_integer: 1] - alias Explorer.Chain.Events.Publisher alias Explorer.{Chain, Repo} - alias Indexer.{BoundQueue, Helper} + alias Indexer.Helper alias Indexer.Fetcher.PolygonEdge.{Deposit, DepositExecute, Withdrawal, WithdrawalExit} @fetcher_name :polygon_edge - @block_check_interval_range_size 100 def child_spec(start_link_arguments) do spec = %{ @@ -41,29 +41,7 @@ defmodule Indexer.Fetcher.PolygonEdge do @impl GenServer def init(_args) do Logger.metadata(fetcher: @fetcher_name) - - modules_using_reorg_monitor = [Deposit, WithdrawalExit] - - reorg_monitor_not_needed = - modules_using_reorg_monitor - |> Enum.all?(fn module -> - is_nil(Application.get_all_env(:indexer)[module][:start_block_l1]) - end) - - if reorg_monitor_not_needed do - :ignore - else - polygon_edge_l1_rpc = Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonEdge][:polygon_edge_l1_rpc] - - json_rpc_named_arguments = json_rpc_named_arguments(polygon_edge_l1_rpc) - - {:ok, block_check_interval, _} = get_block_check_interval(json_rpc_named_arguments) - - Process.send(self(), :reorg_monitor, []) - - {:ok, - %{block_check_interval: block_check_interval, json_rpc_named_arguments: json_rpc_named_arguments, prev_latest: 0}} - end + :ignore end @spec init_l1( @@ -78,8 +56,6 @@ defmodule Indexer.Fetcher.PolygonEdge do def init_l1(table, env, pid, contract_address, contract_name, table_name, entity_name) when table in [Explorer.Chain.PolygonEdge.Deposit, Explorer.Chain.PolygonEdge.WithdrawalExit] do with {:start_block_l1_undefined, false} <- {:start_block_l1_undefined, is_nil(env[:start_block_l1])}, - {:reorg_monitor_started, true} <- - {:reorg_monitor_started, !is_nil(Process.whereis(Indexer.Fetcher.PolygonEdge))}, polygon_edge_l1_rpc = Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonEdge][:polygon_edge_l1_rpc], {:rpc_l1_undefined, false} <- {:rpc_l1_undefined, is_nil(polygon_edge_l1_rpc)}, {:contract_is_valid, true} <- {:contract_is_valid, Helper.address_correct?(contract_address)}, @@ -94,7 +70,7 @@ defmodule Indexer.Fetcher.PolygonEdge do Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments, 100_000_000), {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)}, {:ok, block_check_interval, last_safe_block} <- - get_block_check_interval(json_rpc_named_arguments) do + Helper.get_block_check_interval(json_rpc_named_arguments) do start_block = max(start_block_l1, last_l1_block_number) Process.send(pid, :continue, []) @@ -112,10 +88,6 @@ defmodule Indexer.Fetcher.PolygonEdge do # the process shouldn't start if the start block is not defined :ignore - {:reorg_monitor_started, false} -> - Logger.error("Cannot start this process as reorg monitor in Indexer.Fetcher.PolygonEdge is not started.") - :ignore - {:rpc_l1_undefined, true} -> Logger.error("L1 RPC URL is not defined.") :ignore @@ -217,29 +189,7 @@ defmodule Indexer.Fetcher.PolygonEdge do end end - @impl GenServer - def handle_info( - :reorg_monitor, - %{ - block_check_interval: block_check_interval, - json_rpc_named_arguments: json_rpc_named_arguments, - prev_latest: prev_latest - } = state - ) do - {:ok, latest} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) - - if latest < prev_latest do - Logger.warning("Reorg detected: previous latest block ##{prev_latest}, current latest block ##{latest}.") - - Publisher.broadcast([{:polygon_edge_reorg_block, latest}], :realtime) - end - - Process.send_after(self(), :reorg_monitor, block_check_interval) - - {:noreply, %{state | prev_latest: latest}} - end - - @spec handle_continue(map(), binary(), Deposit | WithdrawalExit, atom()) :: {:noreply, map()} + @spec handle_continue(map(), binary(), Deposit | WithdrawalExit) :: {:noreply, map()} def handle_continue( %{ contract_address: contract_address, @@ -249,8 +199,7 @@ defmodule Indexer.Fetcher.PolygonEdge do json_rpc_named_arguments: json_rpc_named_arguments } = state, event_signature, - calling_module, - fetcher_name + calling_module ) when calling_module in [Deposit, WithdrawalExit] do time_before = Timex.now() @@ -295,14 +244,7 @@ defmodule Indexer.Fetcher.PolygonEdge do ) end - reorg_block = reorg_block_pop(fetcher_name) - - if !is_nil(reorg_block) && reorg_block > 0 do - reorg_handle(reorg_block, calling_module) - {:halt, if(reorg_block <= chunk_end, do: reorg_block - 1, else: chunk_end)} - else - {:cont, chunk_end} - end + {:cont, chunk_end} end) new_start_block = last_written_block + 1 @@ -540,26 +482,6 @@ defmodule Indexer.Fetcher.PolygonEdge do Repo.all(query) end - defp get_block_check_interval(json_rpc_named_arguments) do - {last_safe_block, _} = get_safe_block(json_rpc_named_arguments) - - first_block = max(last_safe_block - @block_check_interval_range_size, 1) - - with {:ok, first_block_timestamp} <- - Helper.get_block_timestamp_by_number(first_block, json_rpc_named_arguments, 100_000_000), - {:ok, last_safe_block_timestamp} <- - Helper.get_block_timestamp_by_number(last_safe_block, json_rpc_named_arguments, 100_000_000) do - block_check_interval = - ceil((last_safe_block_timestamp - first_block_timestamp) / (last_safe_block - first_block) * 1000 / 2) - - Logger.info("Block check interval is calculated as #{block_check_interval} ms.") - {:ok, block_check_interval, last_safe_block} - else - {:error, error} -> - {:error, "Failed to calculate block check interval due to #{inspect(error)}"} - end - end - defp get_safe_block(json_rpc_named_arguments) do case Helper.get_block_number_by_tag("safe", json_rpc_named_arguments) do {:ok, safe_block} -> @@ -667,72 +589,8 @@ defmodule Indexer.Fetcher.PolygonEdge do {events, event_name} end - defp log_deleted_rows_count(reorg_block, count, table_name) do - if count > 0 do - Logger.warning( - "As L1 reorg was detected, all rows with l1_block_number >= #{reorg_block} were removed from the #{table_name} table. Number of removed rows: #{count}." - ) - end - end - @spec repeated_request(list(), any(), list(), non_neg_integer()) :: {:ok, any()} | {:error, atom()} def repeated_request(req, error_message, json_rpc_named_arguments, retries) do Helper.repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) end - - defp reorg_block_pop(fetcher_name) do - table_name = reorg_table_name(fetcher_name) - - case BoundQueue.pop_front(reorg_queue_get(table_name)) do - {:ok, {block_number, updated_queue}} -> - :ets.insert(table_name, {:queue, updated_queue}) - block_number - - {:error, :empty} -> - nil - end - end - - @spec reorg_block_push(atom(), non_neg_integer()) :: no_return() - def reorg_block_push(fetcher_name, block_number) do - table_name = reorg_table_name(fetcher_name) - {:ok, updated_queue} = BoundQueue.push_back(reorg_queue_get(table_name), block_number) - :ets.insert(table_name, {:queue, updated_queue}) - end - - defp reorg_handle(reorg_block, calling_module) do - {table, table_name} = - if calling_module == Deposit do - {Explorer.Chain.PolygonEdge.Deposit, "polygon_edge_deposits"} - else - {Explorer.Chain.PolygonEdge.WithdrawalExit, "polygon_edge_withdrawal_exits"} - end - - {deleted_count, _} = Repo.delete_all(from(item in table, where: item.l1_block_number >= ^reorg_block)) - - log_deleted_rows_count(reorg_block, deleted_count, table_name) - end - - defp reorg_queue_get(table_name) do - if :ets.whereis(table_name) == :undefined do - :ets.new(table_name, [ - :set, - :named_table, - :public, - read_concurrency: true, - write_concurrency: true - ]) - end - - with info when info != :undefined <- :ets.info(table_name), - [{_, value}] <- :ets.lookup(table_name, :queue) do - value - else - _ -> %BoundQueue{} - end - end - - defp reorg_table_name(fetcher_name) do - :"#{fetcher_name}#{:_reorgs}" - end end diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit.ex index ca11c30e08c8..642e1951cacc 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit.ex @@ -3,6 +3,8 @@ defmodule Indexer.Fetcher.PolygonEdge.Deposit do Fills polygon_edge_deposits DB table. """ + # todo: this module is deprecated and should be removed + use GenServer use Indexer.Fetcher @@ -14,7 +16,6 @@ defmodule Indexer.Fetcher.PolygonEdge.Deposit do alias ABI.TypeDecoder alias EthereumJSONRPC.Block.ByNumber alias EthereumJSONRPC.Blocks - alias Explorer.Chain.Events.Subscriber alias Explorer.Chain.PolygonEdge.Deposit alias Indexer.Fetcher.PolygonEdge @@ -47,8 +48,6 @@ defmodule Indexer.Fetcher.PolygonEdge.Deposit do env = Application.get_all_env(:indexer)[__MODULE__] - Subscriber.to(:polygon_edge_reorg_block, :realtime) - PolygonEdge.init_l1( Deposit, env, @@ -62,13 +61,7 @@ defmodule Indexer.Fetcher.PolygonEdge.Deposit do @impl GenServer def handle_info(:continue, state) do - PolygonEdge.handle_continue(state, @state_synced_event, __MODULE__, @fetcher_name) - end - - @impl GenServer - def handle_info({:chain_event, :polygon_edge_reorg_block, :realtime, block_number}, state) do - PolygonEdge.reorg_block_push(@fetcher_name, block_number) - {:noreply, state} + PolygonEdge.handle_continue(state, @state_synced_event, __MODULE__) end @impl GenServer diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex index 8367883ee144..82a8a2d7ed2f 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex @@ -3,6 +3,8 @@ defmodule Indexer.Fetcher.PolygonEdge.DepositExecute do Fills polygon_edge_deposit_executes DB table. """ + # todo: this module is deprecated and should be removed + use GenServer use Indexer.Fetcher diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex index 4a8ae47d220b..c952d51618f2 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex @@ -3,6 +3,8 @@ defmodule Indexer.Fetcher.PolygonEdge.Withdrawal do Fills polygon_edge_withdrawals DB table. """ + # todo: this module is deprecated and should be removed + use GenServer use Indexer.Fetcher diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal_exit.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal_exit.ex index 5b41e122ddc8..e19ea6517cf1 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal_exit.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal_exit.ex @@ -3,6 +3,8 @@ defmodule Indexer.Fetcher.PolygonEdge.WithdrawalExit do Fills polygon_edge_withdrawal_exits DB table. """ + # todo: this module is deprecated and should be removed + use GenServer use Indexer.Fetcher @@ -10,7 +12,6 @@ defmodule Indexer.Fetcher.PolygonEdge.WithdrawalExit do import EthereumJSONRPC, only: [quantity_to_integer: 1] - alias Explorer.Chain.Events.Subscriber alias Explorer.Chain.PolygonEdge.WithdrawalExit alias Indexer.Fetcher.PolygonEdge @@ -40,8 +41,6 @@ defmodule Indexer.Fetcher.PolygonEdge.WithdrawalExit do env = Application.get_all_env(:indexer)[__MODULE__] - Subscriber.to(:polygon_edge_reorg_block, :realtime) - PolygonEdge.init_l1( WithdrawalExit, env, @@ -55,13 +54,7 @@ defmodule Indexer.Fetcher.PolygonEdge.WithdrawalExit do @impl GenServer def handle_info(:continue, state) do - PolygonEdge.handle_continue(state, @exit_processed_event, __MODULE__, @fetcher_name) - end - - @impl GenServer - def handle_info({:chain_event, :polygon_edge_reorg_block, :realtime, block_number}, state) do - PolygonEdge.reorg_block_push(@fetcher_name, block_number) - {:noreply, state} + PolygonEdge.handle_continue(state, @exit_processed_event, __MODULE__) end @impl GenServer diff --git a/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex b/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex new file mode 100644 index 000000000000..bb1cdc7b6eeb --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex @@ -0,0 +1,141 @@ +defmodule Indexer.Fetcher.RollupL1ReorgMonitor do + @moduledoc """ + A module to catch L1 reorgs and notify a rollup module about it. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + alias Indexer.{BoundQueue, Helper} + + @fetcher_name :rollup_l1_reorg_monitor + + def child_spec(start_link_arguments) do + spec = %{ + id: __MODULE__, + start: {__MODULE__, :start_link, start_link_arguments}, + restart: :transient, + type: :worker + } + + Supervisor.child_spec(spec, []) + end + + def start_link(args, gen_server_options \\ []) do + GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__)) + end + + @impl GenServer + def init(_args) do + Logger.metadata(fetcher: @fetcher_name) + + modules_can_use_reorg_monitor = [ + Indexer.Fetcher.Shibarium.L1, + Indexer.Fetcher.Zkevm.BridgeL1 + ] + + modules_using_reorg_monitor = + modules_can_use_reorg_monitor + |> Enum.reject(fn module -> + is_nil(Application.get_all_env(:indexer)[module][:start_block]) + end) + + cond do + Enum.count(modules_using_reorg_monitor) > 1 -> + Logger.error("#{__MODULE__} cannot work for more than one rollup module. Please, check config.") + :ignore + + Enum.empty?(modules_using_reorg_monitor) -> + # don't start reorg monitor as there is no module which would use it + :ignore + + true -> + module_using_reorg_monitor = Enum.at(modules_using_reorg_monitor, 0) + + l1_rpc = Application.get_all_env(:indexer)[module_using_reorg_monitor][:rpc] + + json_rpc_named_arguments = Helper.json_rpc_named_arguments(l1_rpc) + + {:ok, block_check_interval, _} = Helper.get_block_check_interval(json_rpc_named_arguments) + + Process.send(self(), :reorg_monitor, []) + + {:ok, + %{ + block_check_interval: block_check_interval, + json_rpc_named_arguments: json_rpc_named_arguments, + prev_latest: 0 + }} + end + end + + @impl GenServer + def handle_info( + :reorg_monitor, + %{ + block_check_interval: block_check_interval, + json_rpc_named_arguments: json_rpc_named_arguments, + prev_latest: prev_latest + } = state + ) do + {:ok, latest} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + + if latest < prev_latest do + Logger.warning("Reorg detected: previous latest block ##{prev_latest}, current latest block ##{latest}.") + reorg_block_push(latest) + end + + Process.send_after(self(), :reorg_monitor, block_check_interval) + + {:noreply, %{state | prev_latest: latest}} + end + + @doc """ + Pops the number of reorg block from the front of the queue. + Returns `nil` if the reorg queue is empty. + """ + @spec reorg_block_pop() :: non_neg_integer() | nil + def reorg_block_pop do + table_name = reorg_table_name(@fetcher_name) + + case BoundQueue.pop_front(reorg_queue_get(table_name)) do + {:ok, {block_number, updated_queue}} -> + :ets.insert(table_name, {:queue, updated_queue}) + block_number + + {:error, :empty} -> + nil + end + end + + defp reorg_block_push(block_number) do + table_name = reorg_table_name(@fetcher_name) + {:ok, updated_queue} = BoundQueue.push_back(reorg_queue_get(table_name), block_number) + :ets.insert(table_name, {:queue, updated_queue}) + end + + defp reorg_queue_get(table_name) do + if :ets.whereis(table_name) == :undefined do + :ets.new(table_name, [ + :set, + :named_table, + :public, + read_concurrency: true, + write_concurrency: true + ]) + end + + with info when info != :undefined <- :ets.info(table_name), + [{_, value}] <- :ets.lookup(table_name, :queue) do + value + else + _ -> %BoundQueue{} + end + end + + defp reorg_table_name(fetcher_name) do + :"#{fetcher_name}#{:_reorgs}" + end +end diff --git a/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex b/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex index 906acd268902..841dfafd8590 100644 --- a/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex +++ b/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex @@ -25,7 +25,8 @@ defmodule Indexer.Fetcher.Shibarium.L1 do alias Explorer.Chain.Shibarium.Bridge alias Explorer.{Chain, Repo} - alias Indexer.{BoundQueue, Helper} + alias Indexer.Fetcher.RollupL1ReorgMonitor + alias Indexer.Helper @block_check_interval_range_size 100 @eth_get_logs_range_size 1000 @@ -109,6 +110,7 @@ defmodule Indexer.Fetcher.Shibarium.L1 do env = Application.get_all_env(:indexer)[__MODULE__] with {:start_block_undefined, false} <- {:start_block_undefined, is_nil(env[:start_block])}, + {:reorg_monitor_started, true} <- {:reorg_monitor_started, !is_nil(Process.whereis(RollupL1ReorgMonitor))}, rpc = env[:rpc], {:rpc_undefined, false} <- {:rpc_undefined, is_nil(rpc)}, {:deposit_manager_address_is_valid, true} <- @@ -138,7 +140,6 @@ defmodule Indexer.Fetcher.Shibarium.L1 do {:start_block_valid, true} <- {:start_block_valid, start_block <= latest_block} do recalculate_cached_count() - Process.send(self(), :reorg_monitor, []) Process.send(self(), :continue, []) {:noreply, @@ -152,14 +153,17 @@ defmodule Indexer.Fetcher.Shibarium.L1 do block_check_interval: block_check_interval, start_block: max(start_block, last_l1_block_number), end_block: latest_block, - json_rpc_named_arguments: json_rpc_named_arguments, - reorg_monitor_prev_latest: 0 + json_rpc_named_arguments: json_rpc_named_arguments }} else {:start_block_undefined, true} -> # the process shouldn't start if the start block is not defined {:stop, :normal, %{}} + {:reorg_monitor_started, false} -> + Logger.error("Cannot start this process as Indexer.Fetcher.RollupL1ReorgMonitor is not started.") + {:stop, :normal, %{}} + {:rpc_undefined, true} -> Logger.error("L1 RPC URL is not defined.") {:stop, :normal, %{}} @@ -212,27 +216,6 @@ defmodule Indexer.Fetcher.Shibarium.L1 do end end - @impl GenServer - def handle_info( - :reorg_monitor, - %{ - block_check_interval: block_check_interval, - json_rpc_named_arguments: json_rpc_named_arguments, - reorg_monitor_prev_latest: prev_latest - } = state - ) do - {:ok, latest} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) - - if latest < prev_latest do - Logger.warning("Reorg detected: previous latest block ##{prev_latest}, current latest block ##{latest}.") - reorg_block_push(latest) - end - - Process.send_after(self(), :reorg_monitor, block_check_interval) - - {:noreply, %{state | reorg_monitor_prev_latest: latest}} - end - @impl GenServer def handle_info( :continue, @@ -290,7 +273,7 @@ defmodule Indexer.Fetcher.Shibarium.L1 do ) end - reorg_block = reorg_block_pop() + reorg_block = RollupL1ReorgMonitor.reorg_block_pop() if !is_nil(reorg_block) && reorg_block > 0 do reorg_handle(reorg_block) @@ -626,25 +609,6 @@ defmodule Indexer.Fetcher.Shibarium.L1 do "0x#{truncated_hash}" end - defp reorg_block_pop do - table_name = reorg_table_name(@fetcher_name) - - case BoundQueue.pop_front(reorg_queue_get(table_name)) do - {:ok, {block_number, updated_queue}} -> - :ets.insert(table_name, {:queue, updated_queue}) - block_number - - {:error, :empty} -> - nil - end - end - - defp reorg_block_push(block_number) do - table_name = reorg_table_name(@fetcher_name) - {:ok, updated_queue} = BoundQueue.push_back(reorg_queue_get(table_name), block_number) - :ets.insert(table_name, {:queue, updated_queue}) - end - defp reorg_handle(reorg_block) do {deleted_count, _} = Repo.delete_all(from(sb in Bridge, where: sb.l1_block_number >= ^reorg_block and is_nil(sb.l2_transaction_hash))) @@ -675,27 +639,4 @@ defmodule Indexer.Fetcher.Shibarium.L1 do ) end end - - defp reorg_queue_get(table_name) do - if :ets.whereis(table_name) == :undefined do - :ets.new(table_name, [ - :set, - :named_table, - :public, - read_concurrency: true, - write_concurrency: true - ]) - end - - with info when info != :undefined <- :ets.info(table_name), - [{_, value}] <- :ets.lookup(table_name, :queue) do - value - else - _ -> %BoundQueue{} - end - end - - defp reorg_table_name(fetcher_name) do - :"#{fetcher_name}#{:_reorgs}" - end end diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex index 72ea31d67d2d..1180e0771c8d 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex @@ -129,25 +129,6 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do }) end - @doc """ - Forms JSON RPC named arguments for the given RPC URL. - """ - @spec json_rpc_named_arguments(binary()) :: list() - def json_rpc_named_arguments(rpc_url) do - [ - transport: EthereumJSONRPC.HTTP, - transport_options: [ - http: EthereumJSONRPC.HTTP.HTTPoison, - url: rpc_url, - http_options: [ - recv_timeout: :timer.minutes(10), - timeout: :timer.minutes(10), - hackney: [pool: :ethereum_jsonrpc] - ] - ] - ] - end - @doc """ Converts the list of zkEVM bridge events to the list of operations preparing them for importing to the database. diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex index 419bf6fb152f..27fed061e7fc 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex @@ -12,13 +12,13 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do import Explorer.Helper, only: [parse_integer: 1] import Indexer.Fetcher.Zkevm.Bridge, - only: [get_logs_all: 3, import_operations: 1, json_rpc_named_arguments: 1, prepare_operations: 3] + only: [get_logs_all: 3, import_operations: 1, prepare_operations: 3] alias Explorer.Chain.Zkevm.{Bridge, Reader} alias Explorer.Repo - alias Indexer.{BoundQueue, Helper} + alias Indexer.Fetcher.RollupL1ReorgMonitor + alias Indexer.Helper - @block_check_interval_range_size 100 @eth_get_logs_range_size 1000 @fetcher_name :zkevm_bridge_l1 @@ -55,6 +55,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do env = Application.get_all_env(:indexer)[__MODULE__] with {:start_block_undefined, false} <- {:start_block_undefined, is_nil(env[:start_block])}, + {:reorg_monitor_started, true} <- {:reorg_monitor_started, !is_nil(Process.whereis(RollupL1ReorgMonitor))}, rpc = env[:rpc], {:rpc_undefined, false} <- {:rpc_undefined, is_nil(rpc)}, {:bridge_contract_address_is_valid, true} <- @@ -63,15 +64,14 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do false <- is_nil(start_block), true <- start_block > 0, {last_l1_block_number, last_l1_transaction_hash} = Reader.last_l1_item(), - json_rpc_named_arguments = json_rpc_named_arguments(rpc), - {:ok, block_check_interval, safe_block} <- get_block_check_interval(json_rpc_named_arguments), + json_rpc_named_arguments = Helper.json_rpc_named_arguments(rpc), + {:ok, block_check_interval, safe_block} <- Helper.get_block_check_interval(json_rpc_named_arguments), {:start_block_valid, true, _, _} <- {:start_block_valid, (start_block <= last_l1_block_number || last_l1_block_number == 0) && start_block <= safe_block, last_l1_block_number, safe_block}, {:ok, last_l1_tx} <- Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)} do - Process.send(self(), :reorg_monitor, []) Process.send(self(), :continue, []) {:noreply, @@ -79,7 +79,6 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do block_check_interval: block_check_interval, bridge_contract: env[:bridge_contract], json_rpc_named_arguments: json_rpc_named_arguments, - reorg_monitor_prev_latest: 0, end_block: safe_block, start_block: max(start_block, last_l1_block_number) }} @@ -88,6 +87,10 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do # the process shouldn't start if the start block is not defined {:stop, :normal, %{}} + {:reorg_monitor_started, false} -> + Logger.error("Cannot start this process as Indexer.Fetcher.RollupL1ReorgMonitor is not started.") + {:stop, :normal, %{}} + {:rpc_undefined, true} -> Logger.error("L1 RPC URL is not defined.") {:stop, :normal, %{}} @@ -122,27 +125,6 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do end end - @impl GenServer - def handle_info( - :reorg_monitor, - %{ - block_check_interval: block_check_interval, - json_rpc_named_arguments: json_rpc_named_arguments, - reorg_monitor_prev_latest: prev_latest - } = state - ) do - {:ok, latest} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) - - if latest < prev_latest do - Logger.warning("Reorg detected: previous latest block ##{prev_latest}, current latest block ##{latest}.") - reorg_block_push(latest) - end - - Process.send_after(self(), :reorg_monitor, block_check_interval) - - {:noreply, %{state | reorg_monitor_prev_latest: latest}} - end - @impl GenServer def handle_info( :continue, @@ -183,7 +165,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do ) end - reorg_block = reorg_block_pop() + reorg_block = RollupL1ReorgMonitor.reorg_block_pop() if !is_nil(reorg_block) && reorg_block > 0 do reorg_handle(reorg_block) @@ -215,56 +197,6 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do {:noreply, state} end - defp get_block_check_interval(json_rpc_named_arguments) do - {last_safe_block, _} = get_safe_block(json_rpc_named_arguments) - - first_block = max(last_safe_block - @block_check_interval_range_size, 1) - - with {:ok, first_block_timestamp} <- - Helper.get_block_timestamp_by_number(first_block, json_rpc_named_arguments, 100_000_000), - {:ok, last_safe_block_timestamp} <- - Helper.get_block_timestamp_by_number(last_safe_block, json_rpc_named_arguments, 100_000_000) do - block_check_interval = - ceil((last_safe_block_timestamp - first_block_timestamp) / (last_safe_block - first_block) * 1000 / 2) - - Logger.info("Block check interval is calculated as #{block_check_interval} ms.") - {:ok, block_check_interval, last_safe_block} - else - {:error, error} -> - {:error, "Failed to calculate block check interval due to #{inspect(error)}"} - end - end - - defp get_safe_block(json_rpc_named_arguments) do - case Helper.get_block_number_by_tag("safe", json_rpc_named_arguments) do - {:ok, safe_block} -> - {safe_block, false} - - {:error, :not_found} -> - {:ok, latest_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) - {latest_block, true} - end - end - - defp reorg_block_pop do - table_name = reorg_table_name(@fetcher_name) - - case BoundQueue.pop_front(reorg_queue_get(table_name)) do - {:ok, {block_number, updated_queue}} -> - :ets.insert(table_name, {:queue, updated_queue}) - block_number - - {:error, :empty} -> - nil - end - end - - defp reorg_block_push(block_number) do - table_name = reorg_table_name(@fetcher_name) - {:ok, updated_queue} = BoundQueue.push_back(reorg_queue_get(table_name), block_number) - :ets.insert(table_name, {:queue, updated_queue}) - end - defp reorg_handle(reorg_block) do {deleted_count, _} = Repo.delete_all(from(b in Bridge, where: b.type == :deposit and b.block_number >= ^reorg_block)) @@ -275,27 +207,4 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do ) end end - - defp reorg_queue_get(table_name) do - if :ets.whereis(table_name) == :undefined do - :ets.new(table_name, [ - :set, - :named_table, - :public, - read_concurrency: true, - write_concurrency: true - ]) - end - - with info when info != :undefined <- :ets.info(table_name), - [{_, value}] <- :ets.lookup(table_name, :queue) do - value - else - _ -> %BoundQueue{} - end - end - - defp reorg_table_name(fetcher_name) do - :"#{fetcher_name}#{:_reorgs}" - end end diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1_tokens.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1_tokens.ex index e341babf09aa..59e6f9890819 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1_tokens.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1_tokens.ex @@ -9,7 +9,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1Tokens do import Ecto.Query alias Explorer.Repo - alias Indexer.BufferedTask + alias Indexer.{BufferedTask, Helper} alias Indexer.Fetcher.Zkevm.{Bridge, BridgeL1} @behaviour BufferedTask @@ -20,7 +20,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1Tokens do @doc false def child_spec([init_options, gen_server_options]) do rpc = Application.get_all_env(:indexer)[BridgeL1][:rpc] - json_rpc_named_arguments = Bridge.json_rpc_named_arguments(rpc) + json_rpc_named_arguments = Helper.json_rpc_named_arguments(rpc) merged_init_opts = defaults() diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex index aa1b55018cd9..c469602be163 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex @@ -12,7 +12,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL2 do import Explorer.Helper, only: [parse_integer: 1] import Indexer.Fetcher.Zkevm.Bridge, - only: [get_logs_all: 3, import_operations: 1, json_rpc_named_arguments: 1, prepare_operations: 3] + only: [get_logs_all: 3, import_operations: 1, prepare_operations: 3] alias Explorer.Chain.Zkevm.{Bridge, Reader} alias Explorer.Repo @@ -75,7 +75,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL2 do %{ bridge_contract: env[:bridge_contract], json_rpc_named_arguments: json_rpc_named_arguments, - json_rpc_named_arguments_l1: json_rpc_named_arguments(rpc_l1), + json_rpc_named_arguments_l1: Helper.json_rpc_named_arguments(rpc_l1), end_block: latest_block, start_block: max(start_block, last_l2_block_number) }} diff --git a/apps/indexer/lib/indexer/helper.ex b/apps/indexer/lib/indexer/helper.ex index 3b1cb18b61cf..111c518d9676 100644 --- a/apps/indexer/lib/indexer/helper.ex +++ b/apps/indexer/lib/indexer/helper.ex @@ -17,6 +17,8 @@ defmodule Indexer.Helper do alias EthereumJSONRPC.Blocks alias Explorer.Chain.Hash + @block_check_interval_range_size 100 + @doc """ Checks whether the given Ethereum address looks correct. The address should begin with 0x prefix and then contain 40 hexadecimal digits (can be in mixed case). @@ -55,6 +57,45 @@ defmodule Indexer.Helper do end end + @doc """ + Calculates average block time in milliseconds (based on the latest 100 blocks) divided by 2. + Sends corresponding requests to the RPC node. + Returns a tuple {:ok, block_check_interval, last_safe_block} + where `last_safe_block` is the number of the recent `safe` or `latest` block (depending on which one is available). + Returns {:error, description} in case of error. + """ + @spec get_block_check_interval(list()) :: {:ok, non_neg_integer(), non_neg_integer()} | {:error, any()} + def get_block_check_interval(json_rpc_named_arguments) do + {last_safe_block, _} = get_safe_block(json_rpc_named_arguments) + + first_block = max(last_safe_block - @block_check_interval_range_size, 1) + + with {:ok, first_block_timestamp} <- + get_block_timestamp_by_number(first_block, json_rpc_named_arguments, 100_000_000), + {:ok, last_safe_block_timestamp} <- + get_block_timestamp_by_number(last_safe_block, json_rpc_named_arguments, 100_000_000) do + block_check_interval = + ceil((last_safe_block_timestamp - first_block_timestamp) / (last_safe_block - first_block) * 1000 / 2) + + Logger.info("Block check interval is calculated as #{block_check_interval} ms.") + {:ok, block_check_interval, last_safe_block} + else + {:error, error} -> + {:error, "Failed to calculate block check interval due to #{inspect(error)}"} + end + end + + defp get_safe_block(json_rpc_named_arguments) do + case get_block_number_by_tag("safe", json_rpc_named_arguments) do + {:ok, safe_block} -> + {safe_block, false} + + {:error, :not_found} -> + {:ok, latest_block} = get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + {latest_block, true} + end + end + @doc """ Fetches block number by its tag (e.g. `latest` or `safe`) using RPC request. Performs a specified number of retries (up to) if the first attempt returns error. @@ -87,6 +128,25 @@ defmodule Indexer.Helper do repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) end + @doc """ + Forms JSON RPC named arguments for the given RPC URL. + """ + @spec json_rpc_named_arguments(binary()) :: list() + def json_rpc_named_arguments(rpc_url) do + [ + transport: EthereumJSONRPC.HTTP, + transport_options: [ + http: EthereumJSONRPC.HTTP.HTTPoison, + url: rpc_url, + http_options: [ + recv_timeout: :timer.minutes(10), + timeout: :timer.minutes(10), + hackney: [pool: :ethereum_jsonrpc] + ] + ] + ] + end + @doc """ Prints a log of progress when handling something splitted to block chunks. """ diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index 10a9de790b8b..d8f32eed81fa 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -32,7 +32,6 @@ defmodule Indexer.Supervisor do InternalTransaction, PendingBlockOperationsSanitizer, PendingTransaction, - PolygonEdge, ReplacedTransaction, RootstockData, Token, @@ -132,7 +131,7 @@ defmodule Indexer.Supervisor do {TokenUpdater.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, {ReplacedTransaction.Supervisor, [[memory_monitor: memory_monitor]]}, - configure(PolygonEdge.Supervisor, [[memory_monitor: memory_monitor]]), + {Indexer.Fetcher.RollupL1ReorgMonitor.Supervisor, [[memory_monitor: memory_monitor]]}, configure(Indexer.Fetcher.PolygonEdge.Deposit.Supervisor, [[memory_monitor: memory_monitor]]), configure(Indexer.Fetcher.PolygonEdge.DepositExecute.Supervisor, [ [memory_monitor: memory_monitor, json_rpc_named_arguments: json_rpc_named_arguments] diff --git a/apps/indexer/lib/indexer/transform/zkevm/bridge.ex b/apps/indexer/lib/indexer/transform/zkevm/bridge.ex index ae9f0f58e7ba..2d23fcb2e090 100644 --- a/apps/indexer/lib/indexer/transform/zkevm/bridge.ex +++ b/apps/indexer/lib/indexer/transform/zkevm/bridge.ex @@ -6,7 +6,7 @@ defmodule Indexer.Transform.Zkevm.Bridge do require Logger import Indexer.Fetcher.Zkevm.Bridge, - only: [filter_bridge_events: 2, json_rpc_named_arguments: 1, prepare_operations: 4] + only: [filter_bridge_events: 2, prepare_operations: 4] alias Indexer.Fetcher.Zkevm.{BridgeL1, BridgeL2} alias Indexer.Helper @@ -35,7 +35,7 @@ defmodule Indexer.Transform.Zkevm.Bridge do Helper.log_blocks_chunk_handling(start_block, end_block, start_block, end_block, nil, "L2") - json_rpc_named_arguments_l1 = json_rpc_named_arguments(rpc_l1) + json_rpc_named_arguments_l1 = Helper.json_rpc_named_arguments(rpc_l1) block_to_timestamp = Enum.reduce(blocks, %{}, fn block, acc -> Map.put(acc, block.number, block.timestamp) end) diff --git a/config/runtime.exs b/config/runtime.exs index 625b8aa44f28..78b5e3f78230 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -649,8 +649,6 @@ config :indexer, Indexer.Fetcher.Withdrawal.Supervisor, config :indexer, Indexer.Fetcher.Withdrawal, first_block: System.get_env("WITHDRAWALS_FIRST_BLOCK") -config :indexer, Indexer.Fetcher.PolygonEdge.Supervisor, enabled: ConfigHelper.chain_type() == "polygon_edge" - config :indexer, Indexer.Fetcher.PolygonEdge.Deposit.Supervisor, enabled: ConfigHelper.chain_type() == "polygon_edge" config :indexer, Indexer.Fetcher.PolygonEdge.DepositExecute.Supervisor, From 033ffb5132fa2adf7cc9fbdc6b89cb9586067942 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Mon, 5 Feb 2024 15:20:57 +0300 Subject: [PATCH 447/607] Update spelling --- cspell.json | 1 + 1 file changed, 1 insertion(+) diff --git a/cspell.json b/cspell.json index 38fc3b595e07..8a9fc92c989a 100644 --- a/cspell.json +++ b/cspell.json @@ -397,6 +397,7 @@ "retryable", "returnaddress", "reuseaddr", + "rollup", "RPC's", "RPCs", "safelow", From e57ce965121ae87c4394d8ff6b13d85337d62cf3 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Tue, 6 Feb 2024 14:52:13 +0300 Subject: [PATCH 448/607] Allow using Indexer.Fetcher.RollupL1ReorgMonitor for more than one module of the same rollup --- .../fetcher/rollup_l1_reorg_monitor.ex | 81 ++++++++++--------- .../lib/indexer/fetcher/shibarium/l1.ex | 2 +- .../lib/indexer/fetcher/zkevm/bridge_l1.ex | 2 +- 3 files changed, 47 insertions(+), 38 deletions(-) diff --git a/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex b/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex index bb1cdc7b6eeb..602d664c8a7d 100644 --- a/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex +++ b/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex @@ -32,6 +32,8 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do Logger.metadata(fetcher: @fetcher_name) modules_can_use_reorg_monitor = [ + Indexer.Fetcher.PolygonEdge.Deposit, + Indexer.Fetcher.PolygonEdge.WithdrawalExit, Indexer.Fetcher.Shibarium.L1, Indexer.Fetcher.Zkevm.BridgeL1 ] @@ -39,35 +41,41 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do modules_using_reorg_monitor = modules_can_use_reorg_monitor |> Enum.reject(fn module -> - is_nil(Application.get_all_env(:indexer)[module][:start_block]) + module_config = Application.get_all_env(:indexer)[module] + is_nil(module_config[:start_block]) and is_nil(module_config[:start_block_l1]) end) - cond do - Enum.count(modules_using_reorg_monitor) > 1 -> - Logger.error("#{__MODULE__} cannot work for more than one rollup module. Please, check config.") - :ignore - - Enum.empty?(modules_using_reorg_monitor) -> - # don't start reorg monitor as there is no module which would use it - :ignore - - true -> - module_using_reorg_monitor = Enum.at(modules_using_reorg_monitor, 0) - - l1_rpc = Application.get_all_env(:indexer)[module_using_reorg_monitor][:rpc] - - json_rpc_named_arguments = Helper.json_rpc_named_arguments(l1_rpc) - - {:ok, block_check_interval, _} = Helper.get_block_check_interval(json_rpc_named_arguments) - - Process.send(self(), :reorg_monitor, []) - - {:ok, - %{ - block_check_interval: block_check_interval, - json_rpc_named_arguments: json_rpc_named_arguments, - prev_latest: 0 - }} + if Enum.empty?(modules_using_reorg_monitor) do + # don't start reorg monitor as there is no module which would use it + :ignore + else + # As there cannot be different modules for different rollups at the same time, + # it's correct to only get the first item of the list. + # For example, Indexer.Fetcher.PolygonEdge.Deposit and Indexer.Fetcher.PolygonEdge.WithdrawalExit can be in the list + # because they are for the same rollup, but Indexer.Fetcher.Shibarium.L1 and Indexer.Fetcher.Zkevm.BridgeL1 cannot (as they are for different rollups). + module_using_reorg_monitor = Enum.at(modules_using_reorg_monitor, 0) + + l1_rpc = + if Enum.member?([Indexer.Fetcher.PolygonEdge.Deposit, Indexer.Fetcher.PolygonEdge.WithdrawalExit], module_using_reorg_monitor) do + # there can be more than one PolygonEdge.* modules, so we get the common L1 RPC URL for them from Indexer.Fetcher.PolygonEdge + Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonEdge][:polygon_edge_l1_rpc] + else + Application.get_all_env(:indexer)[module_using_reorg_monitor][:rpc] + end + + json_rpc_named_arguments = Helper.json_rpc_named_arguments(l1_rpc) + + {:ok, block_check_interval, _} = Helper.get_block_check_interval(json_rpc_named_arguments) + + Process.send(self(), :reorg_monitor, []) + + {:ok, + %{ + block_check_interval: block_check_interval, + json_rpc_named_arguments: json_rpc_named_arguments, + modules: modules_using_reorg_monitor, + prev_latest: 0 + }} end end @@ -77,6 +85,7 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do %{ block_check_interval: block_check_interval, json_rpc_named_arguments: json_rpc_named_arguments, + modules: modules, prev_latest: prev_latest } = state ) do @@ -84,7 +93,7 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do if latest < prev_latest do Logger.warning("Reorg detected: previous latest block ##{prev_latest}, current latest block ##{latest}.") - reorg_block_push(latest) + Enum.each(modules, &reorg_block_push(latest, &1)) end Process.send_after(self(), :reorg_monitor, block_check_interval) @@ -93,12 +102,12 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do end @doc """ - Pops the number of reorg block from the front of the queue. + Pops the number of reorg block from the front of the queue for the specified rollup module. Returns `nil` if the reorg queue is empty. """ - @spec reorg_block_pop() :: non_neg_integer() | nil - def reorg_block_pop do - table_name = reorg_table_name(@fetcher_name) + @spec reorg_block_pop(module()) :: non_neg_integer() | nil + def reorg_block_pop(module) do + table_name = reorg_table_name(module) case BoundQueue.pop_front(reorg_queue_get(table_name)) do {:ok, {block_number, updated_queue}} -> @@ -110,8 +119,8 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do end end - defp reorg_block_push(block_number) do - table_name = reorg_table_name(@fetcher_name) + defp reorg_block_push(block_number, module) do + table_name = reorg_table_name(module) {:ok, updated_queue} = BoundQueue.push_back(reorg_queue_get(table_name), block_number) :ets.insert(table_name, {:queue, updated_queue}) end @@ -135,7 +144,7 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do end end - defp reorg_table_name(fetcher_name) do - :"#{fetcher_name}#{:_reorgs}" + defp reorg_table_name(module) do + :"#{module}#{:_reorgs}" end end diff --git a/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex b/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex index 841dfafd8590..39b099ff1ed9 100644 --- a/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex +++ b/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex @@ -273,7 +273,7 @@ defmodule Indexer.Fetcher.Shibarium.L1 do ) end - reorg_block = RollupL1ReorgMonitor.reorg_block_pop() + reorg_block = RollupL1ReorgMonitor.reorg_block_pop(__MODULE__) if !is_nil(reorg_block) && reorg_block > 0 do reorg_handle(reorg_block) diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex index 27fed061e7fc..c0411c0dcd56 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex @@ -165,7 +165,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do ) end - reorg_block = RollupL1ReorgMonitor.reorg_block_pop() + reorg_block = RollupL1ReorgMonitor.reorg_block_pop(__MODULE__) if !is_nil(reorg_block) && reorg_block > 0 do reorg_handle(reorg_block) From 816b5d1844a174d66a2cff46f5aac7a22d67c5ba Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Tue, 6 Feb 2024 14:56:29 +0300 Subject: [PATCH 449/607] mix format and update spelling --- apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex | 5 ++++- cspell.json | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex b/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex index 602d664c8a7d..6499ccab1ef3 100644 --- a/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex +++ b/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex @@ -56,7 +56,10 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do module_using_reorg_monitor = Enum.at(modules_using_reorg_monitor, 0) l1_rpc = - if Enum.member?([Indexer.Fetcher.PolygonEdge.Deposit, Indexer.Fetcher.PolygonEdge.WithdrawalExit], module_using_reorg_monitor) do + if Enum.member?( + [Indexer.Fetcher.PolygonEdge.Deposit, Indexer.Fetcher.PolygonEdge.WithdrawalExit], + module_using_reorg_monitor + ) do # there can be more than one PolygonEdge.* modules, so we get the common L1 RPC URL for them from Indexer.Fetcher.PolygonEdge Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonEdge][:polygon_edge_l1_rpc] else diff --git a/cspell.json b/cspell.json index 8a9fc92c989a..0fe81962f174 100644 --- a/cspell.json +++ b/cspell.json @@ -398,6 +398,7 @@ "returnaddress", "reuseaddr", "rollup", + "rollups", "RPC's", "RPCs", "safelow", From 0ecf3fe1732abc291586d3850d304b61ad826d19 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Thu, 8 Feb 2024 13:17:03 +0300 Subject: [PATCH 450/607] Use timestamp_to_datetime --- apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex index 1180e0771c8d..fdf34258231f 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex +++ b/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex @@ -10,7 +10,8 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do integer_to_quantity: 1, json_rpc: 2, quantity_to_integer: 1, - request: 1 + request: 1, + timestamp_to_datetime: 1 ] import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] @@ -215,7 +216,7 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do |> Helper.get_blocks_by_events(json_rpc_named_arguments, 100_000_000) |> Enum.reduce(%{}, fn block, acc -> block_number = quantity_to_integer(Map.get(block, "number")) - {:ok, timestamp} = DateTime.from_unix(quantity_to_integer(Map.get(block, "timestamp"))) + timestamp = timestamp_to_datetime(Map.get(block, "timestamp")) Map.put(acc, block_number, timestamp) end) end From ec76391b2b5e89847f8a38224da0f353d4ea5240 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Thu, 8 Feb 2024 14:36:43 +0300 Subject: [PATCH 451/607] Split RPC requests for blocks into chunks --- apps/indexer/lib/indexer/helper.ex | 49 +++++++++++++++++------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/apps/indexer/lib/indexer/helper.ex b/apps/indexer/lib/indexer/helper.ex index 111c518d9676..d8d3a650cb23 100644 --- a/apps/indexer/lib/indexer/helper.ex +++ b/apps/indexer/lib/indexer/helper.ex @@ -18,6 +18,7 @@ defmodule Indexer.Helper do alias Explorer.Chain.Hash @block_check_interval_range_size 100 + @block_by_number_chunk_size 50 @doc """ Checks whether the given Ethereum address looks correct. @@ -228,29 +229,35 @@ defmodule Indexer.Helper do """ @spec get_blocks_by_events(list(), list(), non_neg_integer()) :: list() def get_blocks_by_events(events, json_rpc_named_arguments, retries) do - request = - events - |> Enum.reduce(%{}, fn event, acc -> - block_number = - if is_map(event) do - event.block_number - else - event["blockNumber"] - end - - Map.put(acc, block_number, 0) - end) - |> Stream.map(fn {block_number, _} -> %{number: block_number} end) - |> Stream.with_index() - |> Enum.into(%{}, fn {params, id} -> {id, params} end) - |> Blocks.requests(&ByNumber.request(&1, false, false)) + events + |> Enum.reduce(%{}, fn event, acc -> + block_number = + if is_map(event) do + event.block_number + else + event["blockNumber"] + end - error_message = &"Cannot fetch blocks with batch request. Error: #{inspect(&1)}. Request: #{inspect(request)}" + Map.put(acc, block_number, 0) + end) + |> Stream.map(fn {block_number, _} -> %{number: block_number} end) + |> Stream.with_index() + |> Enum.into(%{}, fn {params, id} -> {id, params} end) + |> Blocks.requests(&ByNumber.request(&1, false, false)) + |> Enum.chunk_every(@block_by_number_chunk_size) + |> Enum.reduce([], fn current_requests, results_acc -> + error_message = + &"Cannot fetch blocks with batch request. Error: #{inspect(&1)}. Request: #{inspect(current_requests)}" + + # credo:disable-for-lines:3 Credo.Check.Refactor.Nesting + results = + case repeated_call(&json_rpc/2, [current_requests, json_rpc_named_arguments], error_message, retries) do + {:ok, results} -> Enum.map(results, fn %{result: result} -> result end) + {:error, _} -> [] + end - case repeated_call(&json_rpc/2, [request, json_rpc_named_arguments], error_message, retries) do - {:ok, results} -> Enum.map(results, fn %{result: result} -> result end) - {:error, _} -> [] - end + results_acc ++ results + end) end @doc """ From 121092acfbf17293abf985693f76bcecce7701ae Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Tue, 13 Feb 2024 13:27:00 +0300 Subject: [PATCH 452/607] Refactor Indexer.Block.Fetcher --- apps/indexer/lib/indexer/block/fetcher.ex | 65 ++++++++++++++--------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index fb8d24f5210b..65caef9a4d13 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -212,34 +212,17 @@ defmodule Indexer.Block.Fetcher do withdrawals: %{params: withdrawals_params}, token_instances: %{params: token_instances} }, - import_options = - (case Application.get_env(:explorer, :chain_type) do - "ethereum" -> - basic_import_options - |> Map.put_new(:beacon_blob_transactions, %{ - params: transactions_with_receipts |> Enum.filter(&Map.has_key?(&1, :max_fee_per_blob_gas)) - }) - - "polygon_edge" -> - basic_import_options - |> Map.put_new(:polygon_edge_withdrawals, %{params: polygon_edge_withdrawals}) - |> Map.put_new(:polygon_edge_deposit_executes, %{params: polygon_edge_deposit_executes}) - - "polygon_zkevm" -> - basic_import_options - |> Map.put_new(:zkevm_bridge_operations, %{params: zkevm_bridge_operations}) - - "shibarium" -> - basic_import_options - |> Map.put_new(:shibarium_bridge_operations, %{params: shibarium_bridge_operations}) - - _ -> - basic_import_options - end), + chain_type_import_options = %{ + transactions_with_receipts: transactions_with_receipts, + polygon_edge_withdrawals: polygon_edge_withdrawals, + polygon_edge_deposit_executes: polygon_edge_deposit_executes, + zkevm_bridge_operations: zkevm_bridge_operations, + shibarium_bridge_operations: shibarium_bridge_operations + }, {:ok, inserted} <- __MODULE__.import( state, - import_options + import_options(basic_import_options, chain_type_import_options) ), {:tx_actions, {:ok, inserted_tx_actions}} <- {:tx_actions, @@ -262,6 +245,38 @@ defmodule Indexer.Block.Fetcher do end end + defp import_options(basic_import_options, %{ + transactions_with_receipts: transactions_with_receipts, + polygon_edge_withdrawals: polygon_edge_withdrawals, + polygon_edge_deposit_executes: polygon_edge_deposit_executes, + zkevm_bridge_operations: zkevm_bridge_operations, + shibarium_bridge_operations: shibarium_bridge_operations + }) do + case Application.get_env(:explorer, :chain_type) do + "ethereum" -> + basic_import_options + |> Map.put_new(:beacon_blob_transactions, %{ + params: transactions_with_receipts |> Enum.filter(&Map.has_key?(&1, :max_fee_per_blob_gas)) + }) + + "polygon_edge" -> + basic_import_options + |> Map.put_new(:polygon_edge_withdrawals, %{params: polygon_edge_withdrawals}) + |> Map.put_new(:polygon_edge_deposit_executes, %{params: polygon_edge_deposit_executes}) + + "polygon_zkevm" -> + basic_import_options + |> Map.put_new(:zkevm_bridge_operations, %{params: zkevm_bridge_operations}) + + "shibarium" -> + basic_import_options + |> Map.put_new(:shibarium_bridge_operations, %{params: shibarium_bridge_operations}) + + _ -> + basic_import_options + end + end + defp update_block_cache([]), do: :ok defp update_block_cache(blocks) when is_list(blocks) do From b19e196d3096d3f5d9e6656d1b4327e90d30b619 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Tue, 13 Feb 2024 13:37:31 +0300 Subject: [PATCH 453/607] Reset GA cache --- .github/workflows/config.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index bdbc610ff3db..145a881b71b0 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -73,7 +73,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps- @@ -131,7 +131,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -155,7 +155,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -184,7 +184,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -228,7 +228,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -254,7 +254,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -283,7 +283,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -331,7 +331,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -377,7 +377,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -439,7 +439,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -499,7 +499,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -570,7 +570,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -638,7 +638,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" From 98e80b3941e485398860793c23cd7ec17af90b00 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Tue, 13 Feb 2024 13:53:21 +0300 Subject: [PATCH 454/607] Update chain.ex --- apps/block_scout_web/lib/block_scout_web/chain.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index d4d6f3f20f82..2ca930067689 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -38,8 +38,7 @@ defmodule BlockScoutWeb.Chain do Transaction, Transaction.StateChange, UserOperation, - Wei, - Withdrawal + Wei } alias Explorer.Chain.Zkevm.TransactionBatch From 357a7d1a83adc33fa32a4df5d899a75ee80337d9 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:02:59 +0300 Subject: [PATCH 455/607] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a01f0e38d9dd..4d54ed8a84ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - [#9282](https://github.com/blockscout/blockscout/pull/9282) - Add `license_type` to smart contracts - [#9202](https://github.com/blockscout/blockscout/pull/9202) - Add base and priority fee to gas oracle response - [#9168](https://github.com/blockscout/blockscout/pull/9168) - Support EIP4844 blobs indexing & API +- [#9098](https://github.com/blockscout/blockscout/pull/9098) - Polygon zkEVM Bridge indexer and API v2 extension ### Fixes @@ -117,7 +118,6 @@ - [#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 -- [#9098](https://github.com/blockscout/blockscout/pull/9098) - Polygon zkEVM Bridge indexer and API v2 extension ### Fixes From e69c87e7549eb7407773ce754d72543fcdc36aac Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Wed, 14 Feb 2024 05:25:32 -0500 Subject: [PATCH 456/607] Optimize addresses preloads in account abstraction proxy (#9377) * feat: batch address selects and preloads * fix: review comments * chore: changelog --- CHANGELOG.md | 1 + .../proxy/account_abstraction_controller.ex | 83 +++++++++++-------- apps/explorer/lib/explorer/chain.ex | 12 ++- 3 files changed, 57 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bed3db718e48..51234b5c5b82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Fixes +- [#9377](https://github.com/blockscout/blockscout/pull/9377) - Speed up account abstraction proxy - [#9356](https://github.com/blockscout/blockscout/pull/9356) - Remove ERC-1155 logs params from coin balances params - [#9346](https://github.com/blockscout/blockscout/pull/9346) - Process integer balance in genesis.json - [#9317](https://github.com/blockscout/blockscout/pull/9317) - Include null gas price txs in fee calculations diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex index 398d946b49d9..69bd9b9d4420 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex @@ -121,55 +121,68 @@ defmodule BlockScoutWeb.API.V2.Proxy.AccountAbstractionController do end defp extended_info(response) do + address_hashes = + response + |> collect_address_hashes() + |> Chain.hashes_to_addresses( + necessity_by_association: %{ + :names => :optional, + :smart_contract => :optional + }, + api?: true + ) + |> Enum.into(%{}, &{&1.hash, Helper.address_with_info(&1, nil)}) + + response |> replace_address_hashes(address_hashes) + end + + defp collect_address_hashes(response) do + address_hash_strings = + case response do + %{"items" => items} -> + @address_fields |> Enum.flat_map(fn field -> Enum.map(items, & &1[field]) end) + + item -> + @address_fields |> Enum.map(&item[&1]) + end + + address_hash_strings + |> Enum.filter(&(!is_nil(&1))) + |> Enum.uniq() + |> Enum.map(fn hash_string -> + case Chain.string_to_address_hash(hash_string) do + {:ok, hash} -> hash + _ -> nil + end + end) + |> Enum.filter(&(!is_nil(&1))) + end + + defp replace_address_hashes(response, addresses) do case response do %{"items" => items} -> - extended_items = - Enum.map(items, fn response_item -> - add_address_extended_info(response_item) - end) + extended_items = items |> Enum.map(&add_address_extended_info(&1, addresses)) - response - |> Map.put("items", extended_items) + response |> Map.put("items", extended_items) - _ -> - add_address_extended_info(response) + item -> + add_address_extended_info(item, addresses) end end - defp add_address_extended_info(response) do + defp add_address_extended_info(response, addresses) do @address_fields |> Enum.reduce(response, fn address_output_field, output_response -> - if Map.has_key?(output_response, address_output_field) do - output_response - |> Map.replace( - address_output_field, - address_info_from_hash_string(Map.get(output_response, address_output_field)) - ) + with true <- Map.has_key?(output_response, address_output_field), + {:ok, address_hash} <- output_response |> Map.get(address_output_field) |> Chain.string_to_address_hash(), + true <- Map.has_key?(addresses, address_hash) do + output_response |> Map.replace(address_output_field, Map.get(addresses, address_hash)) else - output_response + _ -> output_response end end) end - defp address_info_from_hash_string(address_hash_string) do - with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), - {:ok, address} <- - Chain.hash_to_address( - address_hash, - [ - necessity_by_association: %{ - :names => :optional, - :smart_contract => :optional - } - ], - false - ) do - Helper.address_with_info(address, address_hash_string) - else - _ -> address_hash_string - end - end - defp process_response(response, conn) do case response do {:error, :disabled} -> diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 423ee0dc8761..cedc5225b57f 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1089,11 +1089,13 @@ defmodule Explorer.Chain do @doc """ Converts list of `t:Explorer.Chain.Address.t/0` `hash` to the `t:Explorer.Chain.Address.t/0` with that `hash`. - Returns `[%Explorer.Chain.Address{}]}` if found + Returns `[%Explorer.Chain.Address{}]` if found """ - @spec hashes_to_addresses([Hash.Address.t()]) :: [Address.t()] - def hashes_to_addresses(hashes) when is_list(hashes) do + @spec hashes_to_addresses([Hash.Address.t()], [necessity_by_association_option | api?]) :: [Address.t()] + def hashes_to_addresses(hashes, options \\ []) when is_list(hashes) do + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + query = from( address in Address, @@ -1102,7 +1104,9 @@ defmodule Explorer.Chain do order_by: fragment("array_position(?, ?)", type(^hashes, {:array, Hash.Address}), address.hash) ) - Repo.all(query) + query + |> join_associations(necessity_by_association) + |> select_repo(options).all() end @doc """ From 59cec00a41c12038d60bb450d00e10f63e3daa64 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 9 Feb 2024 16:58:34 +0300 Subject: [PATCH 457/607] Filter empty values in token update --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/chain.ex | 8 ++- .../lib/explorer/chain/bridged_token.ex | 66 +++++++------------ 3 files changed, 31 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51234b5c5b82..02d8bbdbada1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### Fixes - [#9377](https://github.com/blockscout/blockscout/pull/9377) - Speed up account abstraction proxy +- [#9371](https://github.com/blockscout/blockscout/pull/9371) - Filter empty values before token update - [#9356](https://github.com/blockscout/blockscout/pull/9356) - Remove ERC-1155 logs params from coin balances params - [#9346](https://github.com/blockscout/blockscout/pull/9346) - Process integer balance in genesis.json - [#9317](https://github.com/blockscout/blockscout/pull/9317) - Include null gas price txs in fee calculations diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index cedc5225b57f..f15ed25a86e4 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -3718,8 +3718,12 @@ defmodule Explorer.Chain do """ @spec update_token(Token.t(), map()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()} def update_token(%Token{contract_address_hash: address_hash} = token, params \\ %{}) do - token_changeset = Token.changeset(token, Map.put(params, :updated_at, DateTime.utc_now())) - address_name_changeset = Address.Name.changeset(%Address.Name{}, Map.put(params, :address_hash, address_hash)) + filtered_params = for({key, value} <- params, value !== "" && !is_nil(value), do: {key, value}) |> Enum.into(%{}) + + token_changeset = Token.changeset(token, Map.put(filtered_params, :updated_at, DateTime.utc_now())) + + address_name_changeset = + Address.Name.changeset(%Address.Name{}, Map.put(filtered_params, :address_hash, address_hash)) stale_error_field = :contract_address_hash stale_error_message = "is up to date" diff --git a/apps/explorer/lib/explorer/chain/bridged_token.ex b/apps/explorer/lib/explorer/chain/bridged_token.ex index e53b0e1e9887..4717d610b9ae 100644 --- a/apps/explorer/lib/explorer/chain/bridged_token.ex +++ b/apps/explorer/lib/explorer/chain/bridged_token.ex @@ -31,6 +31,18 @@ defmodule Explorer.Chain.BridgedToken do require Logger @default_paging_options %PagingOptions{page_size: 50} + # keccak 256 from name() + @name_signature "0x06fdde03" + # 95d89b41 = keccak256(symbol()) + @symbol_signature "0x95d89b41" + # keccak 256 from decimals() + @decimals_signature "0x313ce567" + # keccak 256 from totalSupply() + @total_supply_signature "0x18160ddd" + # keccak 256 from token0() + @token0_signature "0x0dfe1681" + # keccak 256 from token1() + @token1_signature "0xd21220a7" @derive {Poison.Encoder, except: [ @@ -557,24 +569,12 @@ defmodule Explorer.Chain.BridgedToken do end defp sushiswap_custom_metadata(foreign_token_address_hash, eth_call_foreign_json_rpc_named_arguments) do - # keccak 256 from token0() - token0_signature = "0x0dfe1681" - - # keccak 256 from token1() - token1_signature = "0xd21220a7" - - # keccak 256 from name() - name_signature = "0x06fdde03" - - # keccak 256 from symbol() - symbol_signature = "0x95d89b41" - with {:ok, "0x" <> token0_encoded} <- - token0_signature + @token0_signature |> Contract.eth_call_request(foreign_token_address_hash, 1, nil, nil) |> json_rpc(eth_call_foreign_json_rpc_named_arguments), {:ok, "0x" <> token1_encoded} <- - token1_signature + @token1_signature |> Contract.eth_call_request(foreign_token_address_hash, 2, nil, nil) |> json_rpc(eth_call_foreign_json_rpc_named_arguments), token0_hash <- parse_contract_response(token0_encoded, :address), @@ -584,19 +584,19 @@ defmodule Explorer.Chain.BridgedToken do token0_hash_str <- "0x" <> Base.encode16(token0_hash, case: :lower), token1_hash_str <- "0x" <> Base.encode16(token1_hash, case: :lower), {:ok, "0x" <> token0_name_encoded} <- - name_signature + @name_signature |> Contract.eth_call_request(token0_hash_str, 1, nil, nil) |> json_rpc(eth_call_foreign_json_rpc_named_arguments), {:ok, "0x" <> token1_name_encoded} <- - name_signature + @name_signature |> Contract.eth_call_request(token1_hash_str, 2, nil, nil) |> json_rpc(eth_call_foreign_json_rpc_named_arguments), {:ok, "0x" <> token0_symbol_encoded} <- - symbol_signature + @symbol_signature |> Contract.eth_call_request(token0_hash_str, 1, nil, nil) |> json_rpc(eth_call_foreign_json_rpc_named_arguments), {:ok, "0x" <> token1_symbol_encoded} <- - symbol_signature + @symbol_signature |> Contract.eth_call_request(token1_hash_str, 2, nil, nil) |> json_rpc(eth_call_foreign_json_rpc_named_arguments) do token0_name = parse_contract_response(token0_name_encoded, :string, {:bytes, 32}) @@ -650,21 +650,12 @@ defmodule Explorer.Chain.BridgedToken do # keccak 256 from getReserves() get_reserves_signature = "0x0902f1ac" - # keccak 256 from token0() - token0_signature = "0x0dfe1681" - - # keccak 256 from token1() - token1_signature = "0xd21220a7" - - # keccak 256 from totalSupply() - total_supply_signature = "0x18160ddd" - with {:ok, "0x" <> get_reserves_encoded} <- get_reserves_signature |> Contract.eth_call_request(foreign_token_address_hash, 1, nil, nil) |> json_rpc(eth_call_foreign_json_rpc_named_arguments), {:ok, "0x" <> home_token_total_supply_encoded} <- - total_supply_signature + @total_supply_signature |> Contract.eth_call_request(home_token_contract_address_hash, 1, nil, nil) |> json_rpc(json_rpc_named_arguments), [reserve0, reserve1, _] <- @@ -672,7 +663,7 @@ defmodule Explorer.Chain.BridgedToken do {:ok, token0_cap_usd} <- get_lp_token_cap( home_token_total_supply_encoded, - token0_signature, + @token0_signature, reserve0, foreign_token_address_hash, eth_call_foreign_json_rpc_named_arguments @@ -680,7 +671,7 @@ defmodule Explorer.Chain.BridgedToken do {:ok, token1_cap_usd} <- get_lp_token_cap( home_token_total_supply_encoded, - token1_signature, + @token1_signature, reserve1, foreign_token_address_hash, eth_call_foreign_json_rpc_named_arguments @@ -700,12 +691,6 @@ defmodule Explorer.Chain.BridgedToken do foreign_token_address_hash, eth_call_foreign_json_rpc_named_arguments ) do - # keccak 256 from decimals() - decimals_signature = "0x313ce567" - - # keccak 256 from totalSupply() - total_supply_signature = "0x18160ddd" - home_token_total_supply = home_token_total_supply_encoded |> parse_contract_response({:uint, 256}) @@ -719,11 +704,11 @@ defmodule Explorer.Chain.BridgedToken do false <- is_nil(token_hash), token_hash_str <- "0x" <> Base.encode16(token_hash, case: :lower), {:ok, "0x" <> token_decimals_encoded} <- - decimals_signature + @decimals_signature |> Contract.eth_call_request(token_hash_str, 1, nil, nil) |> json_rpc(eth_call_foreign_json_rpc_named_arguments), {:ok, "0x" <> foreign_token_total_supply_encoded} <- - total_supply_signature + @total_supply_signature |> Contract.eth_call_request(foreign_token_address_hash, 1, nil, nil) |> json_rpc(eth_call_foreign_json_rpc_named_arguments) do token_decimals = parse_contract_response(token_decimals_encoded, {:uint, 256}) @@ -862,10 +847,7 @@ defmodule Explorer.Chain.BridgedToken do balancer_token_hash = "0x" <> balancer_token_hash_without_0x - # 95d89b41 = keccak256(symbol()) - symbol_signature = "0x95d89b41" - - case symbol_signature + case @symbol_signature |> Contract.eth_call_request(balancer_token_hash, 1, nil, nil) |> json_rpc(eth_call_foreign_json_rpc_named_arguments) do {:ok, "0x" <> symbol_encoded} -> From d8fd9b2d20bf2a6813ca94223b12ebea9d11ca73 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Wed, 14 Feb 2024 13:51:14 +0300 Subject: [PATCH 458/607] Rename Zkevm to PolygonZkevm --- .../lib/block_scout_web/api_router.ex | 20 +++++++------- .../lib/block_scout_web/chain.ex | 2 +- ... polygon_zkevm_confirmed_batch_channel.ex} | 6 ++--- .../channels/user_socket_v2.ex | 2 +- ...troller.ex => polygon_zkevm_controller.ex} | 4 +-- .../api/v2/transaction_controller.ex | 6 ++--- .../{zkevm_view.ex => polygon_zkevm_view.ex} | 4 +-- apps/block_scout_web/mix.exs | 2 +- .../batch_transactions.ex | 16 ++++++------ .../bridge_l1_tokens.ex | 14 +++++----- .../bridge_operations.ex | 26 +++++++++---------- .../lifecycle_transactions.ex | 16 ++++++------ .../transaction_batches.ex | 16 ++++++------ .../chain/import/stage/block_referencing.ex | 10 +++---- .../batch_transaction.ex | 6 ++--- .../chain/{zkevm => polygon_zkevm}/bridge.ex | 6 ++--- .../bridge_l1_token.ex | 4 +-- .../lifecycle_transaction.ex | 6 ++--- .../chain/{zkevm => polygon_zkevm}/reader.ex | 24 ++++++++--------- .../transaction_batch.ex | 6 ++--- .../lib/explorer/chain/transaction.ex | 2 +- .../20231010093238_add_bridge_tables.exs | 20 ++++++++------ apps/indexer/lib/indexer/block/fetcher.ex | 26 +++++++++---------- .../lib/indexer/block/realtime/fetcher.ex | 10 +++---- .../{zkevm => polygon_zkevm}/bridge.ex | 16 ++++++------ .../{zkevm => polygon_zkevm}/bridge_l1.ex | 16 ++++++------ .../bridge_l1_tokens.ex | 6 ++--- .../{zkevm => polygon_zkevm}/bridge_l2.ex | 18 ++++++------- .../transaction_batch.ex | 16 ++++++------ .../fetcher/rollup_l1_reorg_monitor.ex | 6 ++--- apps/indexer/lib/indexer/supervisor.ex | 8 +++--- .../lib/indexer/transform/addresses.ex | 4 +-- .../{zkevm => polygon_zkevm}/bridge.ex | 8 +++--- config/runtime.exs | 16 +++++++----- 34 files changed, 187 insertions(+), 181 deletions(-) rename apps/block_scout_web/lib/block_scout_web/channels/{zkevm_confirmed_batch_channel.ex => polygon_zkevm_confirmed_batch_channel.ex} (73%) rename apps/block_scout_web/lib/block_scout_web/controllers/api/v2/{zkevm_controller.ex => polygon_zkevm_controller.ex} (97%) rename apps/block_scout_web/lib/block_scout_web/views/api/v2/{zkevm_view.ex => polygon_zkevm_view.ex} (97%) rename apps/explorer/lib/explorer/chain/import/runner/{zkevm => polygon_zkevm}/batch_transactions.ex (79%) rename apps/explorer/lib/explorer/chain/import/runner/{zkevm => polygon_zkevm}/bridge_l1_tokens.ex (86%) rename apps/explorer/lib/explorer/chain/import/runner/{zkevm => polygon_zkevm}/bridge_operations.ex (83%) rename apps/explorer/lib/explorer/chain/import/runner/{zkevm => polygon_zkevm}/lifecycle_transactions.ex (83%) rename apps/explorer/lib/explorer/chain/import/runner/{zkevm => polygon_zkevm}/transaction_batches.ex (86%) rename apps/explorer/lib/explorer/chain/{zkevm => polygon_zkevm}/batch_transaction.ex (84%) rename apps/explorer/lib/explorer/chain/{zkevm => polygon_zkevm}/bridge.ex (93%) rename apps/explorer/lib/explorer/chain/{zkevm => polygon_zkevm}/bridge_l1_token.ex (89%) rename apps/explorer/lib/explorer/chain/{zkevm => polygon_zkevm}/lifecycle_transaction.ex (82%) rename apps/explorer/lib/explorer/chain/{zkevm => polygon_zkevm}/reader.ex (90%) rename apps/explorer/lib/explorer/chain/{zkevm => polygon_zkevm}/transaction_batch.ex (87%) rename apps/indexer/lib/indexer/fetcher/{zkevm => polygon_zkevm}/bridge.ex (96%) rename apps/indexer/lib/indexer/fetcher/{zkevm => polygon_zkevm}/bridge_l1.ex (93%) rename apps/indexer/lib/indexer/fetcher/{zkevm => polygon_zkevm}/bridge_l1_tokens.ex (90%) rename apps/indexer/lib/indexer/fetcher/{zkevm => polygon_zkevm}/bridge_l2.ex (91%) rename apps/indexer/lib/indexer/fetcher/{zkevm => polygon_zkevm}/transaction_batch.ex (95%) rename apps/indexer/lib/indexer/transform/{zkevm => polygon_zkevm}/bridge.ex (91%) diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 1116688a64d7..467793c43cd1 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -204,7 +204,7 @@ defmodule BlockScoutWeb.ApiRouter do get("/watchlist", V2.TransactionController, :watchlist_transactions) if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do - get("/zkevm-batch/:batch_number", V2.TransactionController, :zkevm_batch) + get("/zkevm-batch/:batch_number", V2.TransactionController, :polygon_zkevm_batch) end if System.get_env("CHAIN_TYPE") == "suave" do @@ -274,8 +274,8 @@ defmodule BlockScoutWeb.ApiRouter do get("/indexing-status", V2.MainPageController, :indexing_status) if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do - get("/zkevm/batches/confirmed", V2.ZkevmController, :batches_confirmed) - get("/zkevm/batches/latest-number", V2.ZkevmController, :batch_latest_number) + get("/zkevm/batches/confirmed", V2.PolygonZkevmController, :batches_confirmed) + get("/zkevm/batches/latest-number", V2.PolygonZkevmController, :batch_latest_number) end end @@ -313,13 +313,13 @@ defmodule BlockScoutWeb.ApiRouter do scope "/zkevm" do if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do - get("/batches", V2.ZkevmController, :batches) - get("/batches/count", V2.ZkevmController, :batches_count) - get("/batches/:batch_number", V2.ZkevmController, :batch) - get("/deposits", V2.ZkevmController, :deposits) - get("/deposits/count", V2.ZkevmController, :deposits_count) - get("/withdrawals", V2.ZkevmController, :withdrawals) - get("/withdrawals/count", V2.ZkevmController, :withdrawals_count) + get("/batches", V2.PolygonZkevmController, :batches) + get("/batches/count", V2.PolygonZkevmController, :batches_count) + get("/batches/:batch_number", V2.PolygonZkevmController, :batch) + get("/deposits", V2.PolygonZkevmController, :deposits) + get("/deposits/count", V2.PolygonZkevmController, :deposits_count) + get("/withdrawals", V2.PolygonZkevmController, :withdrawals) + get("/withdrawals/count", V2.PolygonZkevmController, :withdrawals_count) end end diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index 2ca930067689..5dff1d691b5e 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -41,7 +41,7 @@ defmodule BlockScoutWeb.Chain do Wei } - alias Explorer.Chain.Zkevm.TransactionBatch + alias Explorer.Chain.PolygonZkevm.TransactionBatch alias Explorer.PagingOptions defimpl Poison.Encoder, for: Decimal do diff --git a/apps/block_scout_web/lib/block_scout_web/channels/zkevm_confirmed_batch_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/polygon_zkevm_confirmed_batch_channel.ex similarity index 73% rename from apps/block_scout_web/lib/block_scout_web/channels/zkevm_confirmed_batch_channel.ex rename to apps/block_scout_web/lib/block_scout_web/channels/polygon_zkevm_confirmed_batch_channel.ex index 9007f1176412..0b80fb53580f 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/zkevm_confirmed_batch_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/polygon_zkevm_confirmed_batch_channel.ex @@ -1,10 +1,10 @@ -defmodule BlockScoutWeb.ZkevmConfirmedBatchChannel do +defmodule BlockScoutWeb.PolygonZkevmConfirmedBatchChannel do @moduledoc """ Establishes pub/sub channel for live updates of zkEVM confirmed batch events. """ use BlockScoutWeb, :channel - alias BlockScoutWeb.API.V2.ZkevmView + alias BlockScoutWeb.API.V2.PolygonZkevmView intercept(["new_zkevm_confirmed_batch"]) @@ -17,7 +17,7 @@ defmodule BlockScoutWeb.ZkevmConfirmedBatchChannel do %{batch: batch}, %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket ) do - rendered_batch = ZkevmView.render("zkevm_batch.json", %{batch: batch, socket: nil}) + rendered_batch = PolygonZkevmView.render("zkevm_batch.json", %{batch: batch, socket: nil}) push(socket, "new_zkevm_confirmed_batch", %{ batch: rendered_batch diff --git a/apps/block_scout_web/lib/block_scout_web/channels/user_socket_v2.ex b/apps/block_scout_web/lib/block_scout_web/channels/user_socket_v2.ex index 159993433e35..ec3e5460ecc4 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/user_socket_v2.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/user_socket_v2.ex @@ -10,7 +10,7 @@ defmodule BlockScoutWeb.UserSocketV2 do channel("rewards:*", BlockScoutWeb.RewardChannel) channel("transactions:*", BlockScoutWeb.TransactionChannel) channel("tokens:*", BlockScoutWeb.TokenChannel) - channel("zkevm_batches:*", BlockScoutWeb.ZkevmConfirmedBatchChannel) + channel("zkevm_batches:*", BlockScoutWeb.PolygonZkevmConfirmedBatchChannel) def connect(_params, socket) do {:ok, socket} diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/zkevm_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/polygon_zkevm_controller.ex similarity index 97% rename from apps/block_scout_web/lib/block_scout_web/controllers/api/v2/zkevm_controller.ex rename to apps/block_scout_web/lib/block_scout_web/controllers/api/v2/polygon_zkevm_controller.ex index 961933de883e..e01b9a7caf9c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/zkevm_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/polygon_zkevm_controller.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.API.V2.ZkevmController do +defmodule BlockScoutWeb.API.V2.PolygonZkevmController do use BlockScoutWeb, :controller import BlockScoutWeb.Chain, @@ -8,7 +8,7 @@ defmodule BlockScoutWeb.API.V2.ZkevmController do split_list_by_page: 1 ] - alias Explorer.Chain.Zkevm.Reader + alias Explorer.Chain.PolygonZkevm.Reader action_fallback(BlockScoutWeb.API.V2.FallbackController) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex index 8b19b1801108..a850ed2c421e 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -31,7 +31,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do alias Explorer.Chain alias Explorer.Chain.Beacon.Reader, as: BeaconReader alias Explorer.Chain.{Hash, Transaction} - alias Explorer.Chain.Zkevm.Reader + alias Explorer.Chain.PolygonZkevm.Reader alias Indexer.Fetcher.FirstTraceOnDemand action_fallback(BlockScoutWeb.API.V2.FallbackController) @@ -155,8 +155,8 @@ defmodule BlockScoutWeb.API.V2.TransactionController do Function to handle GET requests to `/api/v2/transactions/zkevm-batch/:batch_number` endpoint. It renders the list of L2 transactions bound to the specified batch. """ - @spec zkevm_batch(Plug.Conn.t(), map()) :: Plug.Conn.t() - def zkevm_batch(conn, %{"batch_number" => batch_number} = _params) do + @spec polygon_zkevm_batch(Plug.Conn.t(), map()) :: Plug.Conn.t() + def polygon_zkevm_batch(conn, %{"batch_number" => batch_number} = _params) do transactions = batch_number |> Reader.batch_transactions(api?: true) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/zkevm_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_zkevm_view.ex similarity index 97% rename from apps/block_scout_web/lib/block_scout_web/views/api/v2/zkevm_view.ex rename to apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_zkevm_view.ex index e46af83bbca3..1f99d6e5749e 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/zkevm_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_zkevm_view.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.API.V2.ZkevmView do +defmodule BlockScoutWeb.API.V2.PolygonZkevmView do use BlockScoutWeb, :view @doc """ @@ -75,7 +75,7 @@ defmodule BlockScoutWeb.API.V2.ZkevmView do items: items, next_page_params: next_page_params }) do - env = Application.get_all_env(:indexer)[Indexer.Fetcher.Zkevm.BridgeL1] + env = Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonZkevm.BridgeL1] %{ items: diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index 1f8961202c51..5f364a4b16e9 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -24,7 +24,7 @@ defmodule BlockScoutWeb.Mixfile do ], start_permanent: Mix.env() == :prod, version: "6.1.0", - xref: [exclude: [Explorer.Chain.Zkevm.Reader, Explorer.Chain.Beacon.Reader]] + xref: [exclude: [Explorer.Chain.PolygonZkevm.Reader, Explorer.Chain.Beacon.Reader]] ] end diff --git a/apps/explorer/lib/explorer/chain/import/runner/zkevm/batch_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/batch_transactions.ex similarity index 79% rename from apps/explorer/lib/explorer/chain/import/runner/zkevm/batch_transactions.ex rename to apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/batch_transactions.ex index 2df1223945a1..c330da10e689 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/zkevm/batch_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/batch_transactions.ex @@ -1,13 +1,13 @@ -defmodule Explorer.Chain.Import.Runner.Zkevm.BatchTransactions do +defmodule Explorer.Chain.Import.Runner.PolygonZkevm.BatchTransactions do @moduledoc """ - Bulk imports `t:Explorer.Chain.Zkevm.BatchTransaction.t/0`. + Bulk imports `t:Explorer.Chain.PolygonZkevm.BatchTransaction.t/0`. """ require Ecto.Query alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.Import - alias Explorer.Chain.Zkevm.BatchTransaction + alias Explorer.Chain.PolygonZkevm.BatchTransaction alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner @@ -21,7 +21,7 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BatchTransactions do def ecto_schema_module, do: BatchTransaction @impl Import.Runner - def option_key, do: :zkevm_batch_transactions + def option_key, do: :polygon_zkevm_batch_transactions @impl Import.Runner @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()} @@ -42,12 +42,12 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BatchTransactions do |> Map.put_new(:timeout, @timeout) |> Map.put(:timestamps, timestamps) - Multi.run(multi, :insert_zkevm_batch_transactions, fn repo, _ -> + Multi.run(multi, :insert_polygon_zkevm_batch_transactions, fn repo, _ -> Instrumenter.block_import_stage_runner( fn -> insert(repo, changes_list, insert_options) end, :block_referencing, - :zkevm_batch_transactions, - :zkevm_batch_transactions + :polygon_zkevm_batch_transactions, + :polygon_zkevm_batch_transactions ) end) end @@ -59,7 +59,7 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BatchTransactions do {:ok, [BatchTransaction.t()]} | {:error, [Changeset.t()]} def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = _options) when is_list(changes_list) do - # Enforce Zkevm.BatchTransaction ShareLocks order (see docs: sharelock.md) + # Enforce PolygonZkevm.BatchTransaction ShareLocks order (see docs: sharelock.md) ordered_changes_list = Enum.sort_by(changes_list, & &1.hash) {:ok, inserted} = diff --git a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_l1_tokens.ex b/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/bridge_l1_tokens.ex similarity index 86% rename from apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_l1_tokens.ex rename to apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/bridge_l1_tokens.ex index 5cb7e5624bd1..03ed1bd5783c 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_l1_tokens.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/bridge_l1_tokens.ex @@ -1,6 +1,6 @@ -defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeL1Tokens do +defmodule Explorer.Chain.Import.Runner.PolygonZkevm.BridgeL1Tokens do @moduledoc """ - Bulk imports `t:Explorer.Chain.Zkevm.BridgeL1Token.t/0`. + Bulk imports `t:Explorer.Chain.PolygonZkevm.BridgeL1Token.t/0`. """ require Ecto.Query @@ -9,7 +9,7 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeL1Tokens do alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.Import - alias Explorer.Chain.Zkevm.BridgeL1Token + alias Explorer.Chain.PolygonZkevm.BridgeL1Token alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner @@ -23,7 +23,7 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeL1Tokens do def ecto_schema_module, do: BridgeL1Token @impl Import.Runner - def option_key, do: :zkevm_bridge_l1_tokens + def option_key, do: :polygon_zkevm_bridge_l1_tokens @impl Import.Runner def imported_table_row do @@ -42,12 +42,12 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeL1Tokens do |> Map.put_new(:timeout, @timeout) |> Map.put(:timestamps, timestamps) - Multi.run(multi, :insert_zkevm_bridge_l1_tokens, fn repo, _ -> + Multi.run(multi, :insert_polygon_zkevm_bridge_l1_tokens, fn repo, _ -> Instrumenter.block_import_stage_runner( fn -> insert(repo, changes_list, insert_options) end, :block_referencing, - :zkevm_bridge_l1_tokens, - :zkevm_bridge_l1_tokens + :polygon_zkevm_bridge_l1_tokens, + :polygon_zkevm_bridge_l1_tokens ) end) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex b/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/bridge_operations.ex similarity index 83% rename from apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex rename to apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/bridge_operations.ex index c7aaef5cb0a9..6cd724fe70cb 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/zkevm/bridge_operations.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/bridge_operations.ex @@ -1,6 +1,6 @@ -defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeOperations do +defmodule Explorer.Chain.Import.Runner.PolygonZkevm.BridgeOperations do @moduledoc """ - Bulk imports `t:Explorer.Chain.Zkevm.Bridge.t/0`. + Bulk imports `t:Explorer.Chain.PolygonZkevm.Bridge.t/0`. """ require Ecto.Query @@ -9,7 +9,7 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeOperations do alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.Import - alias Explorer.Chain.Zkevm.Bridge, as: ZkevmBridge + alias Explorer.Chain.PolygonZkevm.Bridge, as: PolygonZkevmBridge alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner @@ -17,13 +17,13 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeOperations do # milliseconds @timeout 60_000 - @type imported :: [ZkevmBridge.t()] + @type imported :: [PolygonZkevmBridge.t()] @impl Import.Runner - def ecto_schema_module, do: ZkevmBridge + def ecto_schema_module, do: PolygonZkevmBridge @impl Import.Runner - def option_key, do: :zkevm_bridge_operations + def option_key, do: :polygon_zkevm_bridge_operations @impl Import.Runner def imported_table_row do @@ -42,12 +42,12 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeOperations do |> Map.put_new(:timeout, @timeout) |> Map.put(:timestamps, timestamps) - Multi.run(multi, :insert_zkevm_bridge_operations, fn repo, _ -> + Multi.run(multi, :insert_polygon_zkevm_bridge_operations, fn repo, _ -> Instrumenter.block_import_stage_runner( fn -> insert(repo, changes_list, insert_options) end, :block_referencing, - :zkevm_bridge_operations, - :zkevm_bridge_operations + :polygon_zkevm_bridge_operations, + :polygon_zkevm_bridge_operations ) end) end @@ -56,12 +56,12 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeOperations do def timeout, do: @timeout @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: - {:ok, [ZkevmBridge.t()]} + {:ok, [PolygonZkevmBridge.t()]} | {:error, [Changeset.t()]} def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) - # Enforce ZkevmBridge ShareLocks order (see docs: sharelock.md) + # Enforce PolygonZkevmBridge ShareLocks order (see docs: sharelock.md) ordered_changes_list = Enum.sort_by(changes_list, &{&1.type, &1.index}) {:ok, inserted} = @@ -70,7 +70,7 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeOperations do ordered_changes_list, conflict_target: [:type, :index], on_conflict: on_conflict, - for: ZkevmBridge, + for: PolygonZkevmBridge, returning: true, timeout: timeout, timestamps: timestamps @@ -81,7 +81,7 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.BridgeOperations do defp default_on_conflict do from( - op in ZkevmBridge, + op in PolygonZkevmBridge, update: [ set: [ # Don't update `type` as it is part of the composite primary key and used for the conflict target diff --git a/apps/explorer/lib/explorer/chain/import/runner/zkevm/lifecycle_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/lifecycle_transactions.ex similarity index 83% rename from apps/explorer/lib/explorer/chain/import/runner/zkevm/lifecycle_transactions.ex rename to apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/lifecycle_transactions.ex index 7a5e4c132735..3b12c4cd19d9 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/zkevm/lifecycle_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/lifecycle_transactions.ex @@ -1,13 +1,13 @@ -defmodule Explorer.Chain.Import.Runner.Zkevm.LifecycleTransactions do +defmodule Explorer.Chain.Import.Runner.PolygonZkevm.LifecycleTransactions do @moduledoc """ - Bulk imports `t:Explorer.Chain.Zkevm.LifecycleTransaction.t/0`. + Bulk imports `t:Explorer.Chain.PolygonZkevm.LifecycleTransaction.t/0`. """ require Ecto.Query alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.Import - alias Explorer.Chain.Zkevm.LifecycleTransaction + alias Explorer.Chain.PolygonZkevm.LifecycleTransaction alias Explorer.Prometheus.Instrumenter import Ecto.Query, only: [from: 2] @@ -23,7 +23,7 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.LifecycleTransactions do def ecto_schema_module, do: LifecycleTransaction @impl Import.Runner - def option_key, do: :zkevm_lifecycle_transactions + def option_key, do: :polygon_zkevm_lifecycle_transactions @impl Import.Runner @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()} @@ -44,12 +44,12 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.LifecycleTransactions do |> Map.put_new(:timeout, @timeout) |> Map.put(:timestamps, timestamps) - Multi.run(multi, :insert_zkevm_lifecycle_transactions, fn repo, _ -> + Multi.run(multi, :insert_polygon_zkevm_lifecycle_transactions, fn repo, _ -> Instrumenter.block_import_stage_runner( fn -> insert(repo, changes_list, insert_options) end, :block_referencing, - :zkevm_lifecycle_transactions, - :zkevm_lifecycle_transactions + :polygon_zkevm_lifecycle_transactions, + :polygon_zkevm_lifecycle_transactions ) end) end @@ -63,7 +63,7 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.LifecycleTransactions do def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) - # Enforce Zkevm.LifecycleTransaction ShareLocks order (see docs: sharelock.md) + # Enforce PolygonZkevm.LifecycleTransaction ShareLocks order (see docs: sharelock.md) ordered_changes_list = Enum.sort_by(changes_list, & &1.id) {:ok, inserted} = diff --git a/apps/explorer/lib/explorer/chain/import/runner/zkevm/transaction_batches.ex b/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/transaction_batches.ex similarity index 86% rename from apps/explorer/lib/explorer/chain/import/runner/zkevm/transaction_batches.ex rename to apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/transaction_batches.ex index db9f2771ea40..e2b8930b1828 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/zkevm/transaction_batches.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/transaction_batches.ex @@ -1,13 +1,13 @@ -defmodule Explorer.Chain.Import.Runner.Zkevm.TransactionBatches do +defmodule Explorer.Chain.Import.Runner.PolygonZkevm.TransactionBatches do @moduledoc """ - Bulk imports `t:Explorer.Chain.Zkevm.TransactionBatch.t/0`. + Bulk imports `t:Explorer.Chain.PolygonZkevm.TransactionBatch.t/0`. """ require Ecto.Query alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.Import - alias Explorer.Chain.Zkevm.TransactionBatch + alias Explorer.Chain.PolygonZkevm.TransactionBatch alias Explorer.Prometheus.Instrumenter import Ecto.Query, only: [from: 2] @@ -23,7 +23,7 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.TransactionBatches do def ecto_schema_module, do: TransactionBatch @impl Import.Runner - def option_key, do: :zkevm_transaction_batches + def option_key, do: :polygon_zkevm_transaction_batches @impl Import.Runner @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()} @@ -44,12 +44,12 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.TransactionBatches do |> Map.put_new(:timeout, @timeout) |> Map.put(:timestamps, timestamps) - Multi.run(multi, :insert_zkevm_transaction_batches, fn repo, _ -> + Multi.run(multi, :insert_polygon_zkevm_transaction_batches, fn repo, _ -> Instrumenter.block_import_stage_runner( fn -> insert(repo, changes_list, insert_options) end, :block_referencing, - :zkevm_transaction_batches, - :zkevm_transaction_batches + :polygon_zkevm_transaction_batches, + :polygon_zkevm_transaction_batches ) end) end @@ -63,7 +63,7 @@ defmodule Explorer.Chain.Import.Runner.Zkevm.TransactionBatches do def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) - # Enforce Zkevm.TransactionBatch ShareLocks order (see docs: sharelock.md) + # Enforce PolygonZkevm.TransactionBatch ShareLocks order (see docs: sharelock.md) ordered_changes_list = Enum.sort_by(changes_list, & &1.number) {:ok, inserted} = diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex index 4baec4a08240..f85e48a64fe1 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex @@ -26,11 +26,11 @@ defmodule Explorer.Chain.Import.Stage.BlockReferencing do ] @polygon_zkevm_runners [ - Runner.Zkevm.LifecycleTransactions, - Runner.Zkevm.TransactionBatches, - Runner.Zkevm.BatchTransactions, - Runner.Zkevm.BridgeL1Tokens, - Runner.Zkevm.BridgeOperations + Runner.PolygonZkevm.LifecycleTransactions, + Runner.PolygonZkevm.TransactionBatches, + Runner.PolygonZkevm.BatchTransactions, + Runner.PolygonZkevm.BridgeL1Tokens, + Runner.PolygonZkevm.BridgeOperations ] @shibarium_runners [ diff --git a/apps/explorer/lib/explorer/chain/zkevm/batch_transaction.ex b/apps/explorer/lib/explorer/chain/polygon_zkevm/batch_transaction.ex similarity index 84% rename from apps/explorer/lib/explorer/chain/zkevm/batch_transaction.ex rename to apps/explorer/lib/explorer/chain/polygon_zkevm/batch_transaction.ex index c60c7ab07ba8..18d8a775a1b3 100644 --- a/apps/explorer/lib/explorer/chain/zkevm/batch_transaction.ex +++ b/apps/explorer/lib/explorer/chain/polygon_zkevm/batch_transaction.ex @@ -1,15 +1,15 @@ -defmodule Explorer.Chain.Zkevm.BatchTransaction do +defmodule Explorer.Chain.PolygonZkevm.BatchTransaction do @moduledoc "Models a list of transactions related to a batch for zkEVM." use Explorer.Schema alias Explorer.Chain.{Hash, Transaction} - alias Explorer.Chain.Zkevm.TransactionBatch + alias Explorer.Chain.PolygonZkevm.TransactionBatch @required_attrs ~w(batch_number hash)a @primary_key false - typed_schema "zkevm_batch_l2_transactions" do + typed_schema "polygon_zkevm_batch_l2_transactions" do belongs_to(:batch, TransactionBatch, foreign_key: :batch_number, references: :number, type: :integer, null: false) belongs_to(:l2_transaction, Transaction, diff --git a/apps/explorer/lib/explorer/chain/zkevm/bridge.ex b/apps/explorer/lib/explorer/chain/polygon_zkevm/bridge.ex similarity index 93% rename from apps/explorer/lib/explorer/chain/zkevm/bridge.ex rename to apps/explorer/lib/explorer/chain/polygon_zkevm/bridge.ex index 7a725275cbd4..c0623f8fbf52 100644 --- a/apps/explorer/lib/explorer/chain/zkevm/bridge.ex +++ b/apps/explorer/lib/explorer/chain/polygon_zkevm/bridge.ex @@ -1,10 +1,10 @@ -defmodule Explorer.Chain.Zkevm.Bridge do +defmodule Explorer.Chain.PolygonZkevm.Bridge do @moduledoc "Models a bridge operation for Polygon zkEVM." use Explorer.Schema alias Explorer.Chain.{Block, Hash, Token} - alias Explorer.Chain.Zkevm.BridgeL1Token + alias Explorer.Chain.PolygonZkevm.BridgeL1Token @optional_attrs ~w(l1_transaction_hash l2_transaction_hash l1_token_id l2_token_address block_number block_timestamp)a @@ -26,7 +26,7 @@ defmodule Explorer.Chain.Zkevm.Bridge do } @primary_key false - schema "zkevm_bridge" do + schema "polygon_zkevm_bridge" do field(:type, Ecto.Enum, values: [:deposit, :withdrawal], primary_key: true) field(:index, :integer, primary_key: true) field(:l1_transaction_hash, Hash.Full) diff --git a/apps/explorer/lib/explorer/chain/zkevm/bridge_l1_token.ex b/apps/explorer/lib/explorer/chain/polygon_zkevm/bridge_l1_token.ex similarity index 89% rename from apps/explorer/lib/explorer/chain/zkevm/bridge_l1_token.ex rename to apps/explorer/lib/explorer/chain/polygon_zkevm/bridge_l1_token.ex index d54951eb3f64..c3187c28ea40 100644 --- a/apps/explorer/lib/explorer/chain/zkevm/bridge_l1_token.ex +++ b/apps/explorer/lib/explorer/chain/polygon_zkevm/bridge_l1_token.ex @@ -1,4 +1,4 @@ -defmodule Explorer.Chain.Zkevm.BridgeL1Token do +defmodule Explorer.Chain.PolygonZkevm.BridgeL1Token do @moduledoc "Models a bridge token on L1 for Polygon zkEVM." use Explorer.Schema @@ -16,7 +16,7 @@ defmodule Explorer.Chain.Zkevm.BridgeL1Token do } @primary_key {:id, :id, autogenerate: true} - schema "zkevm_bridge_l1_tokens" do + schema "polygon_zkevm_bridge_l1_tokens" do field(:address, Hash.Address) field(:decimals, :integer) field(:symbol, :string) diff --git a/apps/explorer/lib/explorer/chain/zkevm/lifecycle_transaction.ex b/apps/explorer/lib/explorer/chain/polygon_zkevm/lifecycle_transaction.ex similarity index 82% rename from apps/explorer/lib/explorer/chain/zkevm/lifecycle_transaction.ex rename to apps/explorer/lib/explorer/chain/polygon_zkevm/lifecycle_transaction.ex index 4e238dba9670..504cdce41663 100644 --- a/apps/explorer/lib/explorer/chain/zkevm/lifecycle_transaction.ex +++ b/apps/explorer/lib/explorer/chain/polygon_zkevm/lifecycle_transaction.ex @@ -1,15 +1,15 @@ -defmodule Explorer.Chain.Zkevm.LifecycleTransaction do +defmodule Explorer.Chain.PolygonZkevm.LifecycleTransaction do @moduledoc "Models an L1 lifecycle transaction for zkEVM." use Explorer.Schema alias Explorer.Chain.Hash - alias Explorer.Chain.Zkevm.TransactionBatch + alias Explorer.Chain.PolygonZkevm.TransactionBatch @required_attrs ~w(id hash is_verify)a @primary_key false - typed_schema "zkevm_lifecycle_l1_transactions" do + typed_schema "polygon_zkevm_lifecycle_l1_transactions" do field(:id, :integer, primary_key: true, null: false) field(:hash, Hash.Full, null: false) field(:is_verify, :boolean, null: false) diff --git a/apps/explorer/lib/explorer/chain/zkevm/reader.ex b/apps/explorer/lib/explorer/chain/polygon_zkevm/reader.ex similarity index 90% rename from apps/explorer/lib/explorer/chain/zkevm/reader.ex rename to apps/explorer/lib/explorer/chain/polygon_zkevm/reader.ex index 6118ca4820e6..60a8d2b8ed8f 100644 --- a/apps/explorer/lib/explorer/chain/zkevm/reader.ex +++ b/apps/explorer/lib/explorer/chain/polygon_zkevm/reader.ex @@ -1,4 +1,4 @@ -defmodule Explorer.Chain.Zkevm.Reader do +defmodule Explorer.Chain.PolygonZkevm.Reader do @moduledoc "Contains read functions for zkevm modules." import Ecto.Query, @@ -12,13 +12,13 @@ defmodule Explorer.Chain.Zkevm.Reader do import Explorer.Chain, only: [select_repo: 1] - alias Explorer.Chain.Zkevm.{BatchTransaction, Bridge, BridgeL1Token, LifecycleTransaction, TransactionBatch} + alias Explorer.Chain.PolygonZkevm.{BatchTransaction, Bridge, BridgeL1Token, LifecycleTransaction, TransactionBatch} alias Explorer.{Chain, PagingOptions, Repo} alias Indexer.Helper @doc """ Reads a batch by its number from database. - If the number is :latest, gets the latest batch from `zkevm_transaction_batches` table. + If the number is :latest, gets the latest batch from `polygon_zkevm_transaction_batches` table. Returns {:error, :not_found} in case the batch is not found. """ @spec batch(non_neg_integer() | :latest, list()) :: {:ok, map()} | {:error, :not_found} @@ -49,7 +49,7 @@ defmodule Explorer.Chain.Zkevm.Reader do end @doc """ - Reads a list of batches from `zkevm_transaction_batches` table. + Reads a list of batches from `polygon_zkevm_transaction_batches` table. """ @spec batches(list()) :: list() def batches(options \\ []) do @@ -79,7 +79,7 @@ defmodule Explorer.Chain.Zkevm.Reader do end @doc """ - Reads a list of L2 transaction hashes from `zkevm_batch_l2_transactions` table. + Reads a list of L2 transaction hashes from `polygon_zkevm_batch_l2_transactions` table. """ @spec batch_transactions(non_neg_integer(), list()) :: list() def batch_transactions(batch_number, options \\ []) do @@ -90,7 +90,7 @@ defmodule Explorer.Chain.Zkevm.Reader do @doc """ Tries to read L1 token data (address, symbol, decimals) for the given addresses - from the database. If the data for an address is not found in Explorer.Chain.Zkevm.BridgeL1Token, + from the database. If the data for an address is not found in Explorer.Chain.PolygonZkevm.BridgeL1Token, the address is returned in the list inside the tuple (the second item of the tuple). The first item of the returned tuple contains `L1 token address -> L1 token data` map. """ @@ -122,7 +122,7 @@ defmodule Explorer.Chain.Zkevm.Reader do end @doc """ - Gets last known L1 item (deposit) from zkevm_bridge table. + Gets last known L1 item (deposit) from polygon_zkevm_bridge table. Returns block number and L1 transaction hash bound to that deposit. If not found, returns zero block number and nil as the transaction hash. """ @@ -142,7 +142,7 @@ defmodule Explorer.Chain.Zkevm.Reader do end @doc """ - Gets last known L2 item (withdrawal) from zkevm_bridge table. + Gets last known L2 item (withdrawal) from polygon_zkevm_bridge table. Returns block number and L2 transaction hash bound to that withdrawal. If not found, returns zero block number and nil as the transaction hash. """ @@ -162,7 +162,7 @@ defmodule Explorer.Chain.Zkevm.Reader do end @doc """ - Gets the number of the latest batch with defined verify_id from `zkevm_transaction_batches` table. + Gets the number of the latest batch with defined verify_id from `polygon_zkevm_transaction_batches` table. Returns 0 if not found. """ @spec last_verified_batch_number() :: non_neg_integer() @@ -181,7 +181,7 @@ defmodule Explorer.Chain.Zkevm.Reader do end @doc """ - Reads a list of L1 transactions by their hashes from `zkevm_lifecycle_l1_transactions` table. + Reads a list of L1 transactions by their hashes from `polygon_zkevm_lifecycle_l1_transactions` table. """ @spec lifecycle_transactions(list()) :: list() def lifecycle_transactions(l1_tx_hashes) do @@ -196,7 +196,7 @@ defmodule Explorer.Chain.Zkevm.Reader do end @doc """ - Determines ID of the future lifecycle transaction by reading `zkevm_lifecycle_l1_transactions` table. + Determines ID of the future lifecycle transaction by reading `polygon_zkevm_lifecycle_l1_transactions` table. """ @spec next_id() :: non_neg_integer() def next_id do @@ -217,7 +217,7 @@ defmodule Explorer.Chain.Zkevm.Reader do @doc """ Builds `L1 token address -> L1 token id` map for the given token addresses. - The info is taken from Explorer.Chain.Zkevm.BridgeL1Token. + The info is taken from Explorer.Chain.PolygonZkevm.BridgeL1Token. If an address is not in the table, it won't be in the resulting map. """ @spec token_addresses_to_ids_from_db(list()) :: map() diff --git a/apps/explorer/lib/explorer/chain/zkevm/transaction_batch.ex b/apps/explorer/lib/explorer/chain/polygon_zkevm/transaction_batch.ex similarity index 87% rename from apps/explorer/lib/explorer/chain/zkevm/transaction_batch.ex rename to apps/explorer/lib/explorer/chain/polygon_zkevm/transaction_batch.ex index 34c2bdbbab96..b602ff092254 100644 --- a/apps/explorer/lib/explorer/chain/zkevm/transaction_batch.ex +++ b/apps/explorer/lib/explorer/chain/polygon_zkevm/transaction_batch.ex @@ -1,17 +1,17 @@ -defmodule Explorer.Chain.Zkevm.TransactionBatch do +defmodule Explorer.Chain.PolygonZkevm.TransactionBatch do @moduledoc "Models a batch of transactions for zkEVM." use Explorer.Schema alias Explorer.Chain.Hash - alias Explorer.Chain.Zkevm.{BatchTransaction, LifecycleTransaction} + alias Explorer.Chain.PolygonZkevm.{BatchTransaction, LifecycleTransaction} @optional_attrs ~w(sequence_id verify_id)a @required_attrs ~w(number timestamp l2_transactions_count global_exit_root acc_input_hash state_root)a @primary_key false - typed_schema "zkevm_transaction_batches" do + typed_schema "polygon_zkevm_transaction_batches" do field(:number, :integer, primary_key: true, null: false) field(:timestamp, :utc_datetime_usec) field(:l2_transactions_count, :integer) diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index d5341e33b4fe..752b7e56d717 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -14,8 +14,8 @@ defmodule Explorer.Chain.Transaction.Schema do Wei } + alias Explorer.Chain.PolygonZkevm.BatchTransaction alias Explorer.Chain.Transaction.{Fork, Status} - alias Explorer.Chain.Zkevm.BatchTransaction @chain_type_fields (case Application.compile_env(:explorer, :chain_type) do "ethereum" -> diff --git a/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs b/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs index 1eca46f86d23..805acd4d6a70 100644 --- a/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs +++ b/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs @@ -2,7 +2,7 @@ defmodule Explorer.Repo.PolygonZkevm.Migrations.AddBridgeTables do use Ecto.Migration def change do - create table(:zkevm_bridge_l1_tokens, primary_key: false) do + create table(:polygon_zkevm_bridge_l1_tokens, primary_key: false) do add(:id, :identity, primary_key: true, start_value: 0, increment: 1) add(:address, :bytea, null: false) add(:decimals, :smallint, null: true, default: nil) @@ -10,22 +10,22 @@ defmodule Explorer.Repo.PolygonZkevm.Migrations.AddBridgeTables do timestamps(null: false, type: :utc_datetime_usec) end - create(unique_index(:zkevm_bridge_l1_tokens, :address)) + create(unique_index(:polygon_zkevm_bridge_l1_tokens, :address)) execute( - "CREATE TYPE zkevm_bridge_op_type AS ENUM ('deposit', 'withdrawal')", - "DROP TYPE zkevm_bridge_op_type" + "CREATE TYPE polygon_zkevm_bridge_op_type AS ENUM ('deposit', 'withdrawal')", + "DROP TYPE polygon_zkevm_bridge_op_type" ) - create table(:zkevm_bridge, primary_key: false) do - add(:type, :zkevm_bridge_op_type, null: false, primary_key: true) + create table(:polygon_zkevm_bridge, primary_key: false) do + add(:type, :polygon_zkevm_bridge_op_type, null: false, primary_key: true) add(:index, :integer, null: false, primary_key: true) add(:l1_transaction_hash, :bytea, null: true) add(:l2_transaction_hash, :bytea, null: true) add( :l1_token_id, - references(:zkevm_bridge_l1_tokens, on_delete: :restrict, on_update: :update_all, type: :identity), + references(:polygon_zkevm_bridge_l1_tokens, on_delete: :restrict, on_update: :update_all, type: :identity), null: true ) @@ -37,6 +37,10 @@ defmodule Explorer.Repo.PolygonZkevm.Migrations.AddBridgeTables do timestamps(null: false, type: :utc_datetime_usec) end - create(index(:zkevm_bridge, :l1_token_address)) + create(index(:polygon_zkevm_bridge, :l1_token_address)) + + rename(table(:zkevm_lifecycle_l1_transactions), to: table(:polygon_zkevm_lifecycle_l1_transactions)) + rename(table(:zkevm_transaction_batches), to: table(:polygon_zkevm_transaction_batches)) + rename(table(:zkevm_batch_l2_transactions), to: table(:polygon_zkevm_batch_l2_transactions)) end end diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 65caef9a4d13..e85531e04ea9 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -16,8 +16,8 @@ defmodule Indexer.Block.Fetcher do alias Explorer.Chain.Cache.Blocks, as: BlocksCache alias Explorer.Chain.Cache.{Accounts, BlockNumber, Transactions, Uncles} alias Indexer.Block.Fetcher.Receipts + alias Indexer.Fetcher.PolygonZkevm.BridgeL1Tokens, as: PolygonZkevmBridgeL1Tokens alias Indexer.Fetcher.TokenInstance.Realtime, as: TokenInstanceRealtime - alias Indexer.Fetcher.Zkevm.BridgeL1Tokens, as: ZkevmBridgeL1Tokens alias Indexer.Fetcher.{ Beacon.Blob, @@ -48,7 +48,7 @@ defmodule Indexer.Block.Fetcher do alias Indexer.Transform.Shibarium.Bridge, as: ShibariumBridge alias Indexer.Transform.Blocks, as: TransformBlocks - alias Indexer.Transform.Zkevm.Bridge, as: ZkevmBridge + alias Indexer.Transform.PolygonZkevm.Bridge, as: PolygonZkevmBridge @type address_hash_to_fetched_balance_block_number :: %{String.t() => Block.block_number()} @@ -160,9 +160,9 @@ defmodule Indexer.Block.Fetcher do do: ShibariumBridge.parse(blocks, transactions_with_receipts, logs), else: [] ), - zkevm_bridge_operations = + polygon_zkevm_bridge_operations = if(callback_module == Indexer.Block.Realtime.Fetcher, - do: ZkevmBridge.parse(blocks, logs), + do: PolygonZkevmBridge.parse(blocks, logs), else: [] ), %FetchedBeneficiaries{params_set: beneficiary_params_set, errors: beneficiaries_errors} = @@ -178,7 +178,7 @@ defmodule Indexer.Block.Fetcher do transactions: transactions_with_receipts, transaction_actions: transaction_actions, withdrawals: withdrawals_params, - zkevm_bridge_operations: zkevm_bridge_operations + polygon_zkevm_bridge_operations: polygon_zkevm_bridge_operations }), coin_balances_params_set = %{ @@ -216,7 +216,7 @@ defmodule Indexer.Block.Fetcher do transactions_with_receipts: transactions_with_receipts, polygon_edge_withdrawals: polygon_edge_withdrawals, polygon_edge_deposit_executes: polygon_edge_deposit_executes, - zkevm_bridge_operations: zkevm_bridge_operations, + polygon_zkevm_bridge_operations: polygon_zkevm_bridge_operations, shibarium_bridge_operations: shibarium_bridge_operations }, {:ok, inserted} <- @@ -249,7 +249,7 @@ defmodule Indexer.Block.Fetcher do transactions_with_receipts: transactions_with_receipts, polygon_edge_withdrawals: polygon_edge_withdrawals, polygon_edge_deposit_executes: polygon_edge_deposit_executes, - zkevm_bridge_operations: zkevm_bridge_operations, + polygon_zkevm_bridge_operations: polygon_zkevm_bridge_operations, shibarium_bridge_operations: shibarium_bridge_operations }) do case Application.get_env(:explorer, :chain_type) do @@ -266,7 +266,7 @@ defmodule Indexer.Block.Fetcher do "polygon_zkevm" -> basic_import_options - |> Map.put_new(:zkevm_bridge_operations, %{params: zkevm_bridge_operations}) + |> Map.put_new(:polygon_zkevm_bridge_operations, %{params: polygon_zkevm_bridge_operations}) "shibarium" -> basic_import_options @@ -439,15 +439,15 @@ defmodule Indexer.Block.Fetcher do @doc """ Fills a buffer of L1 token addresses to handle it asynchronously in - the Indexer.Fetcher.Zkevm.BridgeL1Tokens module. The addresses are + the Indexer.Fetcher.PolygonZkevm.BridgeL1Tokens module. The addresses are taken from the `operations` list. """ - @spec async_import_zkevm_bridge_l1_tokens(map()) :: :ok - def async_import_zkevm_bridge_l1_tokens(%{zkevm_bridge_operations: operations}) do - ZkevmBridgeL1Tokens.async_fetch(operations) + @spec async_import_polygon_zkevm_bridge_l1_tokens(map()) :: :ok + def async_import_polygon_zkevm_bridge_l1_tokens(%{polygon_zkevm_bridge_operations: operations}) do + PolygonZkevmBridgeL1Tokens.async_fetch(operations) end - def async_import_zkevm_bridge_l1_tokens(_), do: :ok + def async_import_polygon_zkevm_bridge_l1_tokens(_), do: :ok defp block_reward_errors_to_block_numbers(block_reward_errors) when is_list(block_reward_errors) do Enum.map(block_reward_errors, &block_reward_error_to_block_number/1) diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index 13a44e75bbaf..cb49b55d909e 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -22,7 +22,7 @@ defmodule Indexer.Block.Realtime.Fetcher do async_import_token_balances: 1, async_import_token_instances: 1, async_import_uncles: 1, - async_import_zkevm_bridge_l1_tokens: 1, + async_import_polygon_zkevm_bridge_l1_tokens: 1, fetch_and_import_range: 2 ] @@ -37,8 +37,8 @@ defmodule Indexer.Block.Realtime.Fetcher do alias Indexer.Block.Realtime.TaskSupervisor alias Indexer.Fetcher.{CoinBalance, CoinBalanceDailyUpdater} alias Indexer.Fetcher.PolygonEdge.{DepositExecute, Withdrawal} + alias Indexer.Fetcher.PolygonZkevm.BridgeL2, as: PolygonZkevmBridgeL2 alias Indexer.Fetcher.Shibarium.L2, as: ShibariumBridgeL2 - alias Indexer.Fetcher.Zkevm.BridgeL2, as: ZkevmBridgeL2 alias Indexer.Prometheus alias Indexer.Transform.Addresses alias Timex.Duration @@ -294,7 +294,7 @@ defmodule Indexer.Block.Realtime.Fetcher do # we need to remove all rows from `shibarium_bridge` table previously written starting from reorg block number remove_shibarium_assets_by_number(block_number_to_fetch) - # we need to remove all rows from `zkevm_bridge` table previously written starting from reorg block number + # we need to remove all rows from `polygon_zkevm_bridge` table previously written starting from reorg block number remove_polygon_zkevm_assets_by_number(block_number_to_fetch) # give previous fetch attempt (for same block number) a chance to finish @@ -318,7 +318,7 @@ defmodule Indexer.Block.Realtime.Fetcher do defp remove_polygon_zkevm_assets_by_number(block_number_to_fetch) do if Application.get_env(:explorer, :chain_type) == "polygon_zkevm" do - ZkevmBridgeL2.reorg_handle(block_number_to_fetch) + PolygonZkevmBridgeL2.reorg_handle(block_number_to_fetch) end end @@ -452,7 +452,7 @@ defmodule Indexer.Block.Realtime.Fetcher do async_import_uncles(imported) async_import_replaced_transactions(imported) async_import_blobs(imported) - async_import_zkevm_bridge_l1_tokens(imported) + async_import_polygon_zkevm_bridge_l1_tokens(imported) end defp balances( diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge.ex similarity index 96% rename from apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex rename to apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge.ex index fdf34258231f..b38807a5d5b4 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge.ex @@ -1,6 +1,6 @@ -defmodule Indexer.Fetcher.Zkevm.Bridge do +defmodule Indexer.Fetcher.PolygonZkevm.Bridge do @moduledoc """ - Contains common functions for Indexer.Fetcher.Zkevm.Bridge* modules. + Contains common functions for Indexer.Fetcher.PolygonZkevm.Bridge* modules. """ require Logger @@ -20,7 +20,7 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do alias EthereumJSONRPC.Logs alias Explorer.Chain - alias Explorer.Chain.Zkevm.Reader + alias Explorer.Chain.PolygonZkevm.Reader alias Explorer.SmartContract.Reader, as: SmartContractReader alias Indexer.Helper alias Indexer.Transform.Addresses @@ -112,20 +112,20 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do @doc """ Imports the given zkEVM bridge operations into database. - Used by Indexer.Fetcher.Zkevm.BridgeL1 and Indexer.Fetcher.Zkevm.BridgeL2 fetchers. + Used by Indexer.Fetcher.PolygonZkevm.BridgeL1 and Indexer.Fetcher.PolygonZkevm.BridgeL2 fetchers. Doesn't return anything. """ @spec import_operations(list()) :: no_return() def import_operations(operations) do addresses = Addresses.extract_addresses(%{ - zkevm_bridge_operations: operations + polygon_zkevm_bridge_operations: operations }) {:ok, _} = Chain.import(%{ addresses: %{params: addresses, on_conflict: :nothing}, - zkevm_bridge_operations: %{params: operations}, + polygon_zkevm_bridge_operations: %{params: operations}, timeout: :infinity }) end @@ -267,11 +267,11 @@ defmodule Indexer.Fetcher.Zkevm.Bridge do {:ok, inserts} = Chain.import(%{ - zkevm_bridge_l1_tokens: %{params: tokens_to_insert}, + polygon_zkevm_bridge_l1_tokens: %{params: tokens_to_insert}, timeout: :infinity }) - tokens_inserted = Map.get(inserts, :insert_zkevm_bridge_l1_tokens, []) + tokens_inserted = Map.get(inserts, :insert_polygon_zkevm_bridge_l1_tokens, []) # we need to query not inserted tokens from DB separately as they # could be inserted by another module at the same time (a race condition). diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1.ex similarity index 93% rename from apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex rename to apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1.ex index c0411c0dcd56..cd7fe24344f8 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1.ex @@ -1,6 +1,6 @@ -defmodule Indexer.Fetcher.Zkevm.BridgeL1 do +defmodule Indexer.Fetcher.PolygonZkevm.BridgeL1 do @moduledoc """ - Fills zkevm_bridge DB table. + Fills polygon_zkevm_bridge DB table. """ use GenServer @@ -11,16 +11,16 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do import Ecto.Query import Explorer.Helper, only: [parse_integer: 1] - import Indexer.Fetcher.Zkevm.Bridge, + import Indexer.Fetcher.PolygonZkevm.Bridge, only: [get_logs_all: 3, import_operations: 1, prepare_operations: 3] - alias Explorer.Chain.Zkevm.{Bridge, Reader} + alias Explorer.Chain.PolygonZkevm.{Bridge, Reader} alias Explorer.Repo alias Indexer.Fetcher.RollupL1ReorgMonitor alias Indexer.Helper @eth_get_logs_range_size 1000 - @fetcher_name :zkevm_bridge_l1 + @fetcher_name :polygon_zkevm_bridge_l1 def child_spec(start_link_arguments) do spec = %{ @@ -100,7 +100,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do {:stop, :normal, %{}} {:start_block_valid, false, last_l1_block_number, safe_block} -> - Logger.error("Invalid L1 Start Block value. Please, check the value and zkevm_bridge table.") + Logger.error("Invalid L1 Start Block value. Please, check the value and polygon_zkevm_bridge table.") Logger.error("last_l1_block_number = #{inspect(last_l1_block_number)}") Logger.error("safe_block = #{inspect(safe_block)}") {:stop, :normal, %{}} @@ -114,7 +114,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do {:l1_tx_not_found, true} -> Logger.error( - "Cannot find last L1 transaction from RPC by its hash. Probably, there was a reorg on L1 chain. Please, check zkevm_bridge table." + "Cannot find last L1 transaction from RPC by its hash. Probably, there was a reorg on L1 chain. Please, check polygon_zkevm_bridge table." ) {:stop, :normal, %{}} @@ -203,7 +203,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1 do if deleted_count > 0 do Logger.warning( - "As L1 reorg was detected, some deposits with block_number >= #{reorg_block} were removed from zkevm_bridge table. Number of removed rows: #{deleted_count}." + "As L1 reorg was detected, some deposits with block_number >= #{reorg_block} were removed from polygon_zkevm_bridge table. Number of removed rows: #{deleted_count}." ) end end diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1_tokens.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1_tokens.ex similarity index 90% rename from apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1_tokens.ex rename to apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1_tokens.ex index 59e6f9890819..034298174f78 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l1_tokens.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1_tokens.ex @@ -1,4 +1,4 @@ -defmodule Indexer.Fetcher.Zkevm.BridgeL1Tokens do +defmodule Indexer.Fetcher.PolygonZkevm.BridgeL1Tokens do @moduledoc """ Fetches information about L1 tokens for zkEVM bridge. """ @@ -10,7 +10,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1Tokens do alias Explorer.Repo alias Indexer.{BufferedTask, Helper} - alias Indexer.Fetcher.Zkevm.{Bridge, BridgeL1} + alias Indexer.Fetcher.PolygonZkevm.{Bridge, BridgeL1} @behaviour BufferedTask @@ -41,7 +41,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL1Tokens do |> Bridge.token_addresses_to_ids(json_rpc_named_arguments) |> Enum.each(fn {l1_token_address, l1_token_id} -> Repo.update_all( - from(b in Explorer.Chain.Zkevm.Bridge, where: b.l1_token_address == ^l1_token_address), + from(b in Explorer.Chain.PolygonZkevm.Bridge, where: b.l1_token_address == ^l1_token_address), set: [l1_token_id: l1_token_id, l1_token_address: nil] ) end) diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l2.ex similarity index 91% rename from apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex rename to apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l2.ex index c469602be163..f8d7695cd91a 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/bridge_l2.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l2.ex @@ -1,6 +1,6 @@ -defmodule Indexer.Fetcher.Zkevm.BridgeL2 do +defmodule Indexer.Fetcher.PolygonZkevm.BridgeL2 do @moduledoc """ - Fills zkevm_bridge DB table. + Fills polygon_zkevm_bridge DB table. """ use GenServer @@ -11,15 +11,15 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL2 do import Ecto.Query import Explorer.Helper, only: [parse_integer: 1] - import Indexer.Fetcher.Zkevm.Bridge, + import Indexer.Fetcher.PolygonZkevm.Bridge, only: [get_logs_all: 3, import_operations: 1, prepare_operations: 3] - alias Explorer.Chain.Zkevm.{Bridge, Reader} + alias Explorer.Chain.PolygonZkevm.{Bridge, Reader} alias Explorer.Repo alias Indexer.Helper @eth_get_logs_range_size 1000 - @fetcher_name :zkevm_bridge_l2 + @fetcher_name :polygon_zkevm_bridge_l2 def child_spec(start_link_arguments) do spec = %{ @@ -55,7 +55,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL2 do env = Application.get_all_env(:indexer)[__MODULE__] with {:start_block_undefined, false} <- {:start_block_undefined, is_nil(env[:start_block])}, - rpc_l1 = Application.get_all_env(:indexer)[Indexer.Fetcher.Zkevm.BridgeL1][:rpc], + rpc_l1 = Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonZkevm.BridgeL1][:rpc], {:rpc_l1_undefined, false} <- {:rpc_l1_undefined, is_nil(rpc_l1)}, {:bridge_contract_address_is_valid, true} <- {:bridge_contract_address_is_valid, Helper.address_correct?(env[:bridge_contract])}, @@ -93,7 +93,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL2 do {:stop, :normal, state} {:start_block_valid, false} -> - Logger.error("Invalid L2 Start Block value. Please, check the value and zkevm_bridge table.") + Logger.error("Invalid L2 Start Block value. Please, check the value and polygon_zkevm_bridge table.") {:stop, :normal, state} {:error, error_data} -> @@ -105,7 +105,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL2 do {:l2_tx_not_found, true} -> Logger.error( - "Cannot find last L2 transaction from RPC by its hash. Probably, there was a reorg on L2 chain. Please, check zkevm_bridge table." + "Cannot find last L2 transaction from RPC by its hash. Probably, there was a reorg on L2 chain. Please, check polygon_zkevm_bridge table." ) {:stop, :normal, state} @@ -169,7 +169,7 @@ defmodule Indexer.Fetcher.Zkevm.BridgeL2 do if deleted_count > 0 do Logger.warning( - "As L2 reorg was detected, some withdrawals with block_number >= #{reorg_block} were removed from zkevm_bridge table. Number of removed rows: #{deleted_count}." + "As L2 reorg was detected, some withdrawals with block_number >= #{reorg_block} were removed from polygon_zkevm_bridge table. Number of removed rows: #{deleted_count}." ) end end diff --git a/apps/indexer/lib/indexer/fetcher/zkevm/transaction_batch.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex similarity index 95% rename from apps/indexer/lib/indexer/fetcher/zkevm/transaction_batch.ex rename to apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex index 40115826da8a..278682628fe8 100644 --- a/apps/indexer/lib/indexer/fetcher/zkevm/transaction_batch.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex @@ -1,6 +1,6 @@ -defmodule Indexer.Fetcher.Zkevm.TransactionBatch do +defmodule Indexer.Fetcher.PolygonZkevm.TransactionBatch do @moduledoc """ - Fills zkevm_transaction_batches DB table. + Fills polygon_zkevm_transaction_batches DB table. """ use GenServer @@ -12,7 +12,7 @@ defmodule Indexer.Fetcher.Zkevm.TransactionBatch do alias Explorer.Chain alias Explorer.Chain.Events.Publisher - alias Explorer.Chain.Zkevm.Reader + alias Explorer.Chain.PolygonZkevm.Reader alias Indexer.Helper @zero_hash "0000000000000000000000000000000000000000000000000000000000000000" @@ -34,9 +34,9 @@ defmodule Indexer.Fetcher.Zkevm.TransactionBatch do @impl GenServer def init(args) do - Logger.metadata(fetcher: :zkevm_transaction_batches) + Logger.metadata(fetcher: :polygon_zkevm_transaction_batches) - config = Application.get_all_env(:indexer)[Indexer.Fetcher.Zkevm.TransactionBatch] + config = Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonZkevm.TransactionBatch] chunk_size = config[:chunk_size] recheck_interval = config[:recheck_interval] @@ -251,9 +251,9 @@ defmodule Indexer.Fetcher.Zkevm.TransactionBatch do {:ok, _} = Chain.import(%{ - zkevm_lifecycle_transactions: %{params: l1_txs_to_import}, - zkevm_transaction_batches: %{params: batches_to_import}, - zkevm_batch_transactions: %{params: l2_txs_to_import}, + polygon_zkevm_lifecycle_transactions: %{params: l1_txs_to_import}, + polygon_zkevm_transaction_batches: %{params: batches_to_import}, + polygon_zkevm_batch_transactions: %{params: l2_txs_to_import}, timeout: :infinity }) diff --git a/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex b/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex index 6499ccab1ef3..b9a5e8e4ec67 100644 --- a/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex +++ b/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex @@ -34,8 +34,8 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do modules_can_use_reorg_monitor = [ Indexer.Fetcher.PolygonEdge.Deposit, Indexer.Fetcher.PolygonEdge.WithdrawalExit, - Indexer.Fetcher.Shibarium.L1, - Indexer.Fetcher.Zkevm.BridgeL1 + Indexer.Fetcher.PolygonZkevm.BridgeL1, + Indexer.Fetcher.Shibarium.L1 ] modules_using_reorg_monitor = @@ -52,7 +52,7 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do # As there cannot be different modules for different rollups at the same time, # it's correct to only get the first item of the list. # For example, Indexer.Fetcher.PolygonEdge.Deposit and Indexer.Fetcher.PolygonEdge.WithdrawalExit can be in the list - # because they are for the same rollup, but Indexer.Fetcher.Shibarium.L1 and Indexer.Fetcher.Zkevm.BridgeL1 cannot (as they are for different rollups). + # because they are for the same rollup, but Indexer.Fetcher.Shibarium.L1 and Indexer.Fetcher.PolygonZkevm.BridgeL1 cannot (as they are for different rollups). module_using_reorg_monitor = Enum.at(modules_using_reorg_monitor, 0) l1_rpc = diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index d8f32eed81fa..0c338fdecc50 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -144,12 +144,12 @@ defmodule Indexer.Supervisor do [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), configure(Indexer.Fetcher.Shibarium.L1.Supervisor, [[memory_monitor: memory_monitor]]), - configure(Indexer.Fetcher.Zkevm.BridgeL1.Supervisor, [[memory_monitor: memory_monitor]]), - configure(Indexer.Fetcher.Zkevm.BridgeL1Tokens.Supervisor, [[memory_monitor: memory_monitor]]), - configure(Indexer.Fetcher.Zkevm.BridgeL2.Supervisor, [ + configure(Indexer.Fetcher.PolygonZkevm.BridgeL1.Supervisor, [[memory_monitor: memory_monitor]]), + configure(Indexer.Fetcher.PolygonZkevm.BridgeL1Tokens.Supervisor, [[memory_monitor: memory_monitor]]), + configure(Indexer.Fetcher.PolygonZkevm.BridgeL2.Supervisor, [ [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), - configure(Indexer.Fetcher.Zkevm.TransactionBatch.Supervisor, [ + configure(Indexer.Fetcher.PolygonZkevm.TransactionBatch.Supervisor, [ [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), {Indexer.Fetcher.Beacon.Blob.Supervisor, [[memory_monitor: memory_monitor]]}, diff --git a/apps/indexer/lib/indexer/transform/addresses.ex b/apps/indexer/lib/indexer/transform/addresses.ex index aae8fbbb8d49..543352d123e9 100644 --- a/apps/indexer/lib/indexer/transform/addresses.ex +++ b/apps/indexer/lib/indexer/transform/addresses.ex @@ -149,7 +149,7 @@ defmodule Indexer.Transform.Addresses do %{from: :address_hash, to: :hash} ] ], - zkevm_bridge_operations: [ + polygon_zkevm_bridge_operations: [ [ %{from: :l2_token_address, to: :hash} ] @@ -461,7 +461,7 @@ defmodule Indexer.Transform.Addresses do required(:block_number) => non_neg_integer() } ], - optional(:zkevm_bridge_operations) => [ + optional(:polygon_zkevm_bridge_operations) => [ %{ optional(:l2_token_address) => String.t() } diff --git a/apps/indexer/lib/indexer/transform/zkevm/bridge.ex b/apps/indexer/lib/indexer/transform/polygon_zkevm/bridge.ex similarity index 91% rename from apps/indexer/lib/indexer/transform/zkevm/bridge.ex rename to apps/indexer/lib/indexer/transform/polygon_zkevm/bridge.ex index 2d23fcb2e090..441e6950d132 100644 --- a/apps/indexer/lib/indexer/transform/zkevm/bridge.ex +++ b/apps/indexer/lib/indexer/transform/polygon_zkevm/bridge.ex @@ -1,14 +1,14 @@ -defmodule Indexer.Transform.Zkevm.Bridge do +defmodule Indexer.Transform.PolygonZkevm.Bridge do @moduledoc """ Helper functions for transforming data for Polygon zkEVM Bridge operations. """ require Logger - import Indexer.Fetcher.Zkevm.Bridge, + import Indexer.Fetcher.PolygonZkevm.Bridge, only: [filter_bridge_events: 2, prepare_operations: 4] - alias Indexer.Fetcher.Zkevm.{BridgeL1, BridgeL2} + alias Indexer.Fetcher.PolygonZkevm.{BridgeL1, BridgeL2} alias Indexer.Helper @doc """ @@ -17,7 +17,7 @@ defmodule Indexer.Transform.Zkevm.Bridge do @spec parse(list(), list()) :: list() def parse(blocks, logs) do prev_metadata = Logger.metadata() - Logger.metadata(fetcher: :zkevm_bridge_l2_realtime) + Logger.metadata(fetcher: :polygon_zkevm_bridge_l2_realtime) items = with false <- is_nil(Application.get_env(:indexer, BridgeL2)[:start_block]), diff --git a/config/runtime.exs b/config/runtime.exs index 78b5e3f78230..89271ad44e46 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -725,27 +725,29 @@ config :indexer, Indexer.Fetcher.Shibarium.L1.Supervisor, enabled: ConfigHelper. config :indexer, Indexer.Fetcher.Shibarium.L2.Supervisor, enabled: ConfigHelper.chain_type() == "shibarium" -config :indexer, Indexer.Fetcher.Zkevm.BridgeL1, +config :indexer, Indexer.Fetcher.PolygonZkevm.BridgeL1, rpc: System.get_env("INDEXER_POLYGON_ZKEVM_L1_RPC"), start_block: System.get_env("INDEXER_POLYGON_ZKEVM_L1_BRIDGE_START_BLOCK"), bridge_contract: System.get_env("INDEXER_POLYGON_ZKEVM_L1_BRIDGE_CONTRACT"), native_symbol: System.get_env("INDEXER_POLYGON_ZKEVM_L1_BRIDGE_NATIVE_SYMBOL", "ETH"), native_decimals: ConfigHelper.parse_integer_env_var("INDEXER_POLYGON_ZKEVM_L1_BRIDGE_NATIVE_DECIMALS", 18) -config :indexer, Indexer.Fetcher.Zkevm.BridgeL1.Supervisor, enabled: ConfigHelper.chain_type() == "polygon_zkevm" -config :indexer, Indexer.Fetcher.Zkevm.BridgeL1Tokens.Supervisor, enabled: ConfigHelper.chain_type() == "polygon_zkevm" +config :indexer, Indexer.Fetcher.PolygonZkevm.BridgeL1.Supervisor, enabled: ConfigHelper.chain_type() == "polygon_zkevm" -config :indexer, Indexer.Fetcher.Zkevm.BridgeL2, +config :indexer, Indexer.Fetcher.PolygonZkevm.BridgeL1Tokens.Supervisor, + enabled: ConfigHelper.chain_type() == "polygon_zkevm" + +config :indexer, Indexer.Fetcher.PolygonZkevm.BridgeL2, start_block: System.get_env("INDEXER_POLYGON_ZKEVM_L2_BRIDGE_START_BLOCK"), bridge_contract: System.get_env("INDEXER_POLYGON_ZKEVM_L2_BRIDGE_CONTRACT") -config :indexer, Indexer.Fetcher.Zkevm.BridgeL2.Supervisor, enabled: ConfigHelper.chain_type() == "polygon_zkevm" +config :indexer, Indexer.Fetcher.PolygonZkevm.BridgeL2.Supervisor, enabled: ConfigHelper.chain_type() == "polygon_zkevm" -config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch, +config :indexer, Indexer.Fetcher.PolygonZkevm.TransactionBatch, chunk_size: ConfigHelper.parse_integer_env_var("INDEXER_POLYGON_ZKEVM_BATCHES_CHUNK_SIZE", 20), recheck_interval: ConfigHelper.parse_integer_env_var("INDEXER_POLYGON_ZKEVM_BATCHES_RECHECK_INTERVAL", 60) -config :indexer, Indexer.Fetcher.Zkevm.TransactionBatch.Supervisor, +config :indexer, Indexer.Fetcher.PolygonZkevm.TransactionBatch.Supervisor, enabled: ConfigHelper.chain_type() == "polygon_zkevm" && ConfigHelper.parse_bool_env_var("INDEXER_POLYGON_ZKEVM_BATCHES_ENABLED") From cef2819e107dfcc1ebdfd5efe3760655eec0c189 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Wed, 14 Feb 2024 13:58:36 +0300 Subject: [PATCH 459/607] Fix using of startblock/endblock in API v1 list endpoints: txlist, txlistinternal, tokentx (#9364) * Fix using of startblock/endblock in API v1 list endpoints: txlist, txlistinternal, tokentx * Add CHANGELOG entry --- CHANGELOG.md | 1 + .../controllers/api/rpc/address_controller.ex | 4 +- .../lib/block_scout_web/etherscan.ex | 12 +++--- .../api/rpc/address_controller_test.exs | 40 +++++++++---------- apps/explorer/lib/explorer/etherscan.ex | 28 ++++++------- .../explorer/test/explorer/etherscan_test.exs | 28 ++++++------- 6 files changed, 57 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02d8bbdbada1..b88a825e3445 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - [#9379](https://github.com/blockscout/blockscout/pull/9379) - Filter non-traceable transactions for zetachain +- [#9364](https://github.com/blockscout/blockscout/pull/9364) - Fix using of startblock/endblock in API v1 list endpoints: txlist, txlistinternal, tokentx - [#9351](https://github.com/blockscout/blockscout/pull/9351) - Noves.fi: add proxy endpoint for describeTxs endpoint - [#9282](https://github.com/blockscout/blockscout/pull/9282) - Add `license_type` to smart contracts - [#9202](https://github.com/blockscout/blockscout/pull/9202) - Add base and priority fee to gas oracle response diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex index 98d6d8d8f782..ff81d7fc2da0 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex @@ -281,8 +281,8 @@ defmodule BlockScoutWeb.API.RPC.AddressController do %{} |> put_order_by_direction(params) |> Helper.put_pagination_options(params) - |> put_block(params, "start_block") - |> put_block(params, "end_block") + |> put_block(params, "startblock") + |> put_block(params, "endblock") |> put_filter_by(params) |> put_timestamp(params, "start_timestamp") |> put_timestamp(params, "end_timestamp") diff --git a/apps/block_scout_web/lib/block_scout_web/etherscan.ex b/apps/block_scout_web/lib/block_scout_web/etherscan.ex index 74b1c3cfeeb1..e477eb9dca5c 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -1420,12 +1420,12 @@ defmodule BlockScoutWeb.Etherscan do "A string representing the order by block number direction. Defaults to descending order. Available values: asc, desc" }, %{ - key: "start_block", + key: "startblock", type: "integer", description: "A nonnegative integer that represents the starting block number." }, %{ - key: "end_block", + key: "endblock", type: "integer", description: "A nonnegative integer that represents the ending block number." }, @@ -1513,13 +1513,13 @@ defmodule BlockScoutWeb.Etherscan do "A string representing the order by block number direction. Defaults to ascending order. Available values: asc, desc. WARNING: Only available if 'address' is provided." }, %{ - key: "start_block", + key: "startblock", type: "integer", description: "A nonnegative integer that represents the starting block number. WARNING: Only available if 'address' is provided." }, %{ - key: "end_block", + key: "endblock", type: "integer", description: "A nonnegative integer that represents the ending block number. WARNING: Only available if 'address' is provided." @@ -1588,12 +1588,12 @@ defmodule BlockScoutWeb.Etherscan do "A string representing the order by block number direction. Defaults to ascending order. Available values: asc, desc" }, %{ - key: "start_block", + key: "startblock", type: "integer", description: "A nonnegative integer that represents the starting block number." }, %{ - key: "end_block", + key: "endblock", type: "integer", description: "A nonnegative integer that represents the ending block number." }, diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs index 4d14edab7578..5a9475258e3f 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs @@ -1090,7 +1090,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with start_block and end_block params", %{conn: conn} do + test "with startblock and endblock params", %{conn: conn} do blocks = [_, second_block, third_block, _] = insert_list(4, :block) address = insert(:address) @@ -1104,8 +1104,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "start_block" => "#{second_block.number}", - "end_block" => "#{third_block.number}" + "startblock" => "#{second_block.number}", + "endblock" => "#{third_block.number}" } expected_block_numbers = [ @@ -1129,7 +1129,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with start_block but without end_block", %{conn: conn} do + test "with startblock but without endblock", %{conn: conn} do blocks = [_, _, third_block, fourth_block] = insert_list(4, :block) address = insert(:address) @@ -1143,7 +1143,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "start_block" => "#{third_block.number}" + "startblock" => "#{third_block.number}" } expected_block_numbers = [ @@ -1167,7 +1167,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "with end_block but without start_block", %{conn: conn} do + test "with endblock but without startblock", %{conn: conn} do blocks = [first_block, second_block, _, _] = insert_list(4, :block) address = insert(:address) @@ -1181,7 +1181,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "end_block" => "#{second_block.number}" + "endblock" => "#{second_block.number}" } expected_block_numbers = [ @@ -1205,7 +1205,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert :ok = ExJsonSchema.Validator.validate(txlist_schema(), response) end - test "ignores invalid start_block and end_block", %{conn: conn} do + test "ignores invalid startblock and endblock", %{conn: conn} do blocks = [_, _, _, _] = insert_list(4, :block) address = insert(:address) @@ -1219,8 +1219,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "module" => "account", "action" => "txlist", "address" => "#{address.hash}", - "start_block" => "invalidstart", - "end_block" => "invalidend" + "startblock" => "invalidstart", + "endblock" => "invalidend" } assert response = @@ -3108,8 +3108,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do describe "optional_params/1" do test "includes valid optional params in the required format" do params = %{ - "start_block" => "100", - "end_block" => "120", + "startblock" => "100", + "endblock" => "120", "sort" => "asc", # page number "page" => "1", @@ -3128,8 +3128,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert optional_params.page_number == 1 assert optional_params.page_size == 2 assert optional_params.order_by_direction == :asc - assert optional_params.start_block == 100 - assert optional_params.end_block == 120 + assert optional_params.startblock == 100 + assert optional_params.endblock == 120 assert optional_params.filter_by == "to" assert optional_params.start_timestamp == expected_timestamp assert optional_params.end_timestamp == expected_timestamp @@ -3177,8 +3177,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do test "ignores invalid optional params, keeps valid ones" do params1 = %{ - "start_block" => "invalid", - "end_block" => "invalid", + "startblock" => "invalid", + "endblock" => "invalid", "sort" => "invalid", "page" => "invalid", "offset" => "invalid", @@ -3189,8 +3189,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert AddressController.optional_params(params1) == %{} params2 = %{ - "start_block" => "4", - "end_block" => "10", + "startblock" => "4", + "endblock" => "10", "sort" => "invalid", "page" => "invalid", "offset" => "invalid", @@ -3200,8 +3200,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do optional_params = AddressController.optional_params(params2) - assert optional_params.start_block == 4 - assert optional_params.end_block == 10 + assert optional_params.startblock == 4 + assert optional_params.endblock == 10 end test "ignores 'page' if less than 1" do diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index da00577498b8..67e51f75e9a4 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -18,8 +18,8 @@ defmodule Explorer.Etherscan do order_by_direction: :desc, page_number: 1, page_size: 10_000, - start_block: nil, - end_block: nil, + startblock: nil, + endblock: nil, start_timestamp: nil, end_timestamp: nil } @@ -640,21 +640,21 @@ defmodule Explorer.Etherscan do |> Repo.replica().all() end - defp where_start_block_match(query, %{start_block: nil}), do: query + defp where_start_block_match(query, %{startblock: nil}), do: query - defp where_start_block_match(query, %{start_block: start_block}) do + defp where_start_block_match(query, %{startblock: start_block}) do where(query, [..., block], block.number >= ^start_block) end - defp where_end_block_match(query, %{end_block: nil}), do: query + defp where_end_block_match(query, %{endblock: nil}), do: query - defp where_end_block_match(query, %{end_block: end_block}) do + defp where_end_block_match(query, %{endblock: end_block}) 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, %{startblock: nil}), do: query - defp where_start_transaction_block_match(query, %{start_block: start_block} = params) do + defp where_start_transaction_block_match(query, %{startblock: start_block} = params) do if DenormalizationHelper.denormalization_finished?() do where(query, [transaction], transaction.block_number >= ^start_block) else @@ -662,9 +662,9 @@ defmodule Explorer.Etherscan do end end - defp where_end_transaction_block_match(query, %{end_block: nil}), do: query + defp where_end_transaction_block_match(query, %{endblock: nil}), do: query - defp where_end_transaction_block_match(query, %{end_block: end_block} = params) do + defp where_end_transaction_block_match(query, %{endblock: end_block} = params) do if DenormalizationHelper.denormalization_finished?() do where(query, [transaction], transaction.block_number <= ^end_block) else @@ -672,15 +672,15 @@ defmodule Explorer.Etherscan do end end - defp where_start_block_match_tt(query, %{start_block: nil}), do: query + defp where_start_block_match_tt(query, %{startblock: nil}), do: query - defp where_start_block_match_tt(query, %{start_block: start_block}) do + defp where_start_block_match_tt(query, %{startblock: start_block}) do where(query, [tt], tt.block_number >= ^start_block) end - defp where_end_block_match_tt(query, %{end_block: nil}), do: query + defp where_end_block_match_tt(query, %{endblock: nil}), do: query - defp where_end_block_match_tt(query, %{end_block: end_block}) do + defp where_end_block_match_tt(query, %{endblock: end_block}) do where(query, [tt], tt.block_number <= ^end_block) end diff --git a/apps/explorer/test/explorer/etherscan_test.exs b/apps/explorer/test/explorer/etherscan_test.exs index 7d188cd98f49..37b6e685d618 100644 --- a/apps/explorer/test/explorer/etherscan_test.exs +++ b/apps/explorer/test/explorer/etherscan_test.exs @@ -294,8 +294,8 @@ defmodule Explorer.EtherscanTest do end options = %{ - start_block: second_block.number, - end_block: third_block.number + startblock: second_block.number, + endblock: third_block.number } found_transactions = Etherscan.list_transactions(address.hash, options) @@ -309,7 +309,7 @@ defmodule Explorer.EtherscanTest do end end - test "with start_block but no end_block option" do + test "with startblock but no endblock option" do blocks = [_, _, third_block, fourth_block] = insert_list(4, :block) address = insert(:address) @@ -320,7 +320,7 @@ defmodule Explorer.EtherscanTest do end options = %{ - start_block: third_block.number + startblock: third_block.number } found_transactions = Etherscan.list_transactions(address.hash, options) @@ -334,7 +334,7 @@ defmodule Explorer.EtherscanTest do end end - test "with end_block but no start_block option" do + test "with endblock but no startblock option" do blocks = [first_block, second_block, _, _] = insert_list(4, :block) address = insert(:address) @@ -345,7 +345,7 @@ defmodule Explorer.EtherscanTest do end options = %{ - end_block: second_block.number + endblock: second_block.number } found_transactions = Etherscan.list_transactions(address.hash, options) @@ -973,8 +973,8 @@ defmodule Explorer.EtherscanTest do end options = %{ - start_block: second_block.number, - end_block: third_block.number + startblock: second_block.number, + endblock: third_block.number } found_internal_transactions = Etherscan.list_internal_transactions(address.hash, options) @@ -1365,8 +1365,8 @@ defmodule Explorer.EtherscanTest do end options = %{ - start_block: second_block.number, - end_block: third_block.number + startblock: second_block.number, + endblock: third_block.number } found_token_transfers = Etherscan.list_token_transfers(address.hash, nil, options) @@ -1380,7 +1380,7 @@ defmodule Explorer.EtherscanTest do end end - test "with start_block but no end_block option" do + test "with startblock but no endblock option" do blocks = [_, _, third_block, fourth_block] = insert_list(4, :block) address = insert(:address) @@ -1398,7 +1398,7 @@ defmodule Explorer.EtherscanTest do ) end - options = %{start_block: third_block.number} + options = %{startblock: third_block.number} found_token_transfers = Etherscan.list_token_transfers(address.hash, nil, options) @@ -1411,7 +1411,7 @@ defmodule Explorer.EtherscanTest do end end - test "with end_block but no start_block option" do + test "with endblock but no startblock option" do blocks = [first_block, second_block, _, _] = insert_list(4, :block) address = insert(:address) @@ -1429,7 +1429,7 @@ defmodule Explorer.EtherscanTest do ) end - options = %{end_block: second_block.number} + options = %{endblock: second_block.number} found_token_transfers = Etherscan.list_token_transfers(address.hash, nil, options) From c8167be818d671ddb0569a4e8151dd349ce1600b Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Wed, 14 Feb 2024 07:00:04 -0500 Subject: [PATCH 460/607] chore: bump actions/cache to v4 (#9393) * chore: bump actions/cache to v4 * chore: changelog --- .github/workflows/config.yml | 48 ++++++++++++++++++------------------ CHANGELOG.md | 1 + 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index bdbc610ff3db..e05c82eb367d 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -67,7 +67,7 @@ jobs: run: echo "${OTP_VERSION}" > OTP_VERSION.lock - name: Restore Mix Deps Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: deps-cache with: path: | @@ -86,7 +86,7 @@ jobs: mix deps.compile - name: Restore Explorer NPM Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: explorer-npm-cache with: path: apps/explorer/node_modules @@ -100,7 +100,7 @@ jobs: working-directory: apps/explorer - name: Restore Blockscout Web NPM Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: blockscoutweb-npm-cache with: path: apps/block_scout_web/assets/node_modules @@ -125,7 +125,7 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Restore Mix Deps Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: deps-cache with: path: | @@ -149,7 +149,7 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Restore Mix Deps Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: deps-cache with: path: | @@ -178,7 +178,7 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Restore Mix Deps Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: deps-cache with: path: | @@ -189,7 +189,7 @@ jobs: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" - name: Restore Dialyzer Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: dialyzer-cache with: path: priv/plts @@ -222,7 +222,7 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Restore Mix Deps Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: deps-cache with: path: | @@ -248,7 +248,7 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Mix Deps Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: deps-cache with: path: | @@ -277,7 +277,7 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Mix Deps Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: deps-cache with: path: | @@ -288,7 +288,7 @@ jobs: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" - name: Restore Explorer NPM Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: explorer-npm-cache with: path: apps/explorer/node_modules @@ -297,7 +297,7 @@ jobs: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm- - name: Restore Blockscout Web NPM Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: blockscoutweb-npm-cache with: path: apps/block_scout_web/assets/node_modules @@ -325,7 +325,7 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Mix Deps Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: deps-cache with: path: | @@ -336,7 +336,7 @@ jobs: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" - name: Restore Explorer NPM Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: explorer-npm-cache with: path: apps/explorer/node_modules @@ -345,7 +345,7 @@ jobs: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm- - name: Restore Blockscout Web NPM Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: blockscoutweb-npm-cache with: path: apps/block_scout_web/assets/node_modules @@ -371,7 +371,7 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Mix Deps Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: deps-cache with: path: | @@ -382,7 +382,7 @@ jobs: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" - name: Restore Blockscout Web NPM Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: blockscoutweb-npm-cache with: path: apps/block_scout_web/assets/node_modules @@ -433,7 +433,7 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Mix Deps Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: deps-cache with: path: | @@ -493,7 +493,7 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Mix Deps Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: deps-cache with: path: | @@ -504,7 +504,7 @@ jobs: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" - name: Restore Explorer NPM Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: explorer-npm-cache with: path: apps/explorer/node_modules @@ -564,7 +564,7 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Mix Deps Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: deps-cache with: path: | @@ -632,7 +632,7 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Mix Deps Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: deps-cache with: path: | @@ -643,7 +643,7 @@ jobs: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" - name: Restore Explorer NPM Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: explorer-npm-cache with: path: apps/explorer/node_modules @@ -652,7 +652,7 @@ jobs: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm- - name: Restore Blockscout Web NPM Cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: blockscoutweb-npm-cache with: path: apps/block_scout_web/assets/node_modules diff --git a/CHANGELOG.md b/CHANGELOG.md index b88a825e3445..e9213f098654 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ ### Chore +- [#9393](https://github.com/blockscout/blockscout/pull/9393) - Bump actions/cache to v4 - [#9361](https://github.com/blockscout/blockscout/pull/9361) - Define BRIDGED_TOKENS_ENABLED env in Dockerfile - [#8851](https://github.com/blockscout/blockscout/pull/8851) - Fix dialyzer and add TypedEctoSchema From 0e4b14bff6b2e08325ac9cece47c9f525b1f55fc Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Wed, 14 Feb 2024 17:19:18 +0300 Subject: [PATCH 461/607] More-Minimal Proxy support (#9396) * Minimal Proxy (#9365) Co-authored-by: gimlu * Add CHANGELOG, fix tests * Clear GA cache and refactoring --------- Co-authored-by: GimluCom <79271880+GimluCom@users.noreply.github.com> Co-authored-by: gimlu --- .github/workflows/config.yml | 26 +++++++++---------- CHANGELOG.md | 1 + .../chain/smart_contract/proxy/eip_1167.ex | 6 ++++- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index e05c82eb367d..81942ae3d61e 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -73,7 +73,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps- @@ -131,7 +131,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -155,7 +155,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -184,7 +184,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -228,7 +228,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -254,7 +254,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -283,7 +283,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -331,7 +331,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -377,7 +377,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -439,7 +439,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -499,7 +499,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -570,7 +570,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -638,7 +638,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_39-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" diff --git a/CHANGELOG.md b/CHANGELOG.md index e9213f098654..8a5407254936 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#9396](https://github.com/blockscout/blockscout/pull/9396) - More-Minimal Proxy support - [#9379](https://github.com/blockscout/blockscout/pull/9379) - Filter non-traceable transactions for zetachain - [#9364](https://github.com/blockscout/blockscout/pull/9364) - Fix using of startblock/endblock in API v1 list endpoints: txlist, txlistinternal, tokentx - [#9351](https://github.com/blockscout/blockscout/pull/9351) - Noves.fi: add proxy endpoint for describeTxs endpoint diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex index 2396c5419244..acf503c1503e 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex @@ -43,7 +43,11 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1167 do defp get_proxy_eip_1167(contract_bytecode) do case contract_bytecode do - "363d3d373d3d3d363d73" <> <> <> _ -> + "363d3d373d3d3d363d73" <> <> <> "5af43d82803e903d91602b57fd5bf3" -> + "0x" <> template_address + + # https://medium.com/coinmonks/the-more-minimal-proxy-5756ae08ee48 + "3d3d3d3d363d3d37363d73" <> <> <> "5af43d3d93803e602a57fd5bf3" -> "0x" <> template_address _ -> From 4abc3df4b975e6bc123fd5a6edaaa8b01dad219b Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Thu, 8 Feb 2024 15:04:31 +0400 Subject: [PATCH 462/607] Move missing ranges sanitize to a separate background migration --- CHANGELOG.md | 1 + apps/explorer/config/config.exs | 1 + apps/explorer/config/runtime/test.exs | 1 + apps/explorer/lib/explorer/application.ex | 3 +- .../migrator/sanitize_missing_block_ranges.ex | 34 +++++++++++++++++++ .../explorer/utility/missing_block_range.ex | 4 +-- .../block/catchup/missing_ranges_collector.ex | 2 -- 7 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 apps/explorer/lib/explorer/migrator/sanitize_missing_block_ranges.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a5407254936..a2bdc8b810d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [#9396](https://github.com/blockscout/blockscout/pull/9396) - More-Minimal Proxy support - [#9379](https://github.com/blockscout/blockscout/pull/9379) - Filter non-traceable transactions for zetachain - [#9364](https://github.com/blockscout/blockscout/pull/9364) - Fix using of startblock/endblock in API v1 list endpoints: txlist, txlistinternal, tokentx +- [#9360](https://github.com/blockscout/blockscout/pull/9360) - Move missing ranges sanitize to a separate background migration - [#9351](https://github.com/blockscout/blockscout/pull/9351) - Noves.fi: add proxy endpoint for describeTxs endpoint - [#9282](https://github.com/blockscout/blockscout/pull/9282) - Add `license_type` to smart contracts - [#9202](https://github.com/blockscout/blockscout/pull/9202) - Add base and priority fee to gas oracle response diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 117ca2c0ab4a..9e75a6926ea8 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -114,6 +114,7 @@ config :explorer, Explorer.TokenInstanceOwnerAddressMigration.Supervisor, enable config :explorer, Explorer.Migrator.TransactionsDenormalization, enabled: true config :explorer, Explorer.Migrator.AddressCurrentTokenBalanceTokenType, enabled: true config :explorer, Explorer.Migrator.AddressTokenBalanceTokenType, enabled: true +config :explorer, Explorer.Migrator.SanitizeMissingBlockRanges, enabled: true config :explorer, Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand, enabled: true diff --git a/apps/explorer/config/runtime/test.exs b/apps/explorer/config/runtime/test.exs index ec346c1d3f89..da3989383653 100644 --- a/apps/explorer/config/runtime/test.exs +++ b/apps/explorer/config/runtime/test.exs @@ -38,6 +38,7 @@ config :explorer, Explorer.TokenInstanceOwnerAddressMigration.Supervisor, enable config :explorer, Explorer.Migrator.TransactionsDenormalization, enabled: false config :explorer, Explorer.Migrator.AddressCurrentTokenBalanceTokenType, enabled: false config :explorer, Explorer.Migrator.AddressTokenBalanceTokenType, enabled: false +config :explorer, Explorer.Migrator.SanitizeMissingBlockRanges, enabled: false config :explorer, realtime_events_sender: Explorer.Chain.Events.SimpleSender diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 653e91eb4f3d..059d4e22140d 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -128,7 +128,8 @@ defmodule Explorer.Application do configure(Explorer.Chain.Cache.RootstockLockedBTC), configure(Explorer.Migrator.TransactionsDenormalization), configure(Explorer.Migrator.AddressCurrentTokenBalanceTokenType), - configure(Explorer.Migrator.AddressTokenBalanceTokenType) + configure(Explorer.Migrator.AddressTokenBalanceTokenType), + configure(Explorer.Migrator.SanitizeMissingBlockRanges) ] |> List.flatten() diff --git a/apps/explorer/lib/explorer/migrator/sanitize_missing_block_ranges.ex b/apps/explorer/lib/explorer/migrator/sanitize_missing_block_ranges.ex new file mode 100644 index 000000000000..29408229c021 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/sanitize_missing_block_ranges.ex @@ -0,0 +1,34 @@ +defmodule Explorer.Migrator.SanitizeMissingBlockRanges do + @moduledoc """ + Remove invalid missing block ranges (from_number < to_number and intersecting ones) + """ + + use GenServer, restart: :transient + + alias Explorer.Migrator.MigrationStatus + alias Explorer.Utility.MissingBlockRange + + @migration_name "sanitize_missing_ranges" + + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + def init(_) do + case MigrationStatus.get_status(@migration_name) do + "completed" -> + :ignore + + _ -> + MigrationStatus.set_status(@migration_name, "started") + {:ok, %{}, {:continue, :ok}} + end + end + + def handle_continue(:ok, state) do + MissingBlockRange.sanitize_missing_block_ranges() + MigrationStatus.set_status(@migration_name, "completed") + + {:stop, :normal, state} + end +end diff --git a/apps/explorer/lib/explorer/utility/missing_block_range.ex b/apps/explorer/lib/explorer/utility/missing_block_range.ex index 2a193c7108b1..537470a92d16 100644 --- a/apps/explorer/lib/explorer/utility/missing_block_range.ex +++ b/apps/explorer/lib/explorer/utility/missing_block_range.ex @@ -145,7 +145,7 @@ defmodule Explorer.Utility.MissingBlockRange do __MODULE__ |> where([r], r.from_number < r.to_number) |> update([r], set: [from_number: r.to_number, to_number: r.from_number]) - |> Repo.update_all([]) + |> Repo.update_all([], timeout: :infinity) {last_range, merged_ranges} = delete_and_merge_ranges() @@ -175,7 +175,7 @@ defmodule Explorer.Utility.MissingBlockRange do (r.to_number <= r1.from_number and r.to_number >= r1.to_number)) and r1.id != r.id ) |> select([r, r1], r) - |> Repo.delete_all() + |> Repo.delete_all(timeout: :infinity) intersecting_ranges end diff --git a/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex b/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex index 9c776610ae81..f8c97c3d72ef 100644 --- a/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex +++ b/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex @@ -44,8 +44,6 @@ defmodule Indexer.Block.Catchup.MissingRangesCollector do end defp default_init do - MissingBlockRange.sanitize_missing_block_ranges() - {min_number, max_number} = get_initial_min_max() clear_to_bounds(min_number, max_number) From 74cceb5d8416c061818c519585f98ef64a71708c Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Thu, 15 Feb 2024 03:38:06 -0500 Subject: [PATCH 463/607] Improve marking of failed internal transactions (#9306) * fix: marking parent transaction reverts * chore: changelog * fix: don't count itself as a parent --- CHANGELOG.md | 1 + .../indexer/fetcher/internal_transaction.ex | 47 +++++++++++-------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc1564852b28..717826a11ebf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - [#9346](https://github.com/blockscout/blockscout/pull/9346) - Process integer balance in genesis.json - [#9317](https://github.com/blockscout/blockscout/pull/9317) - Include null gas price txs in fee calculations - [#9315](https://github.com/blockscout/blockscout/pull/9315) - Fix manual uncle reward calculation +- [#9306](https://github.com/blockscout/blockscout/pull/9306) - Improve marking of failed internal transactions - [#9305](https://github.com/blockscout/blockscout/pull/9305) - Add effective gas price calculation as fallback - [#9300](https://github.com/blockscout/blockscout/pull/9300) - Fix read contract bug diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex index ad5dfa248205..12e9c4251535 100644 --- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex @@ -254,11 +254,11 @@ defmodule Indexer.Fetcher.InternalTransaction do end defp import_internal_transaction(internal_transactions_params, unique_numbers) do - internal_transactions_params_without_failed_creations = remove_failed_creations(internal_transactions_params) + internal_transactions_params_marked = mark_failed_transactions(internal_transactions_params) addresses_params = Addresses.extract_addresses(%{ - internal_transactions: internal_transactions_params_without_failed_creations + internal_transactions: internal_transactions_params_marked }) address_hash_to_block_number = @@ -269,11 +269,10 @@ defmodule Indexer.Fetcher.InternalTransaction do empty_block_numbers = unique_numbers |> MapSet.new() - |> MapSet.difference(MapSet.new(internal_transactions_params_without_failed_creations, & &1.block_number)) + |> MapSet.difference(MapSet.new(internal_transactions_params_marked, & &1.block_number)) |> Enum.map(&%{block_number: &1}) - internal_transactions_and_empty_block_numbers = - internal_transactions_params_without_failed_creations ++ empty_block_numbers + internal_transactions_and_empty_block_numbers = internal_transactions_params_marked ++ empty_block_numbers imports = Chain.import(%{ @@ -310,34 +309,42 @@ defmodule Indexer.Fetcher.InternalTransaction do end end - defp remove_failed_creations(internal_transactions_params) do + defp mark_failed_transactions(internal_transactions_params) do + # we store reversed trace addresses for more efficient list head-tail decomposition in has_failed_parent? + failed_parent_paths = + internal_transactions_params + |> Enum.filter(& &1[:error]) + |> Enum.map(&Enum.reverse([&1.transaction_hash | &1.trace_address])) + |> MapSet.new() + internal_transactions_params |> Enum.map(fn internal_transaction_param -> - transaction_index = internal_transaction_param[:transaction_index] - block_number = internal_transaction_param[:block_number] - - failed_parent = - internal_transactions_params - |> Enum.filter(fn internal_transactions_param -> - internal_transactions_param[:block_number] == block_number && - internal_transactions_param[:transaction_index] == transaction_index && - internal_transactions_param[:trace_address] == [] && !is_nil(internal_transactions_param[:error]) - end) - |> Enum.at(0) - - if failed_parent do + if has_failed_parent?( + failed_parent_paths, + internal_transaction_param.trace_address, + [internal_transaction_param.transaction_hash] + ) do + # TODO: consider keeping these deleted fields in the reverted transactions internal_transaction_param |> Map.delete(:created_contract_address_hash) |> Map.delete(:created_contract_code) |> Map.delete(:gas_used) |> Map.delete(:output) - |> Map.put(:error, internal_transaction_param[:error] || failed_parent[:error]) + |> Map.put(:error, internal_transaction_param[:error] || "Parent reverted") else internal_transaction_param end end) end + defp has_failed_parent?(failed_parent_paths, [head | tail], reverse_path_acc) do + MapSet.member?(failed_parent_paths, reverse_path_acc) or + has_failed_parent?(failed_parent_paths, tail, [head | reverse_path_acc]) + end + + # don't count itself as a parent + defp has_failed_parent?(_failed_parent_paths, [], _reverse_path_acc), do: false + defp handle_unique_key_violation(%{exception: %{postgres: %{code: :unique_violation}}}, block_numbers) do BlocksRunner.invalidate_consensus_blocks(block_numbers) From b5382c3f525abc229140ced29f831a7ee13ef77a Mon Sep 17 00:00:00 2001 From: varasev <33550681+varasev@users.noreply.github.com> Date: Thu, 15 Feb 2024 13:03:16 +0300 Subject: [PATCH 464/607] Output user address as an object in API v2 for Shibarium (#9389) * Output user address as an object in API v2 for Shibarium * Update changelog * Preload shibarium addresses in views * Handle unique violation * update * Add CI workflow for Shibarium branch --------- Co-authored-by: POA <33550681+poa@users.noreply.github.com> Co-authored-by: Viktor Baranov --- .../publish-docker-image-for-shibarium.yml | 43 +++++++++++++++++++ CHANGELOG.md | 1 + .../views/api/v2/shibarium_view.ex | 29 +++++++++++-- .../lib/indexer/fetcher/shibarium/helper.ex | 34 ++++++++++----- .../lib/indexer/fetcher/shibarium/l1.ex | 11 ++++- .../lib/indexer/fetcher/shibarium/l2.ex | 11 ++++- 6 files changed, 112 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/publish-docker-image-for-shibarium.yml diff --git a/.github/workflows/publish-docker-image-for-shibarium.yml b/.github/workflows/publish-docker-image-for-shibarium.yml new file mode 100644 index 000000000000..6401c46ad2ab --- /dev/null +++ b/.github/workflows/publish-docker-image-for-shibarium.yml @@ -0,0 +1,43 @@ +name: Stability Publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-shibarium +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + DOCKER_CHAIN_NAME: shibarium + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=shibarium \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 717826a11ebf..fe09ba750be6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ ### Chore - [#9393](https://github.com/blockscout/blockscout/pull/9393) - Bump actions/cache to v4 +- [#9389](https://github.com/blockscout/blockscout/pull/9389) - Output user address as an object in API v2 for Shibarium - [#9361](https://github.com/blockscout/blockscout/pull/9361) - Define BRIDGED_TOKENS_ENABLED env in Dockerfile - [#8851](https://github.com/blockscout/blockscout/pull/8851) - Fix dialyzer and add TypedEctoSchema diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/shibarium_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/shibarium_view.ex index d8f273fe62c4..b51a27a0a98f 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/shibarium_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/shibarium_view.ex @@ -1,11 +1,17 @@ defmodule BlockScoutWeb.API.V2.ShibariumView do use BlockScoutWeb, :view + alias BlockScoutWeb.API.V2.Helper + alias Explorer.Chain + @spec render(String.t(), map()) :: map() def render("shibarium_deposits.json", %{ deposits: deposits, - next_page_params: next_page_params + next_page_params: next_page_params, + conn: conn }) do + user_addresses = get_user_addresses(deposits, conn) + %{ items: Enum.map(deposits, fn deposit -> @@ -13,7 +19,7 @@ defmodule BlockScoutWeb.API.V2.ShibariumView do "l1_block_number" => deposit.l1_block_number, "l1_transaction_hash" => deposit.l1_transaction_hash, "l2_transaction_hash" => deposit.l2_transaction_hash, - "user" => deposit.user, + "user" => Map.get(user_addresses, deposit.user, deposit.user), "timestamp" => deposit.timestamp } end), @@ -23,8 +29,11 @@ defmodule BlockScoutWeb.API.V2.ShibariumView do def render("shibarium_withdrawals.json", %{ withdrawals: withdrawals, - next_page_params: next_page_params + next_page_params: next_page_params, + conn: conn }) do + user_addresses = get_user_addresses(withdrawals, conn) + %{ items: Enum.map(withdrawals, fn withdrawal -> @@ -32,7 +41,7 @@ defmodule BlockScoutWeb.API.V2.ShibariumView do "l2_block_number" => withdrawal.l2_block_number, "l2_transaction_hash" => withdrawal.l2_transaction_hash, "l1_transaction_hash" => withdrawal.l1_transaction_hash, - "user" => withdrawal.user, + "user" => Map.get(user_addresses, withdrawal.user, withdrawal.user), "timestamp" => withdrawal.timestamp } end), @@ -43,4 +52,16 @@ defmodule BlockScoutWeb.API.V2.ShibariumView do def render("shibarium_items_count.json", %{count: count}) do count end + + defp get_user_addresses(items, conn) do + items + |> Enum.map(& &1.user) + |> Enum.reject(&is_nil(&1)) + |> Enum.uniq() + |> Chain.hashes_to_addresses( + necessity_by_association: %{:names => :optional, :smart_contract => :optional}, + api?: true + ) + |> Enum.into(%{}, &{&1.hash, Helper.address_with_info(conn, &1, &1.hash, true)}) + end end diff --git a/apps/indexer/lib/indexer/fetcher/shibarium/helper.ex b/apps/indexer/lib/indexer/fetcher/shibarium/helper.ex index b8cafcdb1525..6dceb3eedb78 100644 --- a/apps/indexer/lib/indexer/fetcher/shibarium/helper.ex +++ b/apps/indexer/lib/indexer/fetcher/shibarium/helper.ex @@ -76,22 +76,34 @@ defmodule Indexer.Fetcher.Shibarium.Helper do ShibariumCounter.withdrawals_count_save(Reader.withdrawals_count()) end + # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity defp bind_existing_operation_in_db(op, calling_module) do {query, set} = make_query_for_bind(op, calling_module) - {updated_count, _} = - Repo.update_all( - from(b in Bridge, - join: s in subquery(query), - on: - b.operation_hash == s.operation_hash and b.l1_transaction_hash == s.l1_transaction_hash and - b.l2_transaction_hash == s.l2_transaction_hash - ), - set: set - ) + updated_count = + try do + {updated_count, _} = + Repo.update_all( + from(b in Bridge, + join: s in subquery(query), + on: + b.operation_hash == s.operation_hash and b.l1_transaction_hash == s.l1_transaction_hash and + b.l2_transaction_hash == s.l2_transaction_hash + ), + set: set + ) + + updated_count + rescue + error in Postgrex.Error -> + # if this is unique violation, we just ignore such an operation as it was inserted before + if error.postgres.code != :unique_violation do + reraise error, __STACKTRACE__ + end + end # increment the cached count of complete rows - case updated_count > 0 && op.operation_type do + case !is_nil(updated_count) && updated_count > 0 && op.operation_type do :deposit -> ShibariumCounter.deposits_count_save(updated_count, true) :withdrawal -> ShibariumCounter.withdrawals_count_save(updated_count, true) false -> nil diff --git a/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex b/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex index 39b099ff1ed9..76ed302099de 100644 --- a/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex +++ b/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex @@ -27,6 +27,7 @@ defmodule Indexer.Fetcher.Shibarium.L1 do alias Explorer.{Chain, Repo} alias Indexer.Fetcher.RollupL1ReorgMonitor alias Indexer.Helper + alias Indexer.Transform.Addresses @block_check_interval_range_size 100 @eth_get_logs_range_size 1000 @@ -257,9 +258,17 @@ defmodule Indexer.Fetcher.Shibarium.L1 do ) |> prepare_operations(json_rpc_named_arguments) + insert_items = prepare_insert_items(operations, __MODULE__) + + addresses = + Addresses.extract_addresses(%{ + shibarium_bridge_operations: insert_items + }) + {:ok, _} = Chain.import(%{ - shibarium_bridge_operations: %{params: prepare_insert_items(operations, __MODULE__)}, + addresses: %{params: addresses, on_conflict: :nothing}, + shibarium_bridge_operations: %{params: insert_items}, timeout: :infinity }) diff --git a/apps/indexer/lib/indexer/fetcher/shibarium/l2.ex b/apps/indexer/lib/indexer/fetcher/shibarium/l2.ex index 6a31962d6483..41512c464944 100644 --- a/apps/indexer/lib/indexer/fetcher/shibarium/l2.ex +++ b/apps/indexer/lib/indexer/fetcher/shibarium/l2.ex @@ -29,6 +29,7 @@ defmodule Indexer.Fetcher.Shibarium.L2 do alias Explorer.{Chain, Repo} alias Explorer.Chain.Shibarium.Bridge alias Indexer.Helper + alias Indexer.Transform.Addresses @eth_get_logs_range_size 100 @fetcher_name :shibarium_bridge_l2 @@ -180,9 +181,17 @@ defmodule Indexer.Fetcher.Shibarium.L2 do |> get_logs_all(child_chain, bone_withdraw, json_rpc_named_arguments) |> prepare_operations(weth) + insert_items = prepare_insert_items(operations, __MODULE__) + + addresses = + Addresses.extract_addresses(%{ + shibarium_bridge_operations: insert_items + }) + {:ok, _} = Chain.import(%{ - shibarium_bridge_operations: %{params: prepare_insert_items(operations, __MODULE__)}, + addresses: %{params: addresses, on_conflict: :nothing}, + shibarium_bridge_operations: %{params: insert_items}, timeout: :infinity }) From ad4d2571a1c6ace436caa3b8ab88b075b3f38198 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 15 Feb 2024 13:04:29 +0300 Subject: [PATCH 465/607] Fix Shibarium workflow name --- .github/workflows/publish-docker-image-for-shibarium.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-docker-image-for-shibarium.yml b/.github/workflows/publish-docker-image-for-shibarium.yml index 6401c46ad2ab..8496b598eec3 100644 --- a/.github/workflows/publish-docker-image-for-shibarium.yml +++ b/.github/workflows/publish-docker-image-for-shibarium.yml @@ -1,4 +1,4 @@ -name: Stability Publish Docker image +name: Shibarium Publish Docker image on: workflow_dispatch: From ebfc3158386fbb02a12d24b7ae9c2c59f4e612e0 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Thu, 15 Feb 2024 06:49:11 -0500 Subject: [PATCH 466/607] Fix outdated deps cache in CI (#9398) * chore: try with --skip-umbrella-children * chore: try with double caching * chore: revert to single cache * chore: changelog --- .github/workflows/config.yml | 82 ++++++++++++++++++------------------ CHANGELOG.md | 1 + 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 81942ae3d61e..0bec339163ab 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -73,9 +73,9 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} restore-keys: | - ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps- + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- - name: Conditionally build Mix deps cache if: steps.deps-cache.outputs.cache-hit != 'true' @@ -83,7 +83,7 @@ jobs: mix local.hex --force mix local.rebar --force mix deps.get - mix deps.compile + mix deps.compile --skip-umbrella-children - name: Restore Explorer NPM Cache uses: actions/cache@v4 @@ -125,15 +125,15 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Restore Mix Deps Cache - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: deps-cache with: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} restore-keys: | - ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- - run: mix credo @@ -149,15 +149,15 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Restore Mix Deps Cache - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: deps-cache with: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} restore-keys: | - ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- - run: mix format --check-formatted @@ -178,24 +178,24 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Restore Mix Deps Cache - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: deps-cache with: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} restore-keys: | - ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- - name: Restore Dialyzer Cache uses: actions/cache@v4 id: dialyzer-cache with: path: priv/plts - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-${{ matrix.chain-type }}-dialyzer-mixlockhash_25-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-${{ matrix.chain-type }}-dialyzer-mixlockhash-${{ hashFiles('mix.lock') }} restore-keys: | - ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-${{ matrix.chain-type }}-dialyzer-" + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-${{ matrix.chain-type }}-dialyzer-mixlockhash- - name: Conditionally build Dialyzer Cache if: steps.dialyzer-cache.output.cache-hit != 'true' @@ -222,15 +222,15 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Restore Mix Deps Cache - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: deps-cache with: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} restore-keys: | - ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- - run: | mix gettext.extract --merge | tee stdout.txt @@ -248,15 +248,15 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Mix Deps Cache - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: deps-cache with: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} restore-keys: | - ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- - name: Scan explorer for vulnerabilities run: mix sobelow --config @@ -277,15 +277,15 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Mix Deps Cache - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: deps-cache with: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} restore-keys: | - ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- - name: Restore Explorer NPM Cache uses: actions/cache@v4 @@ -325,15 +325,15 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Mix Deps Cache - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: deps-cache with: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} restore-keys: | - ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- - name: Restore Explorer NPM Cache uses: actions/cache@v4 @@ -371,15 +371,15 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Mix Deps Cache - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: deps-cache with: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} restore-keys: | - ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- - name: Restore Blockscout Web NPM Cache uses: actions/cache@v4 @@ -433,15 +433,15 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Mix Deps Cache - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: deps-cache with: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} restore-keys: | - ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- - run: ./bin/install_chrome_headless.sh - name: mix test --exclude no_nethermind @@ -493,15 +493,15 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Mix Deps Cache - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: deps-cache with: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} restore-keys: | - ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- - name: Restore Explorer NPM Cache uses: actions/cache@v4 @@ -564,15 +564,15 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Mix Deps Cache - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: deps-cache with: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} restore-keys: | - ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- - run: ./bin/install_chrome_headless.sh @@ -632,15 +632,15 @@ jobs: elixir-version: ${{ env.ELIXIR_VERSION }} - name: Mix Deps Cache - uses: actions/cache@v4 + uses: actions/cache/restore@v4 id: deps-cache with: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_40-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} restore-keys: | - ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- - name: Restore Explorer NPM Cache uses: actions/cache@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index fe09ba750be6..2b7cb06dc7df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ ### Chore +- [#9398](https://github.com/blockscout/blockscout/pull/9398) - Improve elixir dependencies caching in CI - [#9393](https://github.com/blockscout/blockscout/pull/9393) - Bump actions/cache to v4 - [#9389](https://github.com/blockscout/blockscout/pull/9389) - Output user address as an object in API v2 for Shibarium - [#9361](https://github.com/blockscout/blockscout/pull/9361) - Define BRIDGED_TOKENS_ENABLED env in Dockerfile From 389debbc85e12a74aeb520ff2e945bfbe68bbb4e Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Thu, 15 Feb 2024 16:34:56 +0300 Subject: [PATCH 467/607] =?UTF-8?q?Create=20Indexer.Fetcher.TokenInstance.?= =?UTF-8?q?{SanitizeERC721,=20SanitizeERC1155=E2=80=A6=20(#9226)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create Indexer.Fetcher.TokenInstance.{SanitizeERC721, SanitizeERC1155}; Move token instances to BlockReferencing stage * Add envs to .env file * Fix dialyzer * Process review comments * Add env to .env file --- CHANGELOG.md | 1 + .../api/v2/address_controller_test.exs | 3 +- .../lib/explorer/application/constants.ex | 36 ++++++++ .../chain/import/stage/block_following.ex | 3 +- .../chain/import/stage/block_referencing.ex | 1 + apps/explorer/lib/explorer/chain/token.ex | 17 +++- .../lib/explorer/chain/token/instance.ex | 45 ++++++++++ .../token_instance/sanitize_erc1155.ex | 51 +++++++++++ .../fetcher/token_instance/sanitize_erc721.ex | 89 +++++++++++++++++++ apps/indexer/lib/indexer/supervisor.ex | 6 +- .../token_instance/sanitize_erc1155_test.exs | 33 +++++++ .../token_instance/sanitize_erc721_test.exs | 39 ++++++++ config/runtime.exs | 20 ++++- docker-compose/envs/common-blockscout.env | 7 ++ 14 files changed, 343 insertions(+), 8 deletions(-) create mode 100644 apps/indexer/lib/indexer/fetcher/token_instance/sanitize_erc1155.ex create mode 100644 apps/indexer/lib/indexer/fetcher/token_instance/sanitize_erc721.ex create mode 100644 apps/indexer/test/indexer/fetcher/token_instance/sanitize_erc1155_test.exs create mode 100644 apps/indexer/test/indexer/fetcher/token_instance/sanitize_erc721_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b7cb06dc7df..5a8c4434d8e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - [#9306](https://github.com/blockscout/blockscout/pull/9306) - Improve marking of failed internal transactions - [#9305](https://github.com/blockscout/blockscout/pull/9305) - Add effective gas price calculation as fallback - [#9300](https://github.com/blockscout/blockscout/pull/9300) - Fix read contract bug +- [#9226](https://github.com/blockscout/blockscout/pull/9226) - Split Indexer.Fetcher.TokenInstance.LegacySanitize ### Chore diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index 56faa2c78ed0..0abc6ae7d48b 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -2476,8 +2476,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do for _ <- 0..(amount - 1) do ti = insert(:token_instance, - token_contract_address_hash: token.contract_address_hash, - owner_address_hash: address.hash + token_contract_address_hash: token.contract_address_hash ) |> Repo.preload([:token]) diff --git a/apps/explorer/lib/explorer/application/constants.ex b/apps/explorer/lib/explorer/application/constants.ex index dc90158dd3be..7dee1bbdae76 100644 --- a/apps/explorer/lib/explorer/application/constants.ex +++ b/apps/explorer/lib/explorer/application/constants.ex @@ -5,8 +5,10 @@ defmodule Explorer.Application.Constants do use Explorer.Schema alias Explorer.{Chain, Repo} + alias Explorer.Chain.Hash @keys_manager_contract_address_key "keys_manager_contract_address" + @last_processed_erc_721_token "token_instance_sanitizer_last_processed_erc_721_token" @primary_key false typed_schema "constants" do @@ -43,4 +45,38 @@ defmodule Explorer.Application.Constants do def get_keys_manager_contract_address(options \\ []) do get_constant_by_key(@keys_manager_contract_address_key, options) end + + @doc """ + For usage in Indexer.Fetcher.TokenInstance.SanitizeERC721 + """ + @spec insert_last_processed_token_address_hash(Hash.Address.t()) :: Ecto.Schema.t() + def insert_last_processed_token_address_hash(address_hash) do + existing_value = Repo.get(__MODULE__, @last_processed_erc_721_token) + + if existing_value do + existing_value + |> changeset(%{value: to_string(address_hash)}) + |> Repo.update!() + else + %{key: @last_processed_erc_721_token, value: to_string(address_hash)} + |> changeset() + |> Repo.insert!() + end + end + + @doc """ + For usage in Indexer.Fetcher.TokenInstance.SanitizeERC721 + """ + @spec get_last_processed_token_address_hash(keyword()) :: nil | Explorer.Chain.Hash.t() + def get_last_processed_token_address_hash(options \\ []) do + result = get_constant_by_key(@last_processed_erc_721_token, options) + + case Chain.string_to_address_hash(result) do + {:ok, address_hash} -> + address_hash + + _ -> + nil + end + end end diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_following.ex b/apps/explorer/lib/explorer/chain/import/stage/block_following.ex index c8ed699d2af7..193de566e68e 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_following.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_following.ex @@ -13,8 +13,7 @@ defmodule Explorer.Chain.Import.Stage.BlockFollowing do do: [ Runner.Block.SecondDegreeRelations, Runner.Block.Rewards, - Runner.Address.CurrentTokenBalances, - Runner.TokenInstances + Runner.Address.CurrentTokenBalances ] @impl Stage diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex index f85e48a64fe1..9a6c68224766 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex @@ -13,6 +13,7 @@ defmodule Explorer.Chain.Import.Stage.BlockReferencing do Runner.Logs, Runner.Tokens, Runner.TokenTransfers, + Runner.TokenInstances, Runner.Address.TokenBalances, Runner.TransactionActions, Runner.Withdrawals diff --git a/apps/explorer/lib/explorer/chain/token.ex b/apps/explorer/lib/explorer/chain/token.ex index 65a3f8985d5d..9800f462f078 100644 --- a/apps/explorer/lib/explorer/chain/token.ex +++ b/apps/explorer/lib/explorer/chain/token.ex @@ -77,7 +77,7 @@ defmodule Explorer.Chain.Token do alias Ecto.Changeset alias Explorer.{Chain, SortingHelper} - alias Explorer.Chain.{BridgedToken, Search, Token} + alias Explorer.Chain.{BridgedToken, Hash, Search, Token} alias Explorer.SmartContract.Helper @default_sorting [ @@ -231,4 +231,19 @@ defmodule Explorer.Chain.Token do def get_by_contract_address_hash(hash, options) do Chain.select_repo(options).get_by(__MODULE__, contract_address_hash: hash) end + + @doc """ + For usage in Indexer.Fetcher.TokenInstance.LegacySanitizeERC721 + """ + @spec ordered_erc_721_token_address_hashes_list_query(integer(), Hash.Address.t() | nil) :: Ecto.Query.t() + def ordered_erc_721_token_address_hashes_list_query(limit, last_address_hash \\ nil) do + query = + __MODULE__ + |> order_by([token], asc: token.contract_address_hash) + |> where([token], token.type == "ERC-721") + |> limit(^limit) + |> select([token], token.contract_address_hash) + + (last_address_hash && where(query, [token], token.contract_address_hash > ^last_address_hash)) || query + end end diff --git a/apps/explorer/lib/explorer/chain/token/instance.ex b/apps/explorer/lib/explorer/chain/token/instance.ex index 05d1ba594c52..d1ce8a181558 100644 --- a/apps/explorer/lib/explorer/chain/token/instance.ex +++ b/apps/explorer/lib/explorer/chain/token/instance.ex @@ -431,6 +431,51 @@ defmodule Explorer.Chain.Token.Instance do |> limit(^limit) end + @doc """ + Finds token instances of a particular token (pairs of contract_address_hash and token_id) which was met in token_transfers table but has no corresponding entry in token_instances table. + """ + @spec not_inserted_token_instances_query_by_token(integer(), Hash.Address.t()) :: Ecto.Query.t() + def not_inserted_token_instances_query_by_token(limit, token_contract_address_hash) do + token_transfers_query = + TokenTransfer + |> where([token_transfer], token_transfer.token_contract_address_hash == ^token_contract_address_hash) + |> select([token_transfer], %{ + token_contract_address_hash: token_transfer.token_contract_address_hash, + token_id: fragment("unnest(?)", token_transfer.token_ids) + }) + + token_transfers_query + |> subquery() + |> join(:left, [token_transfer], token_instance in __MODULE__, + on: + token_instance.token_contract_address_hash == token_transfer.token_contract_address_hash and + token_instance.token_id == token_transfer.token_id + ) + |> where([token_transfer, token_instance], is_nil(token_instance.token_id)) + |> select([token_transfer, token_instance], %{ + contract_address_hash: token_transfer.token_contract_address_hash, + token_id: token_transfer.token_id + }) + |> limit(^limit) + end + + @doc """ + Finds ERC-1155 token instances (pairs of contract_address_hash and token_id) which was met in current_token_balances table but has no corresponding entry in token_instances table. + """ + @spec not_inserted_erc_1155_token_instances(integer()) :: Ecto.Query.t() + def not_inserted_erc_1155_token_instances(limit) do + CurrentTokenBalance + |> join(:left, [actb], ti in __MODULE__, + on: actb.token_contract_address_hash == ti.token_contract_address_hash and actb.token_id == ti.token_id + ) + |> where([actb, ti], not is_nil(actb.token_id) and is_nil(ti.token_id)) + |> select([actb], %{ + contract_address_hash: actb.token_contract_address_hash, + token_id: actb.token_id + }) + |> limit(^limit) + end + @doc """ Puts is_unique field in token instance. Returns updated token instance is_unique is true for ERC-721 always and for ERC-1155 only if token_id is unique diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/sanitize_erc1155.ex b/apps/indexer/lib/indexer/fetcher/token_instance/sanitize_erc1155.ex new file mode 100644 index 000000000000..f2adfa8ac090 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/token_instance/sanitize_erc1155.ex @@ -0,0 +1,51 @@ +defmodule Indexer.Fetcher.TokenInstance.SanitizeERC1155 do + @moduledoc """ + This fetcher is stands for creating token instances which wasn't inserted yet and index meta for them. + + !!!Imports only ERC-1155 token instances!!! + """ + + use GenServer, restart: :transient + + alias Explorer.Chain.Token.Instance + alias Explorer.Repo + + import Indexer.Fetcher.TokenInstance.Helper + + def start_link(_) do + concurrency = Application.get_env(:indexer, __MODULE__)[:concurrency] + batch_size = Application.get_env(:indexer, __MODULE__)[:batch_size] + GenServer.start_link(__MODULE__, %{concurrency: concurrency, batch_size: batch_size}, name: __MODULE__) + end + + @impl true + def init(opts) do + GenServer.cast(__MODULE__, :backfill) + + {:ok, opts} + end + + @impl true + def handle_cast(:backfill, %{concurrency: concurrency, batch_size: batch_size} = state) do + instances_to_fetch = + (concurrency * batch_size) + |> Instance.not_inserted_erc_1155_token_instances() + |> Repo.all() + + if Enum.empty?(instances_to_fetch) do + {:stop, :normal, state} + else + instances_to_fetch + |> Enum.uniq() + |> Enum.chunk_every(batch_size) + |> Enum.map(&process_batch/1) + |> Task.await_many(:infinity) + + GenServer.cast(__MODULE__, :backfill) + + {:noreply, state} + end + end + + defp process_batch(batch), do: Task.async(fn -> batch_fetch_instances(batch) end) +end diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/sanitize_erc721.ex b/apps/indexer/lib/indexer/fetcher/token_instance/sanitize_erc721.ex new file mode 100644 index 000000000000..bbe8bf7540b1 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/token_instance/sanitize_erc721.ex @@ -0,0 +1,89 @@ +defmodule Indexer.Fetcher.TokenInstance.SanitizeERC721 do + @moduledoc """ + This fetcher is stands for creating token instances which wasn't inserted yet and index meta for them. + + !!!Imports only ERC-721 token instances!!! + """ + + use GenServer, restart: :transient + + alias Explorer.Application.Constants + alias Explorer.Chain.Token + alias Explorer.Chain.Token.Instance + alias Explorer.Repo + + import Indexer.Fetcher.TokenInstance.Helper + + def start_link(_) do + concurrency = Application.get_env(:indexer, __MODULE__)[:concurrency] + batch_size = Application.get_env(:indexer, __MODULE__)[:batch_size] + tokens_queue_size = Application.get_env(:indexer, __MODULE__)[:tokens_queue_size] + + GenServer.start_link( + __MODULE__, + %{concurrency: concurrency, batch_size: batch_size, tokens_queue_size: tokens_queue_size}, + name: __MODULE__ + ) + end + + @impl true + def init(opts) do + last_token_address_hash = Constants.get_last_processed_token_address_hash() + GenServer.cast(__MODULE__, :fetch_tokens_queue) + + {:ok, Map.put(opts, :last_token_address_hash, last_token_address_hash)} + end + + @impl true + def handle_cast(:fetch_tokens_queue, state) do + address_hashes = + state[:tokens_queue_size] + |> Token.ordered_erc_721_token_address_hashes_list_query(state[:last_token_address_hash]) + |> Repo.all() + + if Enum.empty?(address_hashes) do + {:stop, :normal, state} + else + GenServer.cast(__MODULE__, :backfill) + + {:noreply, Map.put(state, :tokens_queue, address_hashes)} + end + end + + @impl true + def handle_cast(:backfill, %{tokens_queue: []} = state) do + GenServer.cast(__MODULE__, :fetch_tokens_queue) + + {:noreply, state} + end + + @impl true + def handle_cast( + :backfill, + %{concurrency: concurrency, batch_size: batch_size, tokens_queue: [current_address_hash | remains]} = state + ) do + instances_to_fetch = + (concurrency * batch_size) + |> Instance.not_inserted_token_instances_query_by_token(current_address_hash) + |> Repo.all() + + if Enum.empty?(instances_to_fetch) do + Constants.insert_last_processed_token_address_hash(current_address_hash) + GenServer.cast(__MODULE__, :backfill) + + {:noreply, %{state | tokens_queue: remains, last_token_address_hash: current_address_hash}} + else + instances_to_fetch + |> Enum.uniq() + |> Enum.chunk_every(batch_size) + |> Enum.map(&process_batch/1) + |> Task.await_many(:infinity) + + GenServer.cast(__MODULE__, :backfill) + + {:noreply, state} + end + end + + defp process_batch(batch), do: Task.async(fn -> batch_fetch_instances(batch) end) +end diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index 0c338fdecc50..a560d7642533 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -22,6 +22,8 @@ defmodule Indexer.Supervisor do alias Indexer.Fetcher.TokenInstance.Realtime, as: TokenInstanceRealtime alias Indexer.Fetcher.TokenInstance.Retry, as: TokenInstanceRetry alias Indexer.Fetcher.TokenInstance.Sanitize, as: TokenInstanceSanitize + alias Indexer.Fetcher.TokenInstance.SanitizeERC1155, as: TokenInstanceSanitizeERC1155 + alias Indexer.Fetcher.TokenInstance.SanitizeERC721, as: TokenInstanceSanitizeERC721 alias Indexer.Fetcher.{ BlockReward, @@ -122,7 +124,9 @@ defmodule Indexer.Supervisor do {TokenInstanceRealtime.Supervisor, [[memory_monitor: memory_monitor]]}, {TokenInstanceRetry.Supervisor, [[memory_monitor: memory_monitor]]}, {TokenInstanceSanitize.Supervisor, [[memory_monitor: memory_monitor]]}, - {TokenInstanceLegacySanitize, [[memory_monitor: memory_monitor]]}, + configure(TokenInstanceLegacySanitize, [[memory_monitor: memory_monitor]]), + configure(TokenInstanceSanitizeERC721, [[memory_monitor: memory_monitor]]), + configure(TokenInstanceSanitizeERC1155, [[memory_monitor: memory_monitor]]), configure(TransactionAction.Supervisor, [[memory_monitor: memory_monitor]]), {ContractCode.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, diff --git a/apps/indexer/test/indexer/fetcher/token_instance/sanitize_erc1155_test.exs b/apps/indexer/test/indexer/fetcher/token_instance/sanitize_erc1155_test.exs new file mode 100644 index 000000000000..aa1dd230dd4a --- /dev/null +++ b/apps/indexer/test/indexer/fetcher/token_instance/sanitize_erc1155_test.exs @@ -0,0 +1,33 @@ +defmodule Indexer.Fetcher.TokenInstance.SanitizeERC1155Test do + use Explorer.DataCase + + alias Explorer.Repo + alias Explorer.Chain.Token.Instance + alias EthereumJSONRPC.Encoder + + describe "sanitizer test" do + test "imports token instances" do + for i <- 0..3 do + token = insert(:token, type: "ERC-1155") + + insert(:address_current_token_balance, + token_type: "ERC-1155", + token_id: i, + token_contract_address_hash: token.contract_address_hash, + value: Enum.random(1..100_000) + ) + end + + assert [] = Repo.all(Instance) + + start_supervised!({Indexer.Fetcher.TokenInstance.SanitizeERC1155, []}) + + :timer.sleep(500) + + instances = Repo.all(Instance) + + assert Enum.count(instances) == 4 + assert Enum.all?(instances, fn instance -> !is_nil(instance.error) and is_nil(instance.metadata) end) + end + end +end diff --git a/apps/indexer/test/indexer/fetcher/token_instance/sanitize_erc721_test.exs b/apps/indexer/test/indexer/fetcher/token_instance/sanitize_erc721_test.exs new file mode 100644 index 000000000000..5568b8da3dcc --- /dev/null +++ b/apps/indexer/test/indexer/fetcher/token_instance/sanitize_erc721_test.exs @@ -0,0 +1,39 @@ +defmodule Indexer.Fetcher.TokenInstance.SanitizeERC721Test do + use Explorer.DataCase + + alias Explorer.Repo + alias Explorer.Chain.Token.Instance + alias EthereumJSONRPC.Encoder + + describe "sanitizer test" do + test "imports token instances" do + for x <- 0..3 do + erc_721_token = insert(:token, type: "ERC-721") + + tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + + address = insert(:address) + + insert(:token_transfer, + transaction: tx, + block: tx.block, + block_number: tx.block_number, + from_address: address, + token_contract_address: erc_721_token.contract_address, + token_ids: [x] + ) + end + + assert [] = Repo.all(Instance) + + start_supervised!({Indexer.Fetcher.TokenInstance.SanitizeERC721, []}) + + :timer.sleep(500) + + instances = Repo.all(Instance) + + assert Enum.count(instances) == 4 + assert Enum.all?(instances, fn instance -> !is_nil(instance.error) and is_nil(instance.metadata) end) + end + end +end diff --git a/config/runtime.exs b/config/runtime.exs index 89271ad44e46..b1b86bfedb44 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -596,8 +596,14 @@ config :indexer, Indexer.Fetcher.TokenInstance.Retry.Supervisor, config :indexer, Indexer.Fetcher.TokenInstance.Sanitize.Supervisor, disabled?: ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_TOKEN_INSTANCE_SANITIZE_FETCHER") -config :indexer, Indexer.Fetcher.TokenInstance.LegacySanitize.Supervisor, - disabled?: ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_TOKEN_INSTANCE_LEGACY_SANITIZE_FETCHER", "true") +config :indexer, Indexer.Fetcher.TokenInstance.LegacySanitize, + enabled: !ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_TOKEN_INSTANCE_LEGACY_SANITIZE_FETCHER", "true") + +config :indexer, Indexer.Fetcher.TokenInstance.SanitizeERC1155, + enabled: !ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_TOKEN_INSTANCE_ERC_1155_SANITIZE_FETCHER", "false") + +config :indexer, Indexer.Fetcher.TokenInstance.SanitizeERC721, + enabled: !ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_TOKEN_INSTANCE_ERC_721_SANITIZE_FETCHER", "false") config :indexer, Indexer.Fetcher.EmptyBlocksSanitizer, batch_size: ConfigHelper.parse_integer_env_var("INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE", 100), @@ -634,6 +640,16 @@ config :indexer, Indexer.Fetcher.TokenInstance.LegacySanitize, concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_CONCURRENCY", 2), batch_size: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_BATCH_SIZE", 10) +config :indexer, Indexer.Fetcher.TokenInstance.SanitizeERC1155, + concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_ERC_1155_SANITIZE_CONCURRENCY", 2), + batch_size: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_ERC_1155_SANITIZE_BATCH_SIZE", 10) + +config :indexer, Indexer.Fetcher.TokenInstance.SanitizeERC721, + concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_ERC_721_SANITIZE_CONCURRENCY", 2), + batch_size: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_ERC_721_SANITIZE_BATCH_SIZE", 10), + tokens_queue_size: + ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_ERC_721_SANITIZE_TOKENS_BATCH_SIZE", 100) + config :indexer, Indexer.Fetcher.InternalTransaction, batch_size: ConfigHelper.parse_integer_env_var("INDEXER_INTERNAL_TRANSACTIONS_BATCH_SIZE", 10), concurrency: ConfigHelper.parse_integer_env_var("INDEXER_INTERNAL_TRANSACTIONS_CONCURRENCY", 4), diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 1f78a8dde353..60584a6d1624 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -144,6 +144,13 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false # INDEXER_TOKEN_INSTANCE_SANITIZE_CONCURRENCY= # INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_BATCH_SIZE=10 # INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_CONCURRENCY=10 +# INDEXER_DISABLE_TOKEN_INSTANCE_ERC_1155_SANITIZE_FETCHER=false +# INDEXER_DISABLE_TOKEN_INSTANCE_ERC_721_SANITIZE_FETCHER=false +# INDEXER_TOKEN_INSTANCE_ERC_1155_SANITIZE_CONCURRENCY=2 +# INDEXER_TOKEN_INSTANCE_ERC_1155_SANITIZE_BATCH_SIZE=10 +# INDEXER_TOKEN_INSTANCE_ERC_721_SANITIZE_CONCURRENCY=2 +# INDEXER_TOKEN_INSTANCE_ERC_721_SANITIZE_BATCH_SIZE=10 +# INDEXER_TOKEN_INSTANCE_ERC_721_SANITIZE_TOKENS_BATCH_SIZE=100 # TOKEN_INSTANCE_OWNER_MIGRATION_CONCURRENCY=5 # TOKEN_INSTANCE_OWNER_MIGRATION_BATCH_SIZE=50 # INDEXER_COIN_BALANCES_BATCH_SIZE= From 4f2dc81a6fe41e7cf79dee5fec62e4dd4cbc2b61 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 15 Feb 2024 21:59:53 +0300 Subject: [PATCH 468/607] Retry ERC-721 token instance metadata fetch from baseURI + tokenID (#9257) * Retry to fetch token instance metadata from baseURI + tokenId, if fetch from tokenURI return vm execution error * Process review comments --- CHANGELOG.md | 1 + .../indexer/fetcher/token_instance/helper.ex | 148 +++++++++++++++--- .../token_instance/metadata_retriever.ex | 74 +++++---- .../fetcher/token_instance/helper_test.exs | 126 ++++++++++++--- config/runtime.exs | 3 + docker-compose/envs/common-blockscout.env | 1 + 6 files changed, 280 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a8c4434d8e1..cba481ecf922 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ - [#9393](https://github.com/blockscout/blockscout/pull/9393) - Bump actions/cache to v4 - [#9389](https://github.com/blockscout/blockscout/pull/9389) - Output user address as an object in API v2 for Shibarium - [#9361](https://github.com/blockscout/blockscout/pull/9361) - Define BRIDGED_TOKENS_ENABLED env in Dockerfile +- [#9257](https://github.com/blockscout/blockscout/pull/9257) - Retry token instance metadata fetch from baseURI + tokenID - [#8851](https://github.com/blockscout/blockscout/pull/8851) - Fix dialyzer and add TypedEctoSchema
diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex b/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex index 792f92c85ada..01dc381379db 100644 --- a/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex +++ b/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex @@ -11,9 +11,23 @@ defmodule Indexer.Fetcher.TokenInstance.Helper do @cryptokitties_address_hash "0x06012c8cf97bead5deae237070f9587f8e7a266d" @token_uri "c87b56dd" + @base_uri "6c0360eb" @uri "0e89341c" @erc_721_1155_abi [ + %{ + "inputs" => [], + "name" => "baseURI", + "outputs" => [ + %{ + "internalType" => "string", + "name" => "", + "type" => "string" + } + ], + "stateMutability" => "view", + "type" => "function" + }, %{ "type" => "function", "stateMutability" => "view", @@ -81,16 +95,110 @@ defmodule Indexer.Fetcher.TokenInstance.Helper do Map.put_new(acc, address_hash_string, Chain.get_token_type(contract_address_hash)) end) + {results, failed_results, instances_to_retry} = + other + |> batch_fetch_instances_inner(token_types_map, cryptokitties) + |> Enum.reduce({[], [], []}, fn {{_task, res}, {_result, _normalized_token_id, contract_address_hash, token_id}}, + {results, failed_results, instances_to_retry} -> + case res do + {:ok, {:error, "VM execution error"} = result} -> + add_failed_to_retry_result( + results, + failed_results, + instances_to_retry, + contract_address_hash, + token_id, + result + ) + + {:ok, result} -> + {[ + result_to_insert_params(result, contract_address_hash, token_id) + | results + ], failed_results, instances_to_retry} + + {:exit, reason} -> + {[ + result_to_insert_params( + {:error, MetadataRetriever.truncate_error("Terminated:" <> inspect(reason))}, + contract_address_hash, + token_id + ) + | results + ], failed_results, instances_to_retry} + end + end) + + total_results = + if Application.get_env(:indexer, __MODULE__)[:base_uri_retry?] do + {success_results_from_retry, failed_results_after_retry} = + instances_to_retry + |> batch_fetch_instances_inner(token_types_map, [], true) + |> Enum.reduce({[], []}, fn {{_task, res}, {_result, _normalized_token_id, contract_address_hash, token_id}}, + {success, failed} -> + # credo:disable-for-next-line + case res do + {:ok, result} -> + {[ + result_to_insert_params(result, contract_address_hash, token_id) + | success + ], failed} + + {:exit, reason} -> + { + success, + [ + result_to_insert_params( + {:error, MetadataRetriever.truncate_error("Terminated:" <> inspect(reason))}, + contract_address_hash, + token_id + ) + | failed + ] + } + end + end) + + results ++ success_results_from_retry ++ failed_results_after_retry + else + results ++ failed_results + end + + total_results + |> Enum.map(fn %{token_id: token_id, token_contract_address_hash: contract_address_hash} = result -> + upsert_with_rescue(result, token_id, contract_address_hash) + end) + end + + defp add_failed_to_retry_result(results, failed_results, instances_to_retry, contract_address_hash, token_id, result) do + { + results, + [ + result_to_insert_params(result, contract_address_hash, token_id) + | failed_results + ], + [{contract_address_hash, token_id} | instances_to_retry] + } + end + + defp batch_fetch_instances_inner(_token_instances, _token_types_map, _cryptokitties, from_base_uri? \\ false) + + defp batch_fetch_instances_inner(token_instances, token_types_map, cryptokitties, from_base_uri?) do contract_results = - (other + (token_instances |> Enum.map(fn {contract_address_hash, token_id} -> token_id = prepare_token_id(token_id) contract_address_hash_string = to_string(contract_address_hash) - prepare_request(token_types_map[contract_address_hash_string], contract_address_hash_string, token_id) + prepare_request( + token_types_map[contract_address_hash_string], + contract_address_hash_string, + token_id, + from_base_uri? + ) end) |> Reader.query_contracts(@erc_721_1155_abi, [], false) - |> Enum.zip_reduce(other, [], fn result, {contract_address_hash, token_id}, acc -> + |> Enum.zip_reduce(token_instances, [], fn result, {contract_address_hash, token_id}, acc -> token_id = prepare_token_id(token_id) [ @@ -103,42 +211,30 @@ defmodule Indexer.Fetcher.TokenInstance.Helper do cryptokitties contract_results - |> Enum.map(fn {result, normalized_token_id, _contract_address_hash, _token_id} -> - Task.async(fn -> MetadataRetriever.fetch_json(result, normalized_token_id) end) + |> Enum.map(fn {result, normalized_token_id, _contract_address_hash, token_id} -> + Task.async(fn -> MetadataRetriever.fetch_json(result, token_id, normalized_token_id, from_base_uri?) end) end) |> Task.yield_many(:infinity) |> Enum.zip(contract_results) - |> Enum.map(fn {{_task, res}, {_result, _normalized_token_id, contract_address_hash, token_id}} -> - insert_params = - case res do - {:ok, result} -> - result_to_insert_params(result, contract_address_hash, token_id) - - {:exit, reason} -> - result_to_insert_params( - {:error, MetadataRetriever.truncate_error("Terminated:" <> inspect(reason))}, - contract_address_hash, - token_id - ) - end - - upsert_with_rescue(insert_params, token_id, contract_address_hash) - end) end defp prepare_token_id(%Decimal{} = token_id), do: Decimal.to_integer(token_id) defp prepare_token_id(token_id), do: token_id - defp prepare_request("ERC-721", contract_address_hash_string, token_id) do - %{ + defp prepare_request("ERC-721", contract_address_hash_string, token_id, from_base_uri?) do + request = %{ contract_address: contract_address_hash_string, - method_id: @token_uri, - args: [token_id], block_number: nil } + + if from_base_uri? do + request |> Map.put(:method_id, @base_uri) |> Map.put(:args, []) + else + request |> Map.put(:method_id, @token_uri) |> Map.put(:args, [token_id]) + end end - defp prepare_request(_token_type, contract_address_hash_string, token_id) do + defp prepare_request(_token_type, contract_address_hash_string, token_id, _retry) do %{ contract_address: contract_address_hash_string, method_id: @uri, diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex b/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex index 271da131176b..a713282e4c55 100644 --- a/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex +++ b/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex @@ -36,18 +36,19 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do @doc """ Fetch/parse metadata using smart-contract's response """ - @spec fetch_json(any, binary() | nil) :: {:error, binary} | {:error_code, any} | {:ok, %{metadata: any}} - def fetch_json(uri, hex_token_id \\ nil) + @spec fetch_json(any, binary() | nil, binary() | nil, boolean) :: + {:error, binary} | {:error_code, any} | {:ok, %{metadata: any}} + def fetch_json(uri, token_id \\ nil, hex_token_id \\ nil, from_base_uri? \\ false) - def fetch_json(uri, _hex_token_id) when uri in [{:ok, [""]}, {:ok, [""]}] do + def fetch_json(uri, _token_id, _hex_token_id, _from_base_uri?) when uri in [{:ok, [""]}, {:ok, [""]}] do {:error, @no_uri_error} end - def fetch_json(uri, hex_token_id) do - fetch_json_from_uri(uri, hex_token_id) + def fetch_json(uri, token_id, hex_token_id, from_base_uri?) do + fetch_json_from_uri(uri, token_id, hex_token_id, from_base_uri?) end - defp fetch_json_from_uri({:error, error}, _hex_token_id) do + defp fetch_json_from_uri({:error, error}, _token_id, _hex_token_id, _from_base_uri?) do error = to_string(error) if error =~ "execution reverted" or error =~ @vm_execution_error do @@ -61,9 +62,9 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do end # CIDv0 IPFS links # https://docs.ipfs.tech/concepts/content-addressing/#version-0-v0 - defp fetch_json_from_uri({:ok, ["Qm" <> _ = result]}, hex_token_id) do + defp fetch_json_from_uri({:ok, ["Qm" <> _ = result]}, token_id, hex_token_id, from_base_uri?) do if String.length(result) == 46 do - fetch_json_from_uri({:ok, [ipfs_link() <> result]}, hex_token_id) + fetch_json_from_uri({:ok, [ipfs_link() <> result]}, token_id, hex_token_id, from_base_uri?) else Logger.warn(["Unknown metadata format result #{inspect(result)}."], fetcher: :token_instances) @@ -71,23 +72,23 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do end end - defp fetch_json_from_uri({:ok, ["'" <> token_uri]}, hex_token_id) do + defp fetch_json_from_uri({:ok, ["'" <> token_uri]}, token_id, hex_token_id, from_base_uri?) do token_uri = token_uri |> String.split("'") |> List.first() - fetch_metadata_inner(token_uri, hex_token_id) + fetch_metadata_inner(token_uri, token_id, hex_token_id, from_base_uri?) end - defp fetch_json_from_uri({:ok, ["http://" <> _ = token_uri]}, hex_token_id) do - fetch_metadata_inner(token_uri, hex_token_id) + defp fetch_json_from_uri({:ok, ["http://" <> _ = token_uri]}, token_id, hex_token_id, from_base_uri?) do + fetch_metadata_inner(token_uri, token_id, hex_token_id, from_base_uri?) end - defp fetch_json_from_uri({:ok, ["https://" <> _ = token_uri]}, hex_token_id) do - fetch_metadata_inner(token_uri, hex_token_id) + defp fetch_json_from_uri({:ok, ["https://" <> _ = token_uri]}, token_id, hex_token_id, from_base_uri?) do + fetch_metadata_inner(token_uri, token_id, hex_token_id, from_base_uri?) end - defp fetch_json_from_uri({:ok, ["data:application/json," <> json]}, hex_token_id) do + defp fetch_json_from_uri({:ok, ["data:application/json," <> json]}, token_id, hex_token_id, from_base_uri?) do decoded_json = URI.decode(json) - fetch_json_from_uri({:ok, [decoded_json]}, hex_token_id) + fetch_json_from_uri({:ok, [decoded_json]}, token_id, hex_token_id, from_base_uri?) rescue e -> Logger.warn(["Unknown metadata format #{inspect(json)}.", Exception.format(:error, e, __STACKTRACE__)], @@ -97,10 +98,15 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do {:error, "invalid data:application/json"} end - defp fetch_json_from_uri({:ok, ["data:application/json;base64," <> base64_encoded_json]}, hex_token_id) do + defp fetch_json_from_uri( + {:ok, ["data:application/json;base64," <> base64_encoded_json]}, + token_id, + hex_token_id, + from_base_uri? + ) do case Base.decode64(base64_encoded_json) do {:ok, base64_decoded} -> - fetch_json_from_uri({:ok, [base64_decoded]}, hex_token_id) + fetch_json_from_uri({:ok, [base64_decoded]}, token_id, hex_token_id, from_base_uri?) _ -> {:error, "invalid data:application/json;base64"} @@ -118,19 +124,19 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do {:error, "invalid data:application/json;base64"} end - defp fetch_json_from_uri({:ok, ["#{@ipfs_protocol}ipfs/" <> right]}, hex_token_id) do + defp fetch_json_from_uri({:ok, ["#{@ipfs_protocol}ipfs/" <> right]}, _token_id, hex_token_id, _from_base_uri?) do fetch_from_ipfs(right, hex_token_id) end - defp fetch_json_from_uri({:ok, ["ipfs/" <> right]}, hex_token_id) do + defp fetch_json_from_uri({:ok, ["ipfs/" <> right]}, _token_id, hex_token_id, _from_base_uri?) do fetch_from_ipfs(right, hex_token_id) end - defp fetch_json_from_uri({:ok, [@ipfs_protocol <> right]}, hex_token_id) do + defp fetch_json_from_uri({:ok, [@ipfs_protocol <> right]}, _token_id, hex_token_id, _from_base_uri?) do fetch_from_ipfs(right, hex_token_id) end - defp fetch_json_from_uri({:ok, [json]}, hex_token_id) do + defp fetch_json_from_uri({:ok, [json]}, _token_id, hex_token_id, _from_base_uri?) do json = ExplorerHelper.decode_json(json) check_type(json, hex_token_id) @@ -143,7 +149,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do {:error, "invalid json"} end - defp fetch_json_from_uri(uri, _hex_token_id) do + defp fetch_json_from_uri(uri, _token_id, _hex_token_id, _from_base_uri?) do Logger.warn(["Unknown metadata uri format #{inspect(uri)}."], fetcher: :token_instances) {:error, "unknown metadata uri format"} @@ -151,11 +157,13 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do defp fetch_from_ipfs(ipfs_uid, hex_token_id) do ipfs_url = ipfs_link() <> ipfs_uid - fetch_metadata_inner(ipfs_url, hex_token_id) + fetch_metadata_inner(ipfs_url, nil, hex_token_id) end - defp fetch_metadata_inner(uri, hex_token_id) do - prepared_uri = substitute_token_id_to_token_uri(uri, hex_token_id) + defp fetch_metadata_inner(uri, token_id, hex_token_id, from_base_uri? \\ false) + + defp fetch_metadata_inner(uri, token_id, hex_token_id, from_base_uri?) do + prepared_uri = substitute_token_id_to_token_uri(uri, token_id, hex_token_id, from_base_uri?) fetch_metadata_from_uri(prepared_uri, hex_token_id) rescue e -> @@ -270,9 +278,19 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do {:error, "wrong metadata type"} end - defp substitute_token_id_to_token_uri(token_uri, empty_token_id) when empty_token_id in [nil, ""], do: token_uri + defp substitute_token_id_to_token_uri(base_uri, token_id, _empty_token_id, true) do + if String.ends_with?(base_uri, "/") do + base_uri <> to_string(token_id) + else + base_uri <> "/" <> to_string(token_id) + end + end + + defp substitute_token_id_to_token_uri(token_uri, _token_id, empty_token_id, _from_base_uri?) + when empty_token_id in [nil, ""], + do: token_uri - defp substitute_token_id_to_token_uri(token_uri, hex_token_id) do + defp substitute_token_id_to_token_uri(token_uri, _token_id, hex_token_id, _from_base_uri?) do String.replace(token_uri, @erc1155_token_id_placeholder, hex_token_id) end diff --git a/apps/indexer/test/indexer/fetcher/token_instance/helper_test.exs b/apps/indexer/test/indexer/fetcher/token_instance/helper_test.exs index 8d9987f9de24..bc6fb2dd35ce 100644 --- a/apps/indexer/test/indexer/fetcher/token_instance/helper_test.exs +++ b/apps/indexer/test/indexer/fetcher/token_instance/helper_test.exs @@ -50,26 +50,15 @@ defmodule Indexer.Fetcher.TokenInstance.HelperTest do } """ - abi = - [ - %{ - "type" => "function", - "stateMutability" => "nonpayable", - "payable" => false, - "outputs" => [], - "name" => "tokenURI", - "inputs" => [ - %{"type" => "string", "name" => "name", "internalType" => "string"} - ] - } - ] - |> ABI.parse_specification() - |> Enum.at(0) - encoded_url = - abi - |> Encoder.encode_function_call(["http://localhost:#{bypass.port}/api/card/{id}"]) - |> String.replace("4cf12d26", "") + "0x" <> + (ABI.TypeEncoder.encode(["http://localhost:#{bypass.port}/api/card/{id}"], %ABI.FunctionSelector{ + function: nil, + types: [ + :string + ] + }) + |> Base.encode16(case: :lower)) EthereumJSONRPC.Mox |> expect(:json_rpc, fn [ @@ -174,5 +163,104 @@ defmodule Indexer.Fetcher.TokenInstance.HelperTest do Application.put_env(:explorer, :http_adapter, HTTPoison) end + + test "re-fetch metadata from baseURI", %{bypass: bypass} do + json = """ + { + "name": "123" + } + """ + + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn [ + %{ + id: id, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: + "0xc87b56dd0000000000000000000000000000000000000000000000004f3f5ce294ff3d36", + to: "0x5caebd3b32e210e85ce3e9d51638b9c445481567" + }, + "latest" + ] + } + ], + _options -> + {:ok, + [ + %{ + error: %{code: -32015, data: "Reverted 0x", message: "execution reverted"}, + id: id, + jsonrpc: "2.0" + } + ]} + end) + + encoded_url = + "0x" <> + (ABI.TypeEncoder.encode(["http://localhost:#{bypass.port}/api/card/"], %ABI.FunctionSelector{ + function: nil, + types: [ + :string + ] + }) + |> Base.encode16(case: :lower)) + + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn [ + %{ + id: id, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: "0x6c0360eb", + to: "0x5caebd3b32e210e85ce3e9d51638b9c445481567" + }, + "latest" + ] + } + ], + _options -> + {:ok, + [ + %{ + id: id, + jsonrpc: "2.0", + result: encoded_url + } + ]} + end) + + Bypass.expect( + bypass, + "GET", + "/api/card/5710384980761197878", + fn conn -> + Conn.resp(conn, 200, json) + end + ) + + insert(:token, + contract_address: build(:address, hash: "0x5caebd3b32e210e85ce3e9d51638b9c445481567"), + type: "ERC-721" + ) + + Application.put_env(:indexer, Indexer.Fetcher.TokenInstance.Helper, base_uri_retry?: true) + + assert [ + {:ok, + %Instance{ + metadata: %{ + "name" => "123" + } + }} + ] = + Helper.batch_fetch_instances([{"0x5caebd3b32e210e85ce3e9d51638b9c445481567", 5_710_384_980_761_197_878}]) + + Application.put_env(:indexer, Indexer.Fetcher.TokenInstance.Helper, base_uri_retry?: false) + end end end diff --git a/config/runtime.exs b/config/runtime.exs index b1b86bfedb44..7f17a40cf7ae 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -623,6 +623,9 @@ config :indexer, Indexer.Fetcher.BlockReward, batch_size: ConfigHelper.parse_integer_env_var("INDEXER_BLOCK_REWARD_BATCH_SIZE", 10), concurrency: ConfigHelper.parse_integer_env_var("INDEXER_BLOCK_REWARD_CONCURRENCY", 4) +config :indexer, Indexer.Fetcher.TokenInstance.Helper, + base_uri_retry?: ConfigHelper.parse_bool_env_var("INDEXER_TOKEN_INSTANCE_USE_BASE_URI_RETRY") + config :indexer, Indexer.Fetcher.TokenInstance.Retry, concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_RETRY_CONCURRENCY", 10), batch_size: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_RETRY_BATCH_SIZE", 10), diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 60584a6d1624..697dbe4962c5 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -135,6 +135,7 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false # INDEXER_INTERNAL_TRANSACTIONS_CONCURRENCY= # INDEXER_BLOCK_REWARD_BATCH_SIZE= # INDEXER_BLOCK_REWARD_CONCURRENCY= +# INDEXER_TOKEN_INSTANCE_USE_BASE_URI_RETRY= # INDEXER_TOKEN_INSTANCE_RETRY_REFETCH_INTERVAL= # INDEXER_TOKEN_INSTANCE_RETRY_BATCH_SIZE=10 # INDEXER_TOKEN_INSTANCE_RETRY_CONCURRENCY= From 068bf04cc7d998f89a66ccdbf4dfe1828c1ddc82 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 12 Feb 2024 23:30:47 +0300 Subject: [PATCH 469/607] Filter out Vyper contracts in Solidityscan endpoint --- CHANGELOG.md | 1 + .../controllers/api/v2/fallback_controller.ex | 8 +++++++ .../api/v2/smart_contract_controller.ex | 23 ++++++++++++++----- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cba481ecf922..ccb7482ab8de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ ### Fixes +- [#9387](https://github.com/blockscout/blockscout/pull/9387) - Filter out Vyper contracts in Solidityscan API endpoint - [#9377](https://github.com/blockscout/blockscout/pull/9377) - Speed up account abstraction proxy - [#9371](https://github.com/blockscout/blockscout/pull/9371) - Filter empty values before token update - [#9356](https://github.com/blockscout/blockscout/pull/9356) - Remove ERC-1155 logs params from coin balances params diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex index 52fca8b435af..23dfa66da48c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex @@ -27,6 +27,7 @@ defmodule BlockScoutWeb.API.V2.FallbackController do @wrong_api_key "Wrong API key" @address_not_found "Address not found" @address_is_not_smart_contract "Address is not smart-contract" + @vyper_smart_contract_is_not_supported "Vyper smart-contracts are not supported by SolidityScan" @empty_response "Empty response" @tx_interpreter_service_disabled "Transaction Interpretation Service is not enabled" @disabled "API endpoint is disabled" @@ -261,6 +262,13 @@ defmodule BlockScoutWeb.API.V2.FallbackController do |> render(:message, %{message: @address_is_not_smart_contract}) end + def call(conn, {:is_vyper_contract, result}) when result == true do + conn + |> put_status(:not_found) + |> put_view(ApiView) + |> render(:message, %{message: @vyper_smart_contract_is_not_supported}) + end + def call(conn, {:is_empty_response, true}) do conn |> put_status(500) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex index 294cfaab5f1f..805dfe1065ac 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex @@ -195,17 +195,28 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do | {:is_empty_response, true} | {:is_smart_contract, false | nil} | {:restricted_access, true} + | {:is_vyper_contract, true} | Plug.Conn.t() def solidityscan_report(conn, %{"address_hash" => address_hash_string} = params) do with {:format_address, {:ok, address_hash}} <- {:format_address, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), {:address, {:ok, address}} <- {:address, Chain.hash_to_address(address_hash)}, - {:is_smart_contract, true} <- {:is_smart_contract, Address.smart_contract?(address)}, - response = SolidityScan.solidityscan_request(address_hash_string), - {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do - conn - |> put_status(200) - |> json(response) + {:is_smart_contract, true} <- {:is_smart_contract, Address.smart_contract?(address)} do + smart_contract = SmartContract.address_hash_to_smart_contract_without_twin(address_hash, @api_true) + + if smart_contract && smart_contract.is_vyper_contract do + {:is_vyper_contract, true} + else + response = SolidityScan.solidityscan_request(address_hash_string) + + if is_nil(response) do + {:is_empty_response, true} + else + conn + |> put_status(200) + |> json(response) + end + end end end From dc89d1961f3a316c9cc12c94058aa04fbd8a1166 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 16 Feb 2024 13:14:51 +0300 Subject: [PATCH 470/607] Return indexed raio 1 when there is only 0 block in the chain --- apps/explorer/lib/explorer/chain.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index f15ed25a86e4..51bd0fadbffe 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1423,7 +1423,7 @@ defmodule Explorer.Chain do case {min_saved_block_number, max_saved_block_number} do {0, 0} -> - Decimal.new(0) + Decimal.new(1) _ -> divisor = max_saved_block_number - min_blockchain_block_number + 1 From 2334440b0122f92033224d6d50ff6ff5d9a7bd57 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 16 Feb 2024 19:28:33 +0300 Subject: [PATCH 471/607] indexed_ratio_blocks value is 1, if no blocks --- apps/explorer/lib/explorer/chain.ex | 2 +- apps/explorer/test/explorer/chain_test.exs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 51bd0fadbffe..acab366497ee 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1411,7 +1411,7 @@ defmodule Explorer.Chain do If there are no blocks, the percentage is 0. iex> Explorer.Chain.indexed_ratio_blocks() - Decimal.new(0) + Decimal.new(1) """ @spec indexed_ratio_blocks() :: Decimal.t() diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index d84a614cbf60..e57ebb13a0bc 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -1036,8 +1036,8 @@ defmodule Explorer.ChainTest do assert Decimal.compare(Chain.indexed_ratio_blocks(), Decimal.from_float(0.5)) == :eq end - test "returns 0 if no blocks" do - assert Decimal.new(0) == Chain.indexed_ratio_blocks() + test "returns 1 if no blocks" do + assert Decimal.new(1) == Chain.indexed_ratio_blocks() end test "returns 1.0 if fully indexed blocks" do From 42425edef81d20d1cfd44af57de4cef5b98b1eb5 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop <105209995+Qwerty5Uiop@users.noreply.github.com> Date: Mon, 19 Feb 2024 18:34:23 +0400 Subject: [PATCH 472/607] Null round handling (#9403) * Null round handling * Add repo for filecoin chain type * Add repo for filecoin chain type * Modify gas price constraint for Filecoin as it for PolygonEdge * Fix null round heights db type * Add filecoin to chain-type matrix --------- Co-authored-by: Viktor Baranov --- .github/workflows/config.yml | 2 +- .../publish-docker-image-for-filecoin.yml | 2 +- CHANGELOG.md | 1 + .../models/transaction_state_helper.ex | 12 +-- .../lib/block_scout_web/notifier.ex | 9 ++- apps/block_scout_web/test/test_helper.exs | 1 + apps/explorer/config/dev.exs | 2 + apps/explorer/config/prod.exs | 4 + apps/explorer/config/test.exs | 3 +- apps/explorer/lib/explorer/application.ex | 3 +- apps/explorer/lib/explorer/chain.ex | 75 +++++++++++------ .../lib/explorer/chain/block_number_helper.ex | 25 ++++++ .../explorer/chain/import/runner/blocks.ex | 8 +- .../lib/explorer/chain/null_round_height.ex | 81 +++++++++++++++++++ apps/explorer/lib/explorer/repo.ex | 10 +++ .../explorer/utility/missing_block_range.ex | 21 +++-- ...3_modify_collated_gas_price_constraint.exs | 15 ++++ ...231109104957_create_null_round_heights.exs | 9 +++ ..._change_null_round_heights_height_type.exs | 9 +++ apps/explorer/test/test_helper.exs | 1 + .../lib/indexer/block/catchup/fetcher.ex | 18 ++++- .../lib/indexer/fetcher/transaction_action.ex | 4 +- config/config_helper.exs | 1 + config/runtime/dev.exs | 7 ++ config/runtime/prod.exs | 6 ++ cspell.json | 4 +- 26 files changed, 284 insertions(+), 49 deletions(-) create mode 100644 apps/explorer/lib/explorer/chain/block_number_helper.ex create mode 100644 apps/explorer/lib/explorer/chain/null_round_height.ex create mode 100644 apps/explorer/priv/filecoin/migrations/20230731130103_modify_collated_gas_price_constraint.exs create mode 100644 apps/explorer/priv/filecoin/migrations/20231109104957_create_null_round_heights.exs create mode 100644 apps/explorer/priv/filecoin/migrations/20240219140124_change_null_round_heights_height_type.exs diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 0bec339163ab..4f22355b41b0 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -48,7 +48,7 @@ jobs: run: | echo "matrix=$matrixStringifiedObject" >> $GITHUB_OUTPUT env: - matrixStringifiedObject: '{"chain-type": ["ethereum", "polygon_edge", "polygon_zkevm", "rsk", "suave", "stability"]}' + matrixStringifiedObject: '{"chain-type": ["ethereum", "polygon_edge", "polygon_zkevm", "rsk", "suave", "stability", "filecoin"]}' build-and-cache: name: Build and Cache deps diff --git a/.github/workflows/publish-docker-image-for-filecoin.yml b/.github/workflows/publish-docker-image-for-filecoin.yml index d77f39ac7f99..ca720971fbbc 100644 --- a/.github/workflows/publish-docker-image-for-filecoin.yml +++ b/.github/workflows/publish-docker-image-for-filecoin.yml @@ -36,4 +36,4 @@ jobs: CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=polygon_edge \ No newline at end of file + CHAIN_TYPE=filecoin \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ccb7482ab8de..53bb3211dde4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#9403](https://github.com/blockscout/blockscout/pull/9403) - Null round handling - [#9396](https://github.com/blockscout/blockscout/pull/9396) - More-Minimal Proxy support - [#9379](https://github.com/blockscout/blockscout/pull/9379) - Filter non-traceable transactions for zetachain - [#9364](https://github.com/blockscout/blockscout/pull/9364) - Fix using of startblock/endblock in API v1 list endpoints: txlist, txlistinternal, tokentx diff --git a/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex b/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex index 41b5bd1cfe7b..e37d94adfab9 100644 --- a/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex @@ -8,7 +8,7 @@ defmodule BlockScoutWeb.Models.TransactionStateHelper do alias Explorer.Chain.Transaction.StateChange alias Explorer.{Chain, PagingOptions} - alias Explorer.Chain.{Block, Transaction, Wei} + alias Explorer.Chain.{Block, BlockNumberHelper, Transaction, Wei} alias Explorer.Chain.Cache.StateChanges alias Indexer.Fetcher.{CoinBalanceOnDemand, TokenBalanceOnDemand} @@ -73,9 +73,11 @@ defmodule BlockScoutWeb.Models.TransactionStateHelper do api?: Keyword.get(options, :api?, false) ) - from_before_block = coin_balance(transaction.from_address_hash, block.number - 1, options) - to_before_block = coin_balance(transaction.to_address_hash, block.number - 1, options) - miner_before_block = coin_balance(block.miner_hash, block.number - 1, options) + previous_block_number = BlockNumberHelper.previous_block_number(block.number) + + from_before_block = coin_balance(transaction.from_address_hash, previous_block_number, options) + to_before_block = coin_balance(transaction.to_address_hash, previous_block_number, options) + miner_before_block = coin_balance(block.miner_hash, previous_block_number, options) {from_before_tx, to_before_tx, miner_before_tx} = StateChange.coin_balances_before(transaction, block_txs, from_before_block, to_before_block, miner_before_block) @@ -146,7 +148,7 @@ defmodule BlockScoutWeb.Models.TransactionStateHelper do from = transfer.from_address to = transfer.to_address token_hash = transfer.token_contract_address_hash - prev_block = transfer.block_number - 1 + prev_block = BlockNumberHelper.previous_block_number(transfer.block_number) balances |> case do diff --git a/apps/block_scout_web/lib/block_scout_web/notifier.ex b/apps/block_scout_web/lib/block_scout_web/notifier.ex index 3a70cd316594..b9e5df6ca61d 100644 --- a/apps/block_scout_web/lib/block_scout_web/notifier.ex +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -20,7 +20,7 @@ defmodule BlockScoutWeb.Notifier do alias Explorer.{Chain, Market, Repo} alias Explorer.Chain.Address.Counters - alias Explorer.Chain.{Address, DenormalizationHelper, InternalTransaction, Transaction} + alias Explorer.Chain.{Address, BlockNumberHelper, DenormalizationHelper, InternalTransaction, Transaction} alias Explorer.Chain.Supply.RSK alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.Counters.{AverageBlockTime, Helper} @@ -305,12 +305,13 @@ defmodule BlockScoutWeb.Notifier do defp broadcast_latest_block?(block, last_broadcasted_block_number) do cond do - last_broadcasted_block_number == 0 || last_broadcasted_block_number == block.number - 1 || + last_broadcasted_block_number == 0 || + last_broadcasted_block_number == BlockNumberHelper.previous_block_number(block.number) || last_broadcasted_block_number < block.number - 4 -> broadcast_block(block) :ets.insert(:last_broadcasted_block, {:number, block.number}) - last_broadcasted_block_number > block.number - 1 -> + last_broadcasted_block_number > BlockNumberHelper.previous_block_number(block.number) -> broadcast_block(block) true -> @@ -324,7 +325,7 @@ defmodule BlockScoutWeb.Notifier do :timer.sleep(@check_broadcast_sequence_period) last_broadcasted_block_number = Helper.fetch_from_cache(:number, :last_broadcasted_block) - if last_broadcasted_block_number == block.number - 1 do + if last_broadcasted_block_number == BlockNumberHelper.previous_block_number(block.number) do broadcast_block(block) :ets.insert(:last_broadcasted_block, {:number, block.number}) else diff --git a/apps/block_scout_web/test/test_helper.exs b/apps/block_scout_web/test/test_helper.exs index 35a92622a173..c14efe9d6378 100644 --- a/apps/block_scout_web/test/test_helper.exs +++ b/apps/block_scout_web/test/test_helper.exs @@ -33,6 +33,7 @@ Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Shibarium, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Beacon, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.BridgedTokens, :manual) +Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Filecoin, :manual) Absinthe.Test.prime(BlockScoutWeb.Schema) diff --git a/apps/explorer/config/dev.exs b/apps/explorer/config/dev.exs index 70ff6e48fd92..a56a52620b6e 100644 --- a/apps/explorer/config/dev.exs +++ b/apps/explorer/config/dev.exs @@ -27,6 +27,8 @@ config :explorer, Explorer.Repo.Beacon, timeout: :timer.seconds(80) config :explorer, Explorer.Repo.BridgedTokens, timeout: :timer.seconds(80) +config :explorer, Explorer.Repo.Filecoin, timeout: :timer.seconds(80) + config :explorer, Explorer.Tracer, env: "dev", disabled?: true config :logger, :explorer, diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs index cb1e379e6c9d..68279e1bac3a 100644 --- a/apps/explorer/config/prod.exs +++ b/apps/explorer/config/prod.exs @@ -44,6 +44,10 @@ config :explorer, Explorer.Repo.BridgedTokens, prepare: :unnamed, timeout: :timer.seconds(60) +config :explorer, Explorer.Repo.Filecoin, + prepare: :unnamed, + timeout: :timer.seconds(60) + config :explorer, Explorer.Tracer, env: "production", disabled?: true config :logger, :explorer, diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index 16e54f04eb5d..b1e58b464bb7 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -50,7 +50,8 @@ for repo <- [ Explorer.Repo.RSK, Explorer.Repo.Shibarium, Explorer.Repo.Suave, - Explorer.Repo.BridgedTokens + Explorer.Repo.BridgedTokens, + Explorer.Repo.Filecoin ] do config :explorer, repo, database: "explorer_test", diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 059d4e22140d..1477205e3c4c 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -145,7 +145,8 @@ defmodule Explorer.Application do Explorer.Repo.RSK, Explorer.Repo.Shibarium, Explorer.Repo.Suave, - Explorer.Repo.BridgedTokens + Explorer.Repo.BridgedTokens, + Explorer.Repo.Filecoin ] else [] diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index acab366497ee..0033679fedbd 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -48,6 +48,7 @@ defmodule Explorer.Chain do Address.CurrentTokenBalance, Address.TokenBalance, Block, + BlockNumberHelper, CurrencyHelper, Data, DecompiledSmartContract, @@ -1426,7 +1427,7 @@ defmodule Explorer.Chain do Decimal.new(1) _ -> - divisor = max_saved_block_number - min_blockchain_block_number + 1 + divisor = max_saved_block_number - min_blockchain_block_number - BlockNumberHelper.null_rounds_count() + 1 ratio = get_ratio(BlockCache.estimated_count(), divisor) @@ -1458,7 +1459,9 @@ defmodule Explorer.Chain do Decimal.new(0) _ -> - full_blocks_range = max_saved_block_number - min_blockchain_trace_block_number + 1 + full_blocks_range = + max_saved_block_number - min_blockchain_trace_block_number - BlockNumberHelper.null_rounds_count() + 1 + processed_int_txs_for_blocks_count = max(0, full_blocks_range - pbo_count) ratio = get_ratio(processed_int_txs_for_blocks_count, full_blocks_range) @@ -2211,26 +2214,50 @@ defmodule Explorer.Chain do range_max = max(range_start, range_end) ordered_missing_query = - from(b in Block, - right_join: - missing_range in fragment( - """ - ( - SELECT distinct b1.number - FROM generate_series((?)::integer, (?)::integer) AS b1(number) - WHERE NOT EXISTS - (SELECT 1 FROM blocks b2 WHERE b2.number=b1.number AND b2.consensus) - ORDER BY b1.number DESC - ) - """, - ^range_min, - ^range_max - ), - on: b.number == missing_range.number, - select: missing_range.number, - order_by: missing_range.number, - distinct: missing_range.number - ) + if Application.get_env(:explorer, :chain_type) == "filecoin" do + from(b in Block, + right_join: + missing_range in fragment( + """ + ( + SELECT distinct b1.number + FROM generate_series((?)::integer, (?)::integer) AS b1(number) + WHERE NOT EXISTS + (SELECT 1 FROM blocks b2 WHERE b2.number=b1.number AND b2.consensus) + AND NOT EXISTS (SELECT 1 FROM null_round_heights nrh where nrh.height=b1.number) + ORDER BY b1.number DESC + ) + """, + ^range_min, + ^range_max + ), + on: b.number == missing_range.number, + select: missing_range.number, + order_by: missing_range.number, + distinct: missing_range.number + ) + else + from(b in Block, + right_join: + missing_range in fragment( + """ + ( + SELECT distinct b1.number + FROM generate_series((?)::integer, (?)::integer) AS b1(number) + WHERE NOT EXISTS + (SELECT 1 FROM blocks b2 WHERE b2.number=b1.number AND b2.consensus) + ORDER BY b1.number DESC + ) + """, + ^range_min, + ^range_max + ), + on: b.number == missing_range.number, + select: missing_range.number, + order_by: missing_range.number, + distinct: missing_range.number + ) + end missing_blocks = Repo.all(ordered_missing_query, timeout: :infinity) @@ -2368,13 +2395,13 @@ defmodule Explorer.Chain do DateTime.compare(timestamp, given_timestamp) == :eq do number else - number - 1 + BlockNumberHelper.previous_block_number(number) end :after -> if DateTime.compare(timestamp, given_timestamp) == :lt || DateTime.compare(timestamp, given_timestamp) == :eq do - number + 1 + BlockNumberHelper.next_block_number(number) else number end diff --git a/apps/explorer/lib/explorer/chain/block_number_helper.ex b/apps/explorer/lib/explorer/chain/block_number_helper.ex new file mode 100644 index 000000000000..42da4d376ed7 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/block_number_helper.ex @@ -0,0 +1,25 @@ +# credo:disable-for-this-file +defmodule Explorer.Chain.BlockNumberHelper do + @moduledoc """ + Functions to operate with block numbers based on null round heights (applicable for CHAIN_TYPE=filecoin) + """ + + def previous_block_number(number), do: neighbor_block_number(number, :previous) + + def next_block_number(number), do: neighbor_block_number(number, :next) + + case Application.compile_env(:explorer, :chain_type) do + "filecoin" -> + def null_rounds_count, do: Explorer.Chain.NullRoundHeight.total() + + defp neighbor_block_number(number, direction), + do: Explorer.Chain.NullRoundHeight.neighbor_block_number(number, direction) + + _ -> + def null_rounds_count, do: 0 + defp neighbor_block_number(number, direction), do: move_by_one(number, direction) + end + + def move_by_one(number, :previous), do: number - 1 + def move_by_one(number, :next), do: number + 1 +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex index 4ed9d436d99e..fb66db434b91 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex @@ -14,6 +14,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do alias Explorer.Chain.{ Address, Block, + BlockNumberHelper, Import, PendingBlockOperation, Token, @@ -884,11 +885,14 @@ defmodule Explorer.Chain.Import.Runner.Blocks do number: number }, acc -> + previous_block_number = BlockNumberHelper.previous_block_number(number) + next_block_number = BlockNumberHelper.next_block_number(number) + if consensus do from( block in acc, - or_where: block.number == ^(number - 1) and block.hash != ^parent_hash, - or_where: block.number == ^(number + 1) and block.parent_hash != ^hash + or_where: block.number == ^previous_block_number and block.hash != ^parent_hash, + or_where: block.number == ^next_block_number and block.parent_hash != ^hash ) else acc diff --git a/apps/explorer/lib/explorer/chain/null_round_height.ex b/apps/explorer/lib/explorer/chain/null_round_height.ex new file mode 100644 index 000000000000..41d0fb19049c --- /dev/null +++ b/apps/explorer/lib/explorer/chain/null_round_height.ex @@ -0,0 +1,81 @@ +defmodule Explorer.Chain.NullRoundHeight do + @moduledoc """ + A null round is formed when a block at height N links to a block at height N-2 instead of N-1 + """ + + use Explorer.Schema + + alias Explorer.Repo + + @primary_key false + schema "null_round_heights" do + field(:height, :integer, primary_key: true) + end + + def changeset(null_round_height \\ %__MODULE__{}, params) do + null_round_height + |> cast(params, [:height]) + |> validate_required([:height]) + |> unique_constraint(:height) + end + + def total do + Repo.aggregate(__MODULE__, :count) + end + + def insert_heights(heights) do + params = + heights + |> Enum.uniq() + |> Enum.map(&%{height: &1}) + + Repo.insert_all(__MODULE__, params, on_conflict: :nothing) + end + + defp find_neighbor_from_previous(previous_null_rounds, number, direction) do + previous_null_rounds + |> Enum.reduce_while({number, nil}, fn height, {current, _result} -> + if height == move_by_one(current, direction) do + {:cont, {height, nil}} + else + {:halt, {nil, move_by_one(current, direction)}} + end + end) + |> elem(1) + |> case do + nil -> + previous_null_rounds + |> List.last() + |> neighbor_block_number(direction) + + number -> + number + end + end + + def neighbor_block_number(number, direction) do + number + |> neighbors_query(direction) + |> select([nrh], nrh.height) + |> Repo.all() + |> case do + [] -> + move_by_one(number, direction) + + previous_null_rounds -> + find_neighbor_from_previous(previous_null_rounds, number, direction) + end + end + + defp move_by_one(number, :previous), do: number - 1 + defp move_by_one(number, :next), do: number + 1 + + @batch_size 5 + defp neighbors_query(number, :previous) do + from(nrh in __MODULE__, where: nrh.height < ^number, order_by: [desc: :height], limit: @batch_size) + end + + defp neighbors_query(number, :next) do + from(nrh in __MODULE__, where: nrh.height > ^number, order_by: [asc: :height], limit: @batch_size) + end +end diff --git a/apps/explorer/lib/explorer/repo.ex b/apps/explorer/lib/explorer/repo.ex index 5025d5f3b0c1..3bf981b31908 100644 --- a/apps/explorer/lib/explorer/repo.ex +++ b/apps/explorer/lib/explorer/repo.ex @@ -220,4 +220,14 @@ defmodule Explorer.Repo do ConfigHelper.init_repo_module(__MODULE__, opts) end end + + defmodule Filecoin do + use Ecto.Repo, + otp_app: :explorer, + adapter: Ecto.Adapters.Postgres + + def init(_, opts) do + ConfigHelper.init_repo_module(__MODULE__, opts) + end + end end diff --git a/apps/explorer/lib/explorer/utility/missing_block_range.ex b/apps/explorer/lib/explorer/utility/missing_block_range.ex index 537470a92d16..1bfd30b34044 100644 --- a/apps/explorer/lib/explorer/utility/missing_block_range.ex +++ b/apps/explorer/lib/explorer/utility/missing_block_range.ex @@ -4,6 +4,7 @@ defmodule Explorer.Utility.MissingBlockRange do """ use Explorer.Schema + alias Explorer.Chain.BlockNumberHelper alias Explorer.Repo @default_returning_batch_size 10 @@ -76,21 +77,29 @@ defmodule Explorer.Utility.MissingBlockRange do case {lower_range, higher_range} do {%__MODULE__{} = same_range, %__MODULE__{} = same_range} -> Repo.delete(same_range) - insert_if_needed(%{from_number: same_range.from_number, to_number: max_number + 1}) - insert_if_needed(%{from_number: min_number - 1, to_number: same_range.to_number}) + + insert_if_needed(%{ + from_number: same_range.from_number, + to_number: BlockNumberHelper.next_block_number(max_number) + }) + + insert_if_needed(%{ + from_number: BlockNumberHelper.previous_block_number(min_number), + to_number: same_range.to_number + }) {%__MODULE__{} = range, nil} -> delete_ranges_between(max_number, range.from_number) - update_from_number_or_delete_range(range, min_number - 1) + update_from_number_or_delete_range(range, BlockNumberHelper.previous_block_number(min_number)) {nil, %__MODULE__{} = range} -> delete_ranges_between(range.to_number, min_number) - update_to_number_or_delete_range(range, max_number + 1) + update_to_number_or_delete_range(range, BlockNumberHelper.next_block_number(max_number)) {%__MODULE__{} = range_1, %__MODULE__{} = range_2} -> delete_ranges_between(range_2.to_number, range_1.from_number) - update_from_number_or_delete_range(range_1, min_number - 1) - update_to_number_or_delete_range(range_2, max_number + 1) + update_from_number_or_delete_range(range_1, BlockNumberHelper.previous_block_number(min_number)) + update_to_number_or_delete_range(range_2, BlockNumberHelper.next_block_number(max_number)) _ -> delete_ranges_between(max_number, min_number) diff --git a/apps/explorer/priv/filecoin/migrations/20230731130103_modify_collated_gas_price_constraint.exs b/apps/explorer/priv/filecoin/migrations/20230731130103_modify_collated_gas_price_constraint.exs new file mode 100644 index 000000000000..18ff64b5f382 --- /dev/null +++ b/apps/explorer/priv/filecoin/migrations/20230731130103_modify_collated_gas_price_constraint.exs @@ -0,0 +1,15 @@ +defmodule Explorer.Repo.Filecoin.Migrations.ModifyCollatedGasPriceConstraint do + use Ecto.Migration + + def change do + execute("ALTER TABLE transactions DROP CONSTRAINT collated_gas_price") + + create( + constraint( + :transactions, + :collated_gas_price, + check: "block_hash IS NULL OR gas_price IS NOT NULL OR max_fee_per_gas IS NOT NULL" + ) + ) + end +end diff --git a/apps/explorer/priv/filecoin/migrations/20231109104957_create_null_round_heights.exs b/apps/explorer/priv/filecoin/migrations/20231109104957_create_null_round_heights.exs new file mode 100644 index 000000000000..081d17637dab --- /dev/null +++ b/apps/explorer/priv/filecoin/migrations/20231109104957_create_null_round_heights.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Filecoin.Migrations.CreateNullRoundHeights do + use Ecto.Migration + + def change do + create table(:null_round_heights, primary_key: false) do + add(:height, :integer, primary_key: true) + end + end +end diff --git a/apps/explorer/priv/filecoin/migrations/20240219140124_change_null_round_heights_height_type.exs b/apps/explorer/priv/filecoin/migrations/20240219140124_change_null_round_heights_height_type.exs new file mode 100644 index 000000000000..590a313661a3 --- /dev/null +++ b/apps/explorer/priv/filecoin/migrations/20240219140124_change_null_round_heights_height_type.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Filecoin.Migrations.ChangeNullRoundHeightsHeightType do + use Ecto.Migration + + def change do + alter table(:null_round_heights) do + modify(:height, :bigint) + end + end +end diff --git a/apps/explorer/test/test_helper.exs b/apps/explorer/test/test_helper.exs index 4df42f7bd238..882fb7a26c1a 100644 --- a/apps/explorer/test/test_helper.exs +++ b/apps/explorer/test/test_helper.exs @@ -20,6 +20,7 @@ Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Shibarium, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Beacon, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.BridgedTokens, :auto) +Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Filecoin, :auto) Mox.defmock(Explorer.ExchangeRates.Source.TestSource, for: Explorer.ExchangeRates.Source) Mox.defmock(Explorer.Market.History.Source.Price.TestSource, for: Explorer.Market.History.Source.Price) diff --git a/apps/indexer/lib/indexer/block/catchup/fetcher.ex b/apps/indexer/lib/indexer/block/catchup/fetcher.ex index f0486eacfd9b..8326e2945127 100644 --- a/apps/indexer/lib/indexer/block/catchup/fetcher.ex +++ b/apps/indexer/lib/indexer/block/catchup/fetcher.ex @@ -24,6 +24,7 @@ defmodule Indexer.Block.Catchup.Fetcher do alias Ecto.Changeset alias Explorer.Chain + alias Explorer.Chain.NullRoundHeight alias Explorer.Utility.MissingRangesManipulator alias Indexer.{Block, Tracer} alias Indexer.Block.Catchup.{Sequence, TaskSupervisor} @@ -200,7 +201,8 @@ defmodule Indexer.Block.Catchup.Fetcher do case result do {:ok, %{inserted: inserted, errors: errors}} -> - errors = cap_seq(sequence, errors) + valid_errors = handle_null_rounds(errors) + errors = cap_seq(sequence, valid_errors) retry(sequence, errors) clear_missing_ranges(range, errors) @@ -252,6 +254,20 @@ defmodule Indexer.Block.Catchup.Fetcher do {:error, exception} end + defp handle_null_rounds(errors) do + {null_rounds, other_errors} = + Enum.split_with(errors, fn + %{message: "requested epoch was a null round"} -> true + _ -> false + end) + + null_rounds + |> Enum.map(&block_error_to_number/1) + |> NullRoundHeight.insert_heights() + + other_errors + end + defp cap_seq(seq, errors) do {not_founds, other_errors} = Enum.split_with(errors, fn diff --git a/apps/indexer/lib/indexer/fetcher/transaction_action.ex b/apps/indexer/lib/indexer/fetcher/transaction_action.ex index 6fddbf52a736..1d142aad502c 100644 --- a/apps/indexer/lib/indexer/fetcher/transaction_action.ex +++ b/apps/indexer/lib/indexer/fetcher/transaction_action.ex @@ -15,7 +15,7 @@ defmodule Indexer.Fetcher.TransactionAction do alias Explorer.{Chain, Repo} alias Explorer.Helper, as: ExplorerHelper - alias Explorer.Chain.{Block, Log, TransactionAction} + alias Explorer.Chain.{Block, BlockNumberHelper, Log, TransactionAction} alias Indexer.Transform.{Addresses, TransactionActions} @stage_first_block "tx_action_first_block" @@ -157,7 +157,7 @@ defmodule Indexer.Fetcher.TransactionAction do |> Decimal.round(2) |> Decimal.to_string() - next_block_new = block_number - 1 + next_block_new = BlockNumberHelper.previous_block_number(block_number) Logger.info( "Block #{block_number} handled successfully. Progress: #{progress_percentage}%. Initial block range: #{first_block}..#{last_block}." <> diff --git a/config/config_helper.exs b/config/config_helper.exs index 7b7a46114d58..45e34c40cd7f 100644 --- a/config/config_helper.exs +++ b/config/config_helper.exs @@ -15,6 +15,7 @@ defmodule ConfigHelper do "rsk" -> base_repos ++ [Explorer.Repo.RSK] "shibarium" -> base_repos ++ [Explorer.Repo.Shibarium] "suave" -> base_repos ++ [Explorer.Repo.Suave] + "filecoin" -> base_repos ++ [Explorer.Repo.Filecoin] _ -> base_repos end diff --git a/config/runtime/dev.exs b/config/runtime/dev.exs index e9d66539c07a..12809ebda8b1 100644 --- a/config/runtime/dev.exs +++ b/config/runtime/dev.exs @@ -133,6 +133,13 @@ config :explorer, Explorer.Repo.Suave, url: ExplorerConfigHelper.get_suave_db_url(), pool_size: 1 +# Configure Filecoin database +config :explorer, Explorer.Repo.Filecoin, + database: database, + hostname: hostname, + url: System.get_env("DATABASE_URL"), + pool_size: 1 + variant = Variant.get() Code.require_file("#{variant}.exs", "apps/explorer/config/dev") diff --git a/config/runtime/prod.exs b/config/runtime/prod.exs index 21f40dc30a15..7474cde488cf 100644 --- a/config/runtime/prod.exs +++ b/config/runtime/prod.exs @@ -101,6 +101,12 @@ config :explorer, Explorer.Repo.Suave, pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() +# Configures Filecoin database +config :explorer, Explorer.Repo.Filecoin, + url: System.get_env("DATABASE_URL"), + pool_size: 1, + ssl: ExplorerConfigHelper.ssl_enabled?() + variant = Variant.get() Code.require_file("#{variant}.exs", "apps/explorer/config/prod") diff --git a/cspell.json b/cspell.json index 0fe81962f174..26a129ae43c4 100644 --- a/cspell.json +++ b/cspell.json @@ -573,7 +573,9 @@ "checkproxyverification", "NOTOK", "sushiswap", - "zetachain" + "zetachain", + "filecoin", + "Filecoin" ], "enableFiletypes": [ "dotenv", From 6bf70ae7bf891f88af229ee355312212c9f9deca Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 12 Feb 2024 21:49:53 +0300 Subject: [PATCH 473/607] Filecoin JSON RPC variant --- CHANGELOG.md | 1 + .../lib/ethereum_jsonrpc/filecoin.ex | 218 ++++++++++++++++++ .../lib/ethereum_jsonrpc/geth.ex | 6 +- .../lib/ethereum_jsonrpc/variant.ex | 17 +- apps/explorer/config/dev/filecoin.exs | 33 +++ apps/explorer/config/prod/filecoin.exs | 33 +++ apps/explorer/config/test/filecoin.exs | 13 ++ apps/indexer/config/dev/filecoin.exs | 39 ++++ apps/indexer/config/prod/filecoin.exs | 39 ++++ apps/indexer/config/test/filecoin.exs | 8 + .../indexer/fetcher/internal_transaction.ex | 3 +- cspell.json | 2 + 12 files changed, 408 insertions(+), 4 deletions(-) create mode 100644 apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex create mode 100644 apps/explorer/config/dev/filecoin.exs create mode 100644 apps/explorer/config/prod/filecoin.exs create mode 100644 apps/explorer/config/test/filecoin.exs create mode 100644 apps/indexer/config/dev/filecoin.exs create mode 100644 apps/indexer/config/prod/filecoin.exs create mode 100644 apps/indexer/config/test/filecoin.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 53bb3211dde4..a2262ef85a7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - [#9403](https://github.com/blockscout/blockscout/pull/9403) - Null round handling - [#9396](https://github.com/blockscout/blockscout/pull/9396) - More-Minimal Proxy support +- [#9386](https://github.com/blockscout/blockscout/pull/9386) - Filecoin JSON RPC variant - [#9379](https://github.com/blockscout/blockscout/pull/9379) - Filter non-traceable transactions for zetachain - [#9364](https://github.com/blockscout/blockscout/pull/9364) - Fix using of startblock/endblock in API v1 list endpoints: txlist, txlistinternal, tokentx - [#9360](https://github.com/blockscout/blockscout/pull/9360) - Move missing ranges sanitize to a separate background migration diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex new file mode 100644 index 000000000000..d52dcb59f819 --- /dev/null +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex @@ -0,0 +1,218 @@ +defmodule EthereumJSONRPC.Filecoin do + @moduledoc """ + Ethereum JSONRPC methods that are only supported by Filecoin. + """ + + require Logger + + import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2, request: 1] + + alias EthereumJSONRPC.Geth + alias EthereumJSONRPC.Geth.Calls + + @behaviour EthereumJSONRPC.Variant + + @doc """ + Block reward contract beneficiary fetching is not supported currently for Geth. + + To signal to the caller that fetching is not supported, `:ignore` is returned. + """ + @impl EthereumJSONRPC.Variant + def fetch_beneficiaries(_block_range, _json_rpc_named_arguments), do: :ignore + + @doc """ + Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params. + """ + @impl EthereumJSONRPC.Variant + def fetch_internal_transactions(_transactions_params, _json_rpc_named_arguments), do: :ignore + + @doc """ + Fetches the first trace from the trace URL. + """ + @impl EthereumJSONRPC.Variant + def fetch_first_trace(_transactions_params, _json_rpc_named_arguments), do: :ignore + + @doc """ + Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Geth trace URL. + """ + @impl EthereumJSONRPC.Variant + def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) do + id_to_params = id_to_params(block_numbers) + + with {:ok, blocks_responses} <- + id_to_params + |> debug_trace_block_by_number_requests() + |> json_rpc(json_rpc_named_arguments), + :ok <- Geth.check_errors_exist(blocks_responses, id_to_params) do + transactions_params = to_transactions_params(blocks_responses, id_to_params) + + {transactions_id_to_params, transactions_responses} = + Enum.reduce(transactions_params, {%{}, []}, fn {params, calls}, {id_to_params_acc, calls_acc} -> + {Map.put(id_to_params_acc, params[:id], params), [calls | calls_acc]} + end) + + debug_trace_transaction_responses_to_internal_transactions_params( + transactions_responses, + transactions_id_to_params, + json_rpc_named_arguments + ) + end + end + + defp to_transactions_params(blocks_responses, id_to_params) do + Enum.reduce(blocks_responses, [], fn %{id: id, result: tx_result}, blocks_acc -> + extract_transactions_params(Map.fetch!(id_to_params, id), tx_result) ++ blocks_acc + end) + end + + defp extract_transactions_params(block_number, tx_result) do + tx_result + |> Enum.reduce({[], 0}, fn %{"transactionHash" => tx_hash} = calls_result, {tx_acc, counter} -> + { + [ + {%{block_number: block_number, hash_data: tx_hash, transaction_index: counter, id: counter}, + %{id: counter, result: calls_result}} + | tx_acc + ], + counter + 1 + } + end) + |> elem(0) + end + + @doc """ + Fetches the pending transactions from the Geth node. + """ + @impl EthereumJSONRPC.Variant + def fetch_pending_transactions(_json_rpc_named_arguments), do: :ignore + + defp debug_trace_block_by_number_requests(id_to_params) do + Enum.map(id_to_params, &debug_trace_block_by_number_request/1) + end + + defp debug_trace_block_by_number_request({id, block_number}) do + request(%{ + id: id, + method: "trace_block", + params: [integer_to_quantity(block_number)] + }) + end + + defp debug_trace_transaction_responses_to_internal_transactions_params( + responses, + id_to_params, + _json_rpc_named_arguments + ) + when is_list(responses) and is_map(id_to_params) do + responses + |> EthereumJSONRPC.sanitize_responses(id_to_params) + |> Enum.map(&debug_trace_transaction_response_to_internal_transactions_params(&1, id_to_params)) + |> Geth.reduce_internal_transactions_params() + end + + defp debug_trace_transaction_response_to_internal_transactions_params(%{id: id, result: calls}, id_to_params) + when is_map(id_to_params) do + %{block_number: block_number, hash_data: transaction_hash, transaction_index: transaction_index} = + Map.fetch!(id_to_params, id) + + internal_transaction_params = + calls + |> prepare_calls() + |> (&if(is_list(&1), do: &1, else: [&1])).() + |> Enum.map(fn trace -> + Map.merge(trace, %{ + "blockNumber" => block_number, + "transactionIndex" => transaction_index, + "transactionHash" => transaction_hash + }) + end) + |> Calls.to_internal_transactions_params() + + {:ok, internal_transaction_params} + end + + defp debug_trace_transaction_response_to_internal_transactions_params(%{id: id, error: error}, id_to_params) + when is_map(id_to_params) do + %{ + block_number: block_number, + hash_data: "0x" <> transaction_hash_digits = transaction_hash, + transaction_index: transaction_index + } = Map.fetch!(id_to_params, id) + + not_found_message = "transaction " <> transaction_hash_digits <> " not found" + + normalized_error = + case error do + %{code: -32_000, message: ^not_found_message} -> + %{message: :not_found} + + %{code: -32_000, message: "execution timeout"} -> + %{message: :timeout} + + _ -> + error + end + + annotated_error = + Map.put(normalized_error, :data, %{ + block_number: block_number, + transaction_index: transaction_index, + transaction_hash: transaction_hash + }) + + {:error, annotated_error} + end + + def prepare_calls(calls) do + parse_trace_block_calls(calls) + end + + defp parse_trace_block_calls(calls) + defp parse_trace_block_calls(%{"type" => 0} = res), do: res + + defp parse_trace_block_calls(%{"Type" => type} = call) do + sanitized_call = + call + |> Map.put("type", type) + |> Map.drop(["Type"]) + + parse_trace_block_calls(sanitized_call) + end + + defp parse_trace_block_calls( + %{"type" => upcase_type, "action" => %{"from" => from} = action, "result" => result} = call + ) do + type = String.downcase(upcase_type) + + to = Map.get(action, "to", "0x") + input = Map.get(action, "input", "0x") + + %{ + "type" => if(type in ~w(call callcode delegatecall staticcall), do: "call", else: type), + "callType" => type, + "from" => from, + "to" => to, + "createdContractAddressHash" => to, + "value" => Map.get(action, "value", "0x0"), + "gas" => Map.get(action, "gas", "0x0"), + "gasUsed" => Map.get(result, "gasUsed", "0x0"), + "input" => input, + "init" => input, + "createdContractCode" => Map.get(result, "output", "0x"), + "traceAddress" => Map.get(call, "traceAddress", []), + "index" => Map.get(call, "transactionPosition", 0), + # : check, that error is returned in the root of the call + "error" => call["error"] + } + |> case do + %{"error" => nil} = ok_call -> + ok_call + |> Map.delete("error") + # to handle staticcall, all other cases handled by EthereumJSONRPC.Geth.Call.elixir_to_internal_transaction_params/1 + |> Map.put("output", Map.get(call, "output", "0x")) + + error_call -> + error_call + end + end +end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex index 96379d75bb5c..dc9b556892e3 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex @@ -89,7 +89,8 @@ defmodule EthereumJSONRPC.Geth do end end - defp check_errors_exist(blocks_responses, id_to_params) do + @spec check_errors_exist(list(), %{non_neg_integer() => any()}) :: :ok | {:error, list()} + def check_errors_exist(blocks_responses, id_to_params) do blocks_responses |> EthereumJSONRPC.sanitize_responses(id_to_params) |> Enum.reduce([], fn @@ -400,7 +401,8 @@ defmodule EthereumJSONRPC.Geth do |> Enum.reduce(acc, &parse_call_tracer_calls(&1, &2, trace_address)) end - defp reduce_internal_transactions_params(internal_transactions_params) when is_list(internal_transactions_params) do + @spec reduce_internal_transactions_params(list()) :: {:ok, list()} | {:error, list()} + def reduce_internal_transactions_params(internal_transactions_params) when is_list(internal_transactions_params) do internal_transactions_params |> Enum.reduce({:ok, []}, &internal_transactions_params_reducer/2) |> finalize_internal_transactions_params() diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex index 2468636538a5..4d94b94e6909 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex @@ -96,7 +96,9 @@ defmodule EthereumJSONRPC.Variant do ) :: {:ok, [raw_trace_params]} | {:error, reason :: term} | :ignore def get do - variant = System.get_env("ETHEREUM_JSONRPC_VARIANT", "nethermind") + default_variant = get_default_variant() + + variant = System.get_env("ETHEREUM_JSONRPC_VARIANT", default_variant) cond do is_nil(variant) -> @@ -112,4 +114,17 @@ defmodule EthereumJSONRPC.Variant do |> String.downcase() end end + + defp get_default_variant do + case Application.get_env(:explorer, :chain_type) do + "polygon_zkevm" -> "geth" + "zetachain" -> "geth" + "shibarium" -> "geth" + "stability" -> "geth" + "zksync" -> "geth" + "rsk" -> "rsk" + "filecoin" -> "filecoin" + _ -> "nethermind" + end + end end diff --git a/apps/explorer/config/dev/filecoin.exs b/apps/explorer/config/dev/filecoin.exs new file mode 100644 index 000000000000..68991f7b6910 --- /dev/null +++ b/apps/explorer/config/dev/filecoin.exs @@ -0,0 +1,33 @@ +import Config + +~w(config config_helper.exs) +|> Path.join() +|> Code.eval_file() + +hackney_opts = ConfigHelper.hackney_options() +timeout = ConfigHelper.timeout(1) + +config :explorer, + json_rpc_named_arguments: [ + transport: EthereumJSONRPC.HTTP, + transport_options: [ + http: EthereumJSONRPC.HTTP.HTTPoison, + url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:1234/rpc/v1", + fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:1234/rpc/v1"), + trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:1234/rpc/v1" + ], + http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] + ], + variant: EthereumJSONRPC.Filecoin + ], + subscribe_named_arguments: [ + transport: EthereumJSONRPC.WebSocket, + transport_options: [ + web_socket: EthereumJSONRPC.WebSocket.WebSocketClient, + url: System.get_env("ETHEREUM_JSONRPC_WS_URL") + ], + variant: EthereumJSONRPC.Filecoin + ] diff --git a/apps/explorer/config/prod/filecoin.exs b/apps/explorer/config/prod/filecoin.exs new file mode 100644 index 000000000000..d48af23462de --- /dev/null +++ b/apps/explorer/config/prod/filecoin.exs @@ -0,0 +1,33 @@ +import Config + +~w(config config_helper.exs) +|> Path.join() +|> Code.eval_file() + +hackney_opts = ConfigHelper.hackney_options() +timeout = ConfigHelper.timeout(1) + +config :explorer, + json_rpc_named_arguments: [ + transport: EthereumJSONRPC.HTTP, + transport_options: [ + http: EthereumJSONRPC.HTTP.HTTPoison, + url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), + fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url(), + trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") + ], + http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] + ], + variant: EthereumJSONRPC.Filecoin + ], + subscribe_named_arguments: [ + transport: EthereumJSONRPC.WebSocket, + transport_options: [ + web_socket: EthereumJSONRPC.WebSocket.WebSocketClient, + url: System.get_env("ETHEREUM_JSONRPC_WS_URL") + ], + variant: EthereumJSONRPC.Filecoin + ] diff --git a/apps/explorer/config/test/filecoin.exs b/apps/explorer/config/test/filecoin.exs new file mode 100644 index 000000000000..e25ea90700c9 --- /dev/null +++ b/apps/explorer/config/test/filecoin.exs @@ -0,0 +1,13 @@ +import Config + +config :explorer, + json_rpc_named_arguments: [ + transport: EthereumJSONRPC.Mox, + transport_options: [], + variant: EthereumJSONRPC.Filecoin + ], + subscribe_named_arguments: [ + transport: EthereumJSONRPC.Mox, + transport_options: [], + variant: EthereumJSONRPC.Filecoin + ] diff --git a/apps/indexer/config/dev/filecoin.exs b/apps/indexer/config/dev/filecoin.exs new file mode 100644 index 000000000000..7bf6f8be2613 --- /dev/null +++ b/apps/indexer/config/dev/filecoin.exs @@ -0,0 +1,39 @@ +import Config + +~w(config config_helper.exs) +|> Path.join() +|> Code.eval_file() + +hackney_opts = ConfigHelper.hackney_options() +timeout = ConfigHelper.timeout(1) + +config :indexer, + block_interval: ConfigHelper.parse_time_env_var("INDEXER_CATCHUP_BLOCK_INTERVAL", "5s"), + json_rpc_named_arguments: [ + transport: + if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http", + do: EthereumJSONRPC.HTTP, + else: EthereumJSONRPC.IPC + ), + transport_options: [ + http: EthereumJSONRPC.HTTP.HTTPoison, + url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:1234/rpc/v1", + fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:1234/rpc/v1"), + trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:1234/rpc/v1" + ], + http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] + ], + variant: EthereumJSONRPC.Filecoin + ], + subscribe_named_arguments: [ + transport: + System.get_env("ETHEREUM_JSONRPC_WS_URL") && System.get_env("ETHEREUM_JSONRPC_WS_URL") !== "" && + EthereumJSONRPC.WebSocket, + transport_options: [ + web_socket: EthereumJSONRPC.WebSocket.WebSocketClient, + url: System.get_env("ETHEREUM_JSONRPC_WS_URL") + ] + ] diff --git a/apps/indexer/config/prod/filecoin.exs b/apps/indexer/config/prod/filecoin.exs new file mode 100644 index 000000000000..36e0ea0bbba0 --- /dev/null +++ b/apps/indexer/config/prod/filecoin.exs @@ -0,0 +1,39 @@ +import Config + +~w(config config_helper.exs) +|> Path.join() +|> Code.eval_file() + +hackney_opts = ConfigHelper.hackney_options() +timeout = ConfigHelper.timeout(10) + +config :indexer, + block_interval: ConfigHelper.parse_time_env_var("INDEXER_CATCHUP_BLOCK_INTERVAL", "5s"), + json_rpc_named_arguments: [ + transport: + if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http", + do: EthereumJSONRPC.HTTP, + else: EthereumJSONRPC.IPC + ), + transport_options: [ + http: EthereumJSONRPC.HTTP.HTTPoison, + url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), + fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url(), + trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") + ], + http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] + ], + variant: EthereumJSONRPC.Filecoin + ], + subscribe_named_arguments: [ + transport: + System.get_env("ETHEREUM_JSONRPC_WS_URL") && System.get_env("ETHEREUM_JSONRPC_WS_URL") !== "" && + EthereumJSONRPC.WebSocket, + transport_options: [ + web_socket: EthereumJSONRPC.WebSocket.WebSocketClient, + url: System.get_env("ETHEREUM_JSONRPC_WS_URL") + ] + ] diff --git a/apps/indexer/config/test/filecoin.exs b/apps/indexer/config/test/filecoin.exs new file mode 100644 index 000000000000..a7509d5e827e --- /dev/null +++ b/apps/indexer/config/test/filecoin.exs @@ -0,0 +1,8 @@ +import Config + +config :indexer, + json_rpc_named_arguments: [ + transport: EthereumJSONRPC.Mox, + transport_options: [], + variant: EthereumJSONRPC.Filecoin + ] diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex index 12e9c4251535..8bca28581e7f 100644 --- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex @@ -164,7 +164,8 @@ defmodule Indexer.Fetcher.InternalTransaction do EthereumJSONRPC.Nethermind, EthereumJSONRPC.Erigon, EthereumJSONRPC.Besu, - EthereumJSONRPC.RSK + EthereumJSONRPC.RSK, + EthereumJSONRPC.Filecoin ] defp block_traceable_variants do if Application.get_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth)[:block_traceable?] do diff --git a/cspell.json b/cspell.json index 26a129ae43c4..1d791a23e111 100644 --- a/cspell.json +++ b/cspell.json @@ -171,6 +171,7 @@ "Faileddi", "falala", "Filesize", + "Filecoin", "fkey", "Floki", "fontawesome", @@ -574,6 +575,7 @@ "NOTOK", "sushiswap", "zetachain", + "zksync", "filecoin", "Filecoin" ], From 2da4d77d252391f1c948b852dbc9d16a907f0313 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 19 Feb 2024 21:21:27 +0300 Subject: [PATCH 474/607] Fix index definition --- .../lib/ethereum_jsonrpc/filecoin.ex | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex index d52dcb59f819..51c67cd32b3d 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex @@ -53,8 +53,7 @@ defmodule EthereumJSONRPC.Filecoin do debug_trace_transaction_responses_to_internal_transactions_params( transactions_responses, - transactions_id_to_params, - json_rpc_named_arguments + transactions_id_to_params ) end end @@ -67,10 +66,12 @@ defmodule EthereumJSONRPC.Filecoin do defp extract_transactions_params(block_number, tx_result) do tx_result - |> Enum.reduce({[], 0}, fn %{"transactionHash" => tx_hash} = calls_result, {tx_acc, counter} -> + |> Enum.reduce({[], 0}, fn %{"transactionHash" => tx_hash, "transactionPosition" => transaction_index} = + calls_result, + {tx_acc, counter} -> { [ - {%{block_number: block_number, hash_data: tx_hash, transaction_index: counter, id: counter}, + {%{block_number: block_number, hash_data: tx_hash, transaction_index: transaction_index, id: counter}, %{id: counter, result: calls_result}} | tx_acc ], @@ -100,8 +101,7 @@ defmodule EthereumJSONRPC.Filecoin do defp debug_trace_transaction_responses_to_internal_transactions_params( responses, - id_to_params, - _json_rpc_named_arguments + id_to_params ) when is_list(responses) and is_map(id_to_params) do responses @@ -112,16 +112,17 @@ defmodule EthereumJSONRPC.Filecoin do defp debug_trace_transaction_response_to_internal_transactions_params(%{id: id, result: calls}, id_to_params) when is_map(id_to_params) do - %{block_number: block_number, hash_data: transaction_hash, transaction_index: transaction_index} = + %{block_number: block_number, hash_data: transaction_hash, transaction_index: transaction_index, id: id} = Map.fetch!(id_to_params, id) internal_transaction_params = calls - |> prepare_calls() + |> parse_trace_block_calls() |> (&if(is_list(&1), do: &1, else: [&1])).() |> Enum.map(fn trace -> Map.merge(trace, %{ "blockNumber" => block_number, + "index" => id, "transactionIndex" => transaction_index, "transactionHash" => transaction_hash }) @@ -163,10 +164,6 @@ defmodule EthereumJSONRPC.Filecoin do {:error, annotated_error} end - def prepare_calls(calls) do - parse_trace_block_calls(calls) - end - defp parse_trace_block_calls(calls) defp parse_trace_block_calls(%{"type" => 0} = res), do: res @@ -200,7 +197,6 @@ defmodule EthereumJSONRPC.Filecoin do "init" => input, "createdContractCode" => Map.get(result, "output", "0x"), "traceAddress" => Map.get(call, "traceAddress", []), - "index" => Map.get(call, "transactionPosition", 0), # : check, that error is returned in the root of the call "error" => call["error"] } From 0a69f47795316bfdce84816f2e8e086d5c523658 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 19 Feb 2024 21:43:07 +0300 Subject: [PATCH 475/607] Fix parsing contract creation --- .../lib/ethereum_jsonrpc/filecoin.ex | 5504 ++++++++++++++++- cspell.json | 1 + 2 files changed, 5503 insertions(+), 2 deletions(-) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex index 51c67cd32b3d..db7d55ecf7fc 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex @@ -1,6 +1,5507 @@ defmodule EthereumJSONRPC.Filecoin do @moduledoc """ Ethereum JSONRPC methods that are only supported by Filecoin. + + Sample response from FEVM `trace_block` method: + + curl -s -X POST \ + -H "Content-Type: application/json" \ + --data '{"method":"trace_block","params":["0x37E611"],"id":1,"jsonrpc":"2.0"}' \ + http://...:1234/rpc/v1 | jq -r .result + [ + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000021cc23", + "to": "0xff000000000000000000000000000000001a34e5", + "gas": "0x1891a7d", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f2850d8182004081820d58c0960ee115a7a4b6f2fd36a83da26c608d49e4160a3737655d0f637b81be81b018539809d35519b0b75ca06304b3b4d40c810e50b954e82c5119a8b4a64c3e762a7ae8a2d465d1cd5bf096c87c56ab0da879568378e5a2368c902eea9898cf1e2a1974ddb479ec6257b69aca7734d3b3e1e70428c77f9e528ffcb3dc3f050f0193c2cc005927a765c39a4931d67fb29aaba6e99f2c7d2566b98fdbf30d6e15a2bbd63b8fa059cfad231ccba1d8964542b50419eaad4bc442d3a1dc1f41941944c11a0037e5f45820d41114bb6abbf966c2528f5705447a53ee37b7055cd4478503ea5eaf1fe165c60000000000000000000000000000" + }, + "result": { + "gasUsed": "0x14696c1", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf37d8b8bf67df3ddaa264e22322d2b092e390ed33f1ab14c8a136b2767979254", + "transactionPosition": 1 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000012e66c", + "to": "0xff0000000000000000000000000000000012e5f7", + "gas": "0x4482939", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285108182064081820d58c097ea8a30fc450e9f5370bdfd0a5fbadb528b137c52ba4b22cbdd91cd1b312707556314b8400967aeb858c2dc8d68d7eb8b76960a414bf41ad73831bd6500d3ff06d8f8b1af823e1d2f5f9803d5402c4038b87a4c77803589bc0a9b982ae90d4a02381370e0f4aa4f3145acaa5a99854ba6bffbf02778c2f7ed66b141da1aab9fac560a184662c5e47e2764e9c4221ff982c750a5aafb97968a0348331218b069e0f754e62341ed115f2f05a5c86def9ce1dff851918cfa69095611517d99f27e1a0037e5f358205ded7c8109656ee788ec8de051bc70331254b551d154eac21abf8fcf339d86b50000000000000000000000000000" + }, + "result": { + "gasUsed": "0x3c65351", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x83ca0e80894733453286b03e4caa9b1f3d4f4e14e52e583a46c99f3504a10e78", + "transactionPosition": 2 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000012e66c", + "to": "0xff0000000000000000000000000000000012e5f7", + "gas": "0x42d9aa2", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285108182074081820d58c08a894f371e2e6808da865600757651857a086c1b186f6b8b7e28ad730a0d4febc506609da7629fd39966a7d44ca8d40c8203bf0625b54f4dc6a5598fc5154a0498e940820d49b5c19fa1211766feac30d08f2f886be3e3e677d6da346b9eb92a0a89aaeb839f00ad85631801e18397c1390a3847d3b9fd04f091f55ba561ebe401d6d66baa19e41fb7aa030590c5808280431c0b0d6a64fb2dbc77f3e79ddd563b039dcc821b30bbf8d55863066f48c8fb3c1e8504754a77238eb65ce35e309c1a0037e5f358205ded7c8109656ee788ec8de051bc70331254b551d154eac21abf8fcf339d86b50000000000000000000000000000" + }, + "result": { + "gasUsed": "0x3ba34f2", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x4e0ddcc7723b6adb8b04553005bf7d2b92ff21515a2d475ed7cd904adf463ea2", + "transactionPosition": 3 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000012e66c", + "to": "0xff0000000000000000000000000000000012e5f7", + "gas": "0x37e0b4f", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285108182084081820d58c0a621f85fb8c62ff3dfc46ceb3c8b39eaf26d12058084cf132e8b929e76d78b09f8dcab75ae6b8fb0c08cbb19401282b8a22a134dd6501245016c6b291475ffd9f5c849472158d35a4a2fbb1c3ae3a9659c6977bbba744ca93d88ce6b8463c2240c76e0369b0b7ce704c125ee0a1659bbe480f9332d93ce5cdfc8a4165a375faca2f7dd6f32b1ad5e7139a67132abc88fb92087b5bbfc783c538b72f940ff1270670a8d4cf75c00f841f7428a4882fc81ba7e878f130d4d064c32b297db1d83b61a0037e5f358205ded7c8109656ee788ec8de051bc70331254b551d154eac21abf8fcf339d86b50000000000000000000000000000" + }, + "result": { + "gasUsed": "0x32c5c44", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x0ff1f5cf62ea3d5bff02b2179b65e04351887c8818c674566b97c40f00bb31cb", + "transactionPosition": 4 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000012e66c", + "to": "0xff0000000000000000000000000000000012e5f7", + "gas": "0x373949a", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285108182094081820d58c0894b04554ba37a92ed433153a56ef1252a8c9b03c959eb389de91e623f8ed365dde113420eedc91ecc4e3ddd6b7769ec9300462f93ca58128694b0e7479ef0e055cfb5852f19e3cf682ba5ef3508b42e25f9b3fc8b3eba3b49d8343d5e366a3500a8f48a186b76cc22b4caf1496d209daacd2bc310de177f820c9acf354bc12d26f90f36a18a63c90d7a8857e6c0c608b63488ac62d688931752d4664331b1445aaf9ee9b2d394e48282f89e1eeef729617a097817ee1aa62a2cf5219f9d02df1a0037e5f358205ded7c8109656ee788ec8de051bc70331254b551d154eac21abf8fcf339d86b50000000000000000000000000000" + }, + "result": { + "gasUsed": "0x344d3f5", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x037fde0b4fd04ee19c25d3ede9e65a37726995e3b11e8d475035b26db230782d", + "transactionPosition": 5 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000012e66c", + "to": "0xff0000000000000000000000000000000012e5f7", + "gas": "0x3780216", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f2851081820a4081820d58c087803bf7ff2604290afb95c3e1084540ead1f85b60816b716253e741d9637f1521c4dc70f6b3a53f593104e7d063873ba924e36be13b16f6bcc594199faf091c36c65bc971026e24af1ee7b20aeec80a3cc4edf7444bb1ae5943ff4cb01027fb101199f22ef757fbe6e7ef13560b7d0558fe5270cd529907d91c223f61a31096dab63246b7717cd517b8d68744c193f68b60d96b1f4eaf7761e87ceeea4fc378c1709b29e19d762f50ee1b8337e5f96135c35837a2d858b66116d0e1eb2800541a0037e5f358205ded7c8109656ee788ec8de051bc70331254b551d154eac21abf8fcf339d86b50000000000000000000000000000" + }, + "result": { + "gasUsed": "0x322a428", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x98021260660c66e6be8af4b99f97e941b6ddcb2c48e84d2d0ad338522159a706", + "transactionPosition": 6 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000012e66c", + "to": "0xff0000000000000000000000000000000012e5f7", + "gas": "0x374cedf", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f2851081820b4081820d58c0b6f12b90e50d2c0b86f2cc341a1e8a7787c2e78b933f4613a7af22cdfb66ce15a53bcd9f79092ecd91d0677310f86791b3142770e01ecf3a9eeab55ed5294435d108773019404632105da406f9558eca8bf25295c0a6d5885c95f24e65050e7203c7d40e07a30103ef93233b137908ef4ea3fc3d0741c0bf1c3f439ec4b23fbff6be77e190cf2ba6f7bd609b10055ffb931645123bf6d85ead1fa04e6800717ff253eacf5d3bb9ccc8afadada897aae831aabfc20cabed2ff25d0a24f37a3c501a0037e5f358205ded7c8109656ee788ec8de051bc70331254b551d154eac21abf8fcf339d86b50000000000000000000000000000" + }, + "result": { + "gasUsed": "0x32ae600", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xd3989d5f90ec878cc091194075bb9ed447ccae35a8dcc4035fad9c44987c55fd", + "transactionPosition": 7 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000023c695", + "to": "0xff000000000000000000000000000000001d922c", + "gas": "0x1a964d9", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285148182004081820e58c0a24cb66ff00fd1f20a84a596a215dc56f44b240eb7c7fa2a9cc1f103a86388ee860b5ba5e6cba7cd8869b194c1b45fc292ae448fc49a96f16b891ce29aa6560f36273080297214c94845cb210144e1a237a82e427eda1bbcf75bb8fa914134c118c6687cf61f857e92a1f19cb6547235ac0802384e5e6b5fbdcf81dfa3b8a60aa92ffd452ff362aee38dacd297c611f3b5de12f612b38a366f3353274f784d820956afd67eb365d4cd53af1140ed2bed9bcbcd31da2b58b83651994b3bc694451a0037e60358202f916e2c95962eb5027d2f5fc467e9983027e6da80714e1b7169cda64ff2bb610000000000000000000000000000" + }, + "result": { + "gasUsed": "0x17effb4", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xcee1106b264da4388f0b82d3eb834d75a5c82bd5baa76f58f56dd2d4df8f118b", + "transactionPosition": 8 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002af64b", + "to": "0xff000000000000000000000000000000002af684", + "gas": "0x490a329", + "value": "0x1734adf7a686149e", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a0002997259078097f66fc0113af242cae4bb45a9a217eb538185b0b3cdcf059af23b77984e794d0dcb1994ca805f5621818d80b192ac269665ef565bcf33e86568c0b2ce805875dd607cf8875725c94646f87adc591a1d1bcdad9e5defe0474898ecc9178e50a709f7a49c4d41de880fa7dccaefecd9c4c499fdf84eac1446e23b9c746d2f929afce10a9fe4085d7f9af6a883e9bbe85995e07b2277e58a4bf8d106490a989c0d7fba52162a15d5d1de3580238ca8bd84c341fd5e3dd90e58604b44e9e6efa26f8bdb01f81f72e8a90e4d3781e6ee575ced52406e111c29cf8d39d394079ac4678c907b2e9b2aee659ea7f1862cb73805ab42c3f7962a6c63e43d5615da1fa4620c5accb67f30463512afe1ea313367efabc66d132eca27dd4edc5a4f88cabe6517ef84269d1db59b0a9b5df61babf9f4e60f2a9ebd6bb2f1cf4c51fb3e899a64866b05775a76d9d02835c7c72dbe9c1aae0eba486b759688b26bcf3dbb8a56b99fa90219b4b72ddf96c198bf37309c0e87f7bbcc4207397efd93176508b1948b83b9d29e6fb9eb90f175c634374e662ca6618c27b4044caab2682b295e490bfaa3816b6856ba95b07c88a4d354f66526b6bcd48cb7b3dbe34651c7ed4fe0522a802c4abd8c9865bed859706476ef28e65e0ef995f6ed4355cd2ad049b28b479218a2d145c48484b15997e1e50e0e811bb25da5e299970d83cc12e927261f4672444f24a7181364dd2c0a46f38aa24bd38284398e866b5012d763ee70fa8c6851c28083cc47a3ce35712dfb578f99c2cb878f0eab25e380dc88ee6ecb705b31cb9492c01937ef66e226efc048dd5ae78cacf8391056d4d0555bfb37ee93865b5d79d73609b3b80c5c22fb22826834e81699a29fe7667d577fb4b1719fbbdfb988afe34d91b009a0c92f6a9f33f04e4237a5b5db7124d46b80d4af0592e338790008841c0d1562229a78bfa2e0a084e4144aed4edac7a8b7504848979452ad547909552382b655009bc4257d96f0f3fe39960fe3ea1a5c6ccd0d5bf09462f53b8bb298abfd5c5a039c7bf4d76f908574827ff275e129a29f057b3410d61d35788db2eadfc5b32ae50b3169df0bb625ca5a88dda75e0e4d5bf16b669df3ce3e3abd89bd5369ab8fae5fc4fef3b427f991ab800c9207df5028ceec134b6d65b62ee8feca0014c1a5bb13473bef7c64589df5300c1ce2b9bf52a4b296a21f7e64506a0d1dc5fcd208e71cb66df368346857546e886bf6961534d4208c312a415c46951fde39ae9fe2716fb76f93a654b260a584339bf04f08e92b09364de4079533fc5685a0379bd07a63c8d8c0d5150ccd54575138fcdcfc386ecca7e176f0369b7f830271d00f756227bf1858ff0c4b0a39db2586f81f1c2eb94e6dd800c41604a7daa5893f880fd0ec02196e5fed08f2f7837de91e9ab414479e220bb734a20081ff3709d623548efd15fa09dcb976aee94e61e54261b074f4e43a88ece20d167a0c8a255ca5da4197cef5bcaff55f80e7a4016a2bddcd9a772ea3a5b311041c4912087260c7028b17076a3a580da693eb98b25cc3ac3384cfeca799f185fadbe4a24a87d40e675ba2324870fe946d28c38bd385a8e75dafcb7c17209fc6ab477196a9d0b8c1cd853a41a6a99431114cb391a62449fc2b9a17149c321ed7937cb6a5f534fdc6cfc0ba7b96e0f7c9141e49905c40df94acbc1375f8d6d04101dd3b2aa031b68c27f50158879e705de80672abd7708f88d3446cb19261533a34e26a17ffa04395e3d130b13a96081f1d9c06a235168c6c81d471aaad92d5a9ee4b9f02822ac53d3ef5ce2020f47bed59d05999c3916325105c6a052870f678fe52019150bbafaf4f06db0bfc6f1f3461fc205e84a3232bd885ef2b7baf7e635e4730b29631c723318c7c37671d30c26e967804aa34c94eb927c868c0eabbe394262c18699d7275a735f2a5e5191c64dccd03986556e7f541475bcec57a8b43baab5f915168872d46d5a3292db6da12f75eb57ca579f8bc4dd9ab206cb5fe7cdf3031135515d8d01802fd78ad24ef1fc91814e010620e8a6eec9f7c0fd5efbee822072dac9f21965fde15fde86b25eabc02818a2e92d997c4ea0b2903bf0a7d3ec24754f6105511f72880810133487b02754bad91d57a342fc9844077b0b082158d728b2c3bc0ec07ca096f2996eeb83e3b45934a76d8e98b9afd8f11899aa15b374ce63108375fd93386ae4e4e0b0d6c22d4acfa0983faddeee59869e54919ee7a52a3fb87e528ed6b63172a647974534f957808b0720656dc5b963a2111edab5a731559a8935397c786869d96f60a1a5c0c4ad57084262a2b33139efa864eeff0d350327903b1db5ac981e6603160b47308b9570a8d0a0a33816b7b35dca64d40b9316ab9bb9d29b10fc09072235758da48817ac298870691e245b099cc7a7298f490e46609a8de673cc645d4dadda47a13291bf52c7eceeed22bc18c522a9063c81fae43eb13f04f1473421d784f05b7f9a595a053fa3be4fe7efba02cc23d4f4c1111bda71774695176c79c904f1d7863044004055200cef7c76e62d9902118cf063dd599b0d30824074e0306cc3d01f18d68aa46aa986e3146847e0e23289fbc9e30e13a54d39885bc65de72eb7a7756a44bc6effe2584f1a8ffba854295e4d98d3ae515a551538aded3ed59e034b9c24ab3e7792aecdb84c7e6443bc5f6932e0000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x773e01", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x4d88a10bcf487e66019cae5da1f3e29fd36de5ebeabe342d034b0dcc70bf32f4", + "transactionPosition": 9 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002af684", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x44a899c", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008338808821a002af6841a00029972811a045b2b535820c4602a2df716d569dd747e0fdb722f40fa6ee1a9fca69c250bd098de14a9b3ff58203ea65ad0e910f3d9e0556bfe40f97e34cf87a3307197b1f9fedc51dc291f828859078097f66fc0113af242cae4bb45a9a217eb538185b0b3cdcf059af23b77984e794d0dcb1994ca805f5621818d80b192ac269665ef565bcf33e86568c0b2ce805875dd607cf8875725c94646f87adc591a1d1bcdad9e5defe0474898ecc9178e50a709f7a49c4d41de880fa7dccaefecd9c4c499fdf84eac1446e23b9c746d2f929afce10a9fe4085d7f9af6a883e9bbe85995e07b2277e58a4bf8d106490a989c0d7fba52162a15d5d1de3580238ca8bd84c341fd5e3dd90e58604b44e9e6efa26f8bdb01f81f72e8a90e4d3781e6ee575ced52406e111c29cf8d39d394079ac4678c907b2e9b2aee659ea7f1862cb73805ab42c3f7962a6c63e43d5615da1fa4620c5accb67f30463512afe1ea313367efabc66d132eca27dd4edc5a4f88cabe6517ef84269d1db59b0a9b5df61babf9f4e60f2a9ebd6bb2f1cf4c51fb3e899a64866b05775a76d9d02835c7c72dbe9c1aae0eba486b759688b26bcf3dbb8a56b99fa90219b4b72ddf96c198bf37309c0e87f7bbcc4207397efd93176508b1948b83b9d29e6fb9eb90f175c634374e662ca6618c27b4044caab2682b295e490bfaa3816b6856ba95b07c88a4d354f66526b6bcd48cb7b3dbe34651c7ed4fe0522a802c4abd8c9865bed859706476ef28e65e0ef995f6ed4355cd2ad049b28b479218a2d145c48484b15997e1e50e0e811bb25da5e299970d83cc12e927261f4672444f24a7181364dd2c0a46f38aa24bd38284398e866b5012d763ee70fa8c6851c28083cc47a3ce35712dfb578f99c2cb878f0eab25e380dc88ee6ecb705b31cb9492c01937ef66e226efc048dd5ae78cacf8391056d4d0555bfb37ee93865b5d79d73609b3b80c5c22fb22826834e81699a29fe7667d577fb4b1719fbbdfb988afe34d91b009a0c92f6a9f33f04e4237a5b5db7124d46b80d4af0592e338790008841c0d1562229a78bfa2e0a084e4144aed4edac7a8b7504848979452ad547909552382b655009bc4257d96f0f3fe39960fe3ea1a5c6ccd0d5bf09462f53b8bb298abfd5c5a039c7bf4d76f908574827ff275e129a29f057b3410d61d35788db2eadfc5b32ae50b3169df0bb625ca5a88dda75e0e4d5bf16b669df3ce3e3abd89bd5369ab8fae5fc4fef3b427f991ab800c9207df5028ceec134b6d65b62ee8feca0014c1a5bb13473bef7c64589df5300c1ce2b9bf52a4b296a21f7e64506a0d1dc5fcd208e71cb66df368346857546e886bf6961534d4208c312a415c46951fde39ae9fe2716fb76f93a654b260a584339bf04f08e92b09364de4079533fc5685a0379bd07a63c8d8c0d5150ccd54575138fcdcfc386ecca7e176f0369b7f830271d00f756227bf1858ff0c4b0a39db2586f81f1c2eb94e6dd800c41604a7daa5893f880fd0ec02196e5fed08f2f7837de91e9ab414479e220bb734a20081ff3709d623548efd15fa09dcb976aee94e61e54261b074f4e43a88ece20d167a0c8a255ca5da4197cef5bcaff55f80e7a4016a2bddcd9a772ea3a5b311041c4912087260c7028b17076a3a580da693eb98b25cc3ac3384cfeca799f185fadbe4a24a87d40e675ba2324870fe946d28c38bd385a8e75dafcb7c17209fc6ab477196a9d0b8c1cd853a41a6a99431114cb391a62449fc2b9a17149c321ed7937cb6a5f534fdc6cfc0ba7b96e0f7c9141e49905c40df94acbc1375f8d6d04101dd3b2aa031b68c27f50158879e705de80672abd7708f88d3446cb19261533a34e26a17ffa04395e3d130b13a96081f1d9c06a235168c6c81d471aaad92d5a9ee4b9f02822ac53d3ef5ce2020f47bed59d05999c3916325105c6a052870f678fe52019150bbafaf4f06db0bfc6f1f3461fc205e84a3232bd885ef2b7baf7e635e4730b29631c723318c7c37671d30c26e967804aa34c94eb927c868c0eabbe394262c18699d7275a735f2a5e5191c64dccd03986556e7f541475bcec57a8b43baab5f915168872d46d5a3292db6da12f75eb57ca579f8bc4dd9ab206cb5fe7cdf3031135515d8d01802fd78ad24ef1fc91814e010620e8a6eec9f7c0fd5efbee822072dac9f21965fde15fde86b25eabc02818a2e92d997c4ea0b2903bf0a7d3ec24754f6105511f72880810133487b02754bad91d57a342fc9844077b0b082158d728b2c3bc0ec07ca096f2996eeb83e3b45934a76d8e98b9afd8f11899aa15b374ce63108375fd93386ae4e4e0b0d6c22d4acfa0983faddeee59869e54919ee7a52a3fb87e528ed6b63172a647974534f957808b0720656dc5b963a2111edab5a731559a8935397c786869d96f60a1a5c0c4ad57084262a2b33139efa864eeff0d350327903b1db5ac981e6603160b47308b9570a8d0a0a33816b7b35dca64d40b9316ab9bb9d29b10fc09072235758da48817ac298870691e245b099cc7a7298f490e46609a8de673cc645d4dadda47a13291bf52c7eceeed22bc18c522a9063c81fae43eb13f04f1473421d784f05b7f9a595a053fa3be4fe7efba02cc23d4f4c1111bda71774695176c79c904f1d7863044004055200cef7c76e62d9902118cf063dd599b0d30824074e0306cc3d01f18d68aa46aa986e3146847e0e23289fbc9e30e13a54d39885bc65de72eb7a7756a44bc6effe2584f1a8ffba854295e4d98d3ae515a551538aded3ed59e034b9c24ab3e7792aecdb84c7e6443bc5f6932ed82a5829000182e20381e8022036ea0ddccbc309b556e4fdc791438210c26b8b03c39d18834ba80a36bbdd2d6cd82a5828000181e203922020487c0c209649065502b78229fc0b7615d57ece831e64ce43bd3d8c9c42a8c72b00000000000000000000000000" + }, + "result": { + "gasUsed": "0x2ddb87d", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x4d88a10bcf487e66019cae5da1f3e29fd36de5ebeabe342d034b0dcc70bf32f4", + "transactionPosition": 9 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000011edcb", + "to": "0xff0000000000000000000000000000000011edd8", + "gas": "0x1a9c56b", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f2850d8182004081820d58c0a319a243963a781b9065dd3cc6cb01dd443611f71e95c03b2c43067837920f7c0091d21220384a8d9fbc2b7485aece108ff8910a39017f80753b3dc546957d44fd949fa40facd72ff1f62b3d20dfadfb1d6b5bb0716284bfd4e0b80078039d500e43ccd19408e9f8f2e355df930feabe48aa2d27426bc1f5f9a3dfc08b1f7ab1d699abf7d86811e82e261575be40bdac8bbec5b061f3bdcbca100996367c6e4e33a7aed7ab5f4d9543a5a69f89ad068a150a29e0aacbed0b50803956fdf161531a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" + }, + "result": { + "gasUsed": "0x16185c7", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x0ce7f1628f49ca1f4714588d59b6c3fc7791441a7833af66e373be1c4064b926", + "transactionPosition": 10 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001e4c50", + "to": "0xff000000000000000000000000000000001e4c4b", + "gas": "0x1a4fb4d", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f2850b8182004081820d58c086ecedea35396878a90a05fd41b2ed46c73869919a9c07fe1ad04d67f97b9bb0040c70eb525811ef34bc95c4664979ba887ac5fe5539cefa9ad5d572cbe31833dd735eae1e3afd27982c2058e68ef536fa96211b0a236edc47904dd9e6028f95101167d96f9b7ca02324ed4f51120454c6be09703b0ce0d6d66e6150f1c11544b2a3ba052eef2233fd8c99d0818754efacfd3fde07189a9b927073627d23177dbb1908fbc60ec768af3b957833df277f702e08605456646890744f16fdc256231a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" + }, + "result": { + "gasUsed": "0x15db0e5", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x12dbb560d3620454a40e275965817a7fe23c82e2e6870a32261d1e1469e7694e", + "transactionPosition": 11 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001b6165", + "to": "0xff000000000000000000000000000000001134fe", + "gas": "0x304eec29", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f9851381820047e03a7420ed060181820d58c0ac75f6bdc5cef0564f0d17c739ef653c268ab69c15398de184d3a078f758a7948b81092f68636a6932ce7a13bb8a7ce38910b7287ac053bc9b33c33dfae64e5488103cd61842f1436f844cd71f67d69ef5fefefdcf32d9c759a0b63e878381f009e3b8a1c77ff44855979bec458688aba0538f793866d0736224f55e689597207c315007cccd3fdecba298b46b59a364ace16c261151728065e32a59505eafda71866382dea9138bda682ac474030a49effab33f17a5119a8181b3a416737c931a0037e5e758205d5641b3010b198d37a1939dc3e78c500a46a23f433f5c1e5cb8b8847ce653d900000000000000" + }, + "result": { + "gasUsed": "0x25482600", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x333b9ec6232ca0b62db161908423e51e9c15847dc1af46bea47ecfbc5c9a1de4", + "transactionPosition": 12 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001134fe", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0xb1b85b3", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f8246010800000000460150000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x1699cec", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x333b9ec6232ca0b62db161908423e51e9c15847dc1af46bea47ecfbc5c9a1de4", + "transactionPosition": 12 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001258fe", + "to": "0xff00000000000000000000000000000000110330", + "gas": "0x1b7e49d", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285028182004081820d58c0abdbdc0a9b0148ce1f4cc7774bbf9f8fb55359a26601885ac33ed35e76777a4bcfc34970fcd77ddbcae1eb1f7482d886aa811dbb853217324c2e3883c72dc559aa75e59c266ca217c44d1bfe2d7454a35c6b58a66a46e1e8ecde020d596e3c5b18b5c4111bcc5a02a840b5eaef79804cb8495df887488c77ca3d39aac9fb2e8d6029075253f8574896e98a6467aecb12b36223ecd87ba86489b351e004dd803e701a5545df4f04b499894f564240b5b812b84d4a76171e22153dff71d0c076b91a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" + }, + "result": { + "gasUsed": "0x16c053d", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x78c49827be7cb10a97def7625f879f784e74d319f0163e41a0bfb9e69bd50a23", + "transactionPosition": 13 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b3cd0", + "to": "0xff000000000000000000000000000000002b3dda", + "gas": "0x2c685ac", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001b685128282004082014081820d590180b06714322c16170ecb7c9c0368bca79c52e042c91842f054d7ff485b6f0eebe6eb842984fc8fb422f6aeee8014dd9318889569e50128dd7836a3a772b3cb42b74b5683bf14f0f6f2408365594f66c0dc2b9892240e09288bbc5c581edda19f4d01e95a51180cd10582f7c98a65b0eed7285e99f8a0ac9f9da0d65ae7a9fbd0c15ca17feb4707418f8f290079e9e0e7fb9149a2a00ff464b45b456962ab56bb3c22f0385cda8d34487df04e7a8f97e5bd1afa7fa79e6057ae32dd840c2b65308da9c82b53bbaef4e6ec4ca0a0ed87f48d69232ec9ed7dfdc27ec802ea31bed16f936bc6c3e6a1bd7a9bf866065eb33ce2b982e2138648025932a9f5d44a4e164ebea35a41267a0ba86be5ed21764d0f120275658d1480be047ded3fd6f1e5f7f611b774515ff8669dfd229401058548a21ebdfdc5f9e2ac6a045d6b4c9e095f37d2ae66d7e7b44258a47aca8cada74470a2efbd48c33cb04479ffe5cf1c0a2251ad6930a75b7381ee9fbce76e1bb149dc72a72f9e7ac0c7b66f9c4080e32703661a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000" + }, + "result": { + "gasUsed": "0x248721c", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc7602fdaef4828a493b88f871dcf5c5750270f84c69f609c4ed047b30e49ab21", + "transactionPosition": 14 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b42c9", + "to": "0xff000000000000000000000000000000002b2288", + "gas": "0x1919d9a", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285018182004081820d58c09315579871183fc3376dbacee3f035e3740908c1c2dff5930be1c84897435c1fdb3a3381fc674ad538ae651328dfbae2adaf6cc61cb8677c27c0fa6bd966544735584e3b2417df9697ec1af32e07a53c68be0dba944b20a0df6d66a1ee74af6606460b398e6a5e76c7aefb8426b60a89014c62db52ae4b946bffd71ebc66d9874829e31f1777c487a86f64cc785e1d9f91d5d367a76a699b368ad9cb21da81bbe3ca211498d38a4d3669b33b1f8dc7404fdb2b87db35f82482819509e38dd70d1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" + }, + "result": { + "gasUsed": "0x14e3bdf", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x32042ed62912073406b1f825b0d4baf5256b6773cfcab6c269352c74f6b7c39d", + "transactionPosition": 15 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000129602", + "to": "0xff0000000000000000000000000000000012968a", + "gas": "0x219f358", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f38518258182024081820d58c0a4c24df9a93858d253c0b949ab0cfd5043f8243151ae45eaaa8836e1d5c7668fe6d88c1b8ea0d172543c781f5cb35fe8b4b97f5cd4495bcc3d60c4d475707afd9ecc87dd4abe324c80c86c8cbe557455b5d67f473018cbd37fe02a95e4603d740c2f2e281124b9ce62ec8279a9b8c0cf4722657cdfb13ded895f089eac23da5835281fe1dec4fe041ee5d2bb5cf0dfe3a1388b3f4bb1c898cdc9e16b9bfb2880798d1f892074ef35c4524f3212f181020c012f6396f9a5f90968871334397d331a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000000000" + }, + "result": { + "gasUsed": "0x1bb5b3b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x89d1a98260f8ce9a489e96618fc2eb9a71aae288a0dce7e576cc9f92777bf78d", + "transactionPosition": 16 + }, + { + "type": "call", + "subtraces": 4, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b2b89", + "to": "0xff000000000000000000000000000000002b620f", + "gas": "0xa24a14c", + "value": "0x1411b1db93e7400", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000089a8194870819ba01d82a5829000182e20381e80220093c553e711af9713a922b0a6a904eb17092ad8c5f5f4dc072c47235c4befd721a0037dfff811a045b26c31a004fa108d82a5828000181e2039220203f3de710bca81902e9c52aa8a856501d7597cb246059090e1a122a6bf3cc0d24870819ba91d82a5829000182e20381e80220d680339874b015ea1e6f62258dd33039d11d6e52ca2ed52d75710390146ef9031a0037e0a5811a045b33e31a004fa109d82a5828000181e20392202065c3e8e0b9d5e7a3c98c22481ccd92a02df2e940dec694d41b65b8a159bff530870819b948d82a5829000182e20381e80220c8ba717e0876f47d552a01c73caaa0a474fcb68d5191646b77faf23543bd80361a0037df37811a045b123b1a004fa10bd82a5828000181e203922020ecd65fb1dadfb1c73d465961ab5670d04ab5f16778ba538e377863628da9210f870819ba8ed82a5829000182e20381e80220d4308d1779e1d8a0cecc71a1bad51c00d5e8548f97897e393f492a98e7605c4f1a0037e0a1811a045b33e11a004fa100d82a5828000181e2039220208da956908f35641d819d6ca593f70da0f0bc50178320c0f2b5d98f845e225908870819ba3cd82a5829000182e20381e8022038edc7a8c7b4a3709dfacd2740137e8e129142ffb8e927ea5a9edbb6c64567251a0037e048811a045b2bef1a004fa102d82a5828000181e20392202067e6ff31806a59a94409982748b7b95b0b3e111831a4cd1082e45c28316a6412870819b925d82a5829000182e20381e80220b813d4997aa7758f20ff8a61a3c85a424c095600f19affa27f131388351df4501a0037df06811a045b0e301a004fa104d82a5828000181e203922020fc03bc87e425b22b4ccced611f45235b986b85f108f3f2549bcd4eee9290cd2f870819ba50d82a5829000182e20381e802202482832ae44ad6cd1a2e6a2b7619b26b7b7149eabc950010bc1866875d029e1d1a0037e05a811a045b2df11a004fa106d82a5828000181e2039220202661ae0bcacdec750b339f0ab0ce9328f2692058ae1045968e0edd5f7b3dd811870819ba39d82a5829000182e20381e80220dd0de1bdfc9c1619c66b73b4bf977f3b3447c68c27aa10ded4555a12ab3a2f511a0037e047811a045b2beb1a004fa107d82a5828000181e203922020d1d07a5a06841c47767b57ef90892dbaa6023e96124a3f76148c58368cca2617870819b9ded82a5829000182e20381e80220027e1870716e523c0f3d08715b2a1490e446125fbd7d2e8022fd303e3c56f93c1a0037dfe1811a045b24031a004fa108d82a5828000181e203922020f29aacc7c8fe59516a3b527547eaf8d250d2f48f981cbf311166fb0d9c031808870819ba6dd82a5829000182e20381e802206875104c4ef4690f4589759cca1f539fa69e9673deb29a68453abc7140da57511a0037e071811a045b30281a004fa110d82a5828000181e20392202009cbd082f5c1b3806813ae5240871a7e99adddec8696ccb63409829969e90513870819ba15d82a5829000182e20381e80220880db767d7ad153fa104053668a3dcefa92d626d76e1abc9f685674f1ffbc83f1a0037e017811a045b27f01a004fa0fed82a5828000181e203922020d102fcda4fd8039589c643cb8a40c68f6fac58b780572d5c786ec3d911c65221870819ba38d82a5829000182e20381e80220a94505e3725f268c7cb9ad1ee0538de448b2999894ddced47e43fa43dad37d4f1a0037e042811a045b2bea1a004fa105d82a5828000181e2039220200cb6f8071eab0af5717dfe15386545450b7e931cd9c08dcd47f5ef6006f40d34870819ba69d82a5829000182e20381e802202012760d042b85e82cb9f87fed5736c0936e9d4eaf003de6074cf35de125c5591a0037e072811a045b302b1a004fa109d82a5828000181e20392202000068b68d6d878c79e8d6c3747c52c39ff17f60bf6e9c8baa0db5e487a6e4105870819ba55d82a5829000182e20381e802208966b25c9e3954eca9ea62c2ef736fe1a78e72d1d9b2c1030f4a9f0af1ec5f321a0037e063811a045b2dee1a004fa10ed82a5828000181e2039220206c73256ff2c356844554e46940dc1df9fd03156c148b40a39732db2b1bb9e228870819b9ecd82a5829000182e20381e8022013f73f929cd0028ba741b3796b8d8ea1fc7bd22140e228767573ef84619666231a0037dfe9811a045b24ac1a004fa110d82a5828000181e20392202017c9587ea90cc9d2394afe6ce5e78ba20b0282aff1fecef387465f6903d73d33870819ba2ed82a5829000182e20381e802206f9dc8dc410c97bedf1b7cc1d72d15abf1e7918f84cd8dab15ef1cf9889070091a0037e038811a045b2b0c1a004fa0ffd82a5828000181e2039220203565803f11d30abdde5e2c8f99bd2160791c62175345f68c20aedc61110fb53f870819ba0dd82a5829000182e20381e802207c7241beb346264e70baef27c4191100515937ee353cf04485e718f03d537a641a0037e010811a045b274c1a004fa102d82a5828000181e203922020781476353489a83554fb6ec661e03fd81e2f92a8de6b7871ef76607d9d636127870819b617d82a5829000182e20381e80220f1841d865f1de264b0c50c7f8e653e61ed65734ac0ddaace8089d1049d9f855b1a0037db02811a045aaec81a004fa102d82a5828000181e203922020964507149377a01e67dc8ece2da6c9b49418687d75676f351c70567ff9628821870819b9d3d82a5829000182e20381e80220134d720e957f2dafa018b497bdef3d69bc67c3d429c43562b3a6211ce76102591a0037dfca811a045b22b71a004fa103d82a5828000181e2039220204ab78880ebe55d1606957e0b2e9ffcbd599b9545d5245c48f4db7b1b0e7b0b07870819ba97d82a5829000182e20381e80220a32b01b61da64c40030ef19ecc0b07aa93be21853fcfe6573a3a753a58da2a151a0037e0a7811a045b346f1a004fa10fd82a5828000181e20392202049e915acdfd6178f081d37cd17d86c8143415a65a2d6d4d72c52998888679332000000000000" + }, + "result": { + "gasUsed": "0x61131c0", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x578dcd2316d11d619e685216afa861d1eb631f38b4fdbf4a189088485f2db90d", + "transactionPosition": 17 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b620f", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0xa0a644f", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x578dcd2316d11d619e685216afa861d1eb631f38b4fdbf4a189088485f2db90d", + "transactionPosition": 17 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b620f", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x9f99f07", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x578dcd2316d11d619e685216afa861d1eb631f38b4fdbf4a189088485f2db90d", + "transactionPosition": 17 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b620f", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x9e66af0", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000106819483081a004fa108811a045b26c383081a004fa109811a045b33e383081a004fa10b811a045b123b83081a004fa100811a045b33e183081a004fa102811a045b2bef83081a004fa104811a045b0e3083081a004fa106811a045b2df183081a004fa107811a045b2beb83081a004fa108811a045b240383081a004fa110811a045b302883081a004fa0fe811a045b27f083081a004fa105811a045b2bea83081a004fa109811a045b302b83081a004fa10e811a045b2dee83081a004fa110811a045b24ac83081a004fa0ff811a045b2b0c83081a004fa102811a045b274c83081a004fa102811a045aaec883081a004fa103811a045b22b783081a004fa10f811a045b346f0000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2183932", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000003728194d82a5828000181e2039220203f3de710bca81902e9c52aa8a856501d7597cb246059090e1a122a6bf3cc0d24d82a5828000181e20392202065c3e8e0b9d5e7a3c98c22481ccd92a02df2e940dec694d41b65b8a159bff530d82a5828000181e203922020ecd65fb1dadfb1c73d465961ab5670d04ab5f16778ba538e377863628da9210fd82a5828000181e2039220208da956908f35641d819d6ca593f70da0f0bc50178320c0f2b5d98f845e225908d82a5828000181e20392202067e6ff31806a59a94409982748b7b95b0b3e111831a4cd1082e45c28316a6412d82a5828000181e203922020fc03bc87e425b22b4ccced611f45235b986b85f108f3f2549bcd4eee9290cd2fd82a5828000181e2039220202661ae0bcacdec750b339f0ab0ce9328f2692058ae1045968e0edd5f7b3dd811d82a5828000181e203922020d1d07a5a06841c47767b57ef90892dbaa6023e96124a3f76148c58368cca2617d82a5828000181e203922020f29aacc7c8fe59516a3b527547eaf8d250d2f48f981cbf311166fb0d9c031808d82a5828000181e20392202009cbd082f5c1b3806813ae5240871a7e99adddec8696ccb63409829969e90513d82a5828000181e203922020d102fcda4fd8039589c643cb8a40c68f6fac58b780572d5c786ec3d911c65221d82a5828000181e2039220200cb6f8071eab0af5717dfe15386545450b7e931cd9c08dcd47f5ef6006f40d34d82a5828000181e20392202000068b68d6d878c79e8d6c3747c52c39ff17f60bf6e9c8baa0db5e487a6e4105d82a5828000181e2039220206c73256ff2c356844554e46940dc1df9fd03156c148b40a39732db2b1bb9e228d82a5828000181e20392202017c9587ea90cc9d2394afe6ce5e78ba20b0282aff1fecef387465f6903d73d33d82a5828000181e2039220203565803f11d30abdde5e2c8f99bd2160791c62175345f68c20aedc61110fb53fd82a5828000181e203922020781476353489a83554fb6ec661e03fd81e2f92a8de6b7871ef76607d9d636127d82a5828000181e203922020964507149377a01e67dc8ece2da6c9b49418687d75676f351c70567ff9628821d82a5828000181e2039220204ab78880ebe55d1606957e0b2e9ffcbd599b9545d5245c48f4db7b1b0e7b0b07d82a5828000181e20392202049e915acdfd6178f081d37cd17d86c8143415a65a2d6d4d72c529988886793320000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x578dcd2316d11d619e685216afa861d1eb631f38b4fdbf4a189088485f2db90d", + "transactionPosition": 17 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 3 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b620f", + "to": "0xff00000000000000000000000000000000000063", + "gas": "0x2176414", + "value": "0x123ea1b057e9800", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x1770", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x578dcd2316d11d619e685216afa861d1eb631f38b4fdbf4a189088485f2db90d", + "transactionPosition": 17 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002d33a0", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x548da5a", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009185583103b39880989620c017d43ab24caf829d6d2b1cb27401aa18ba501d87d32526973a498c8879fb1833f2c4198c3634753bfd583103b39880989620c017d43ab24caf829d6d2b1cb27401aa18ba501d87d32526973a498c8879fb1833f2c4198c3634753bfd0d5826002408011220b6da1051bedb96e0c5636bad2656b365291dbd94f1482642b8a5e51edffaafad80000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x170f6f2", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001d824500e689b501550278e8a50631934966637b6ce6ad7ca7e3c68c5995000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x86ccda9dc76bd37c7201a6da1e10260bf984590efc6b221635c8dd33cc520067", + "transactionPosition": 18 + }, + { + "type": "create", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "from": "0xff00000000000000000000000000000000000004", + "gas": "0x53cf101", + "value": "0x0", + "init": "0xfe" + }, + "result": { + "address": "0xff000000000000000000000000000000002d44e6", + "gasUsed": "0x1be32fc", + "code": "0xfe" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x86ccda9dc76bd37c7201a6da1e10260bf984590efc6b221635c8dd33cc520067", + "transactionPosition": 18 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000000fce75", + "to": "0xff0000000000000000000000000000000001b5d7", + "gas": "0x2985b7c", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000279850a8382004082014082024081820d5902408fc9978c8c1861e78438a099a06de4d9b807567e3eb578fde1ddbd7c88c23ced96d89887d40e41888f6a359ba0d98f3e89d8003c62be48cb4f7a6d8132329e1e18480a9e89a39da05dbe12d48d027bdeac20d8d46c2e9889ab76a7800cffc27212d72abb73090cc21af180b27add3ee619abcc012a98145bbd466dfeaad245901a7f1106a77ee7bf9818680a028229cbb9c6c1c32b329a01fd9ca68308c92399fac66c67707d3efd760e9b5a0d6a6fad89a425c6ec2bc654e1693c3679059c9cb9543366f77db1c65728b88a98d317b17dc66e3bfe2d30c85cd1e883be09ef4fc6ab3b50a684042e30627cd01b67d964932e9ec0d8608ab66b7da8dc9fd99480e517bc58aa752fd9bddcfaa4aba5f59101d108684a40a8ff3d954d5a58c230fa03ab135a17fe691663d1eceeff68d541a69eb913f0fe3b266dcf66543ee36e374c6596525ade50dd4845347e445773a29476a841d05c79df4ddb1904aaae2473cc9bcba41de75f0072bfe0b7df94aa3231044d2d1f8ca32bb7d4707920918ad9b31854e6791623c69c5cf6bbd57a7589ad39c8cb72459b24099d210fa1b4e11351185315739e2853a81ddb02286907bd9804026f2b59ea68462a4e56263960a2a8f4c20f625f87e13d47cedb499fdf4cc034f010fc170b505540200603103d360231fe050e5d769ce47f97ecf0ee862a6225f91fb57b2c5fdddf11e96f69f68742371714ef808477c8ca21f339ca1273b0aea3c066ad68f49784cc6ec88a7dd3af0408e61e6dcffbd19b9811107d960c137c7225b0c783ccb69194d1915085781a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000" + }, + "result": { + "gasUsed": "0x226bdfd", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xdfe4b81a94cce7b6ca32cc497e32a8447786ceac845e366a9b2fcbcf16971a6d", + "transactionPosition": 19 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002c2afd", + "to": "0xff000000000000000000000000000000002c2c61", + "gas": "0x2ca9cf3", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000072818187081a000114cbd82a5829000182e20381e802202a0b230182f9610882b85622ee618ffa5933e6e6fc3e70253837708f7fae630a1a0037df72811a045b19341a004f65a4d82a5828000181e203922020fa592a0514dce829ad6e2bbe0fc789d094183e20304be069b5b9393182ffa6030000000000000000000000000000" + }, + "result": { + "gasUsed": "0x1df238c", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x76fae58091c030e252484105473c8950ef633dd02b4ac8892b341bf46c77d528", + "transactionPosition": 20 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002c2c61", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x2bf36a3", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x76fae58091c030e252484105473c8950ef633dd02b4ac8892b341bf46c77d528", + "transactionPosition": 20 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002c2c61", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x2ae715b", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x76fae58091c030e252484105473c8950ef633dd02b4ac8892b341bf46c77d528", + "transactionPosition": 20 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002c2c61", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x29c5bea", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f65a4811a045b19340000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x477778", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020fa592a0514dce829ad6e2bbe0fc789d094183e20304be069b5b9393182ffa603000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x76fae58091c030e252484105473c8950ef633dd02b4ac8892b341bf46c77d528", + "transactionPosition": 20 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002c2afd", + "to": "0xff000000000000000000000000000000002c2c61", + "gas": "0x2e2d67e", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000072818187081a0001160fd82a5829000182e20381e80220ed53117c4c68300789b739922ced240b43091dcddfed253062cf17677f9775141a0037e0e5811a045b37331a004f66fcd82a5828000181e203922020c775d47a111888a1ad17dd5989a957c59e7a2e89cb7df8639a15556593cb382f0000000000000000000000000000" + }, + "result": { + "gasUsed": "0x1f29209", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xbc62a61e0be0e8f6ae09e21ad10f6d79c9a8b8ebc46f8ce076dc0dbe1d6ed4a9", + "transactionPosition": 21 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002c2c61", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x2d7702e", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xbc62a61e0be0e8f6ae09e21ad10f6d79c9a8b8ebc46f8ce076dc0dbe1d6ed4a9", + "transactionPosition": 21 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002c2c61", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x2c6aae6", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xbc62a61e0be0e8f6ae09e21ad10f6d79c9a8b8ebc46f8ce076dc0dbe1d6ed4a9", + "transactionPosition": 21 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002c2c61", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x2b49575", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f66fc811a045b37330000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x4769b4", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020c775d47a111888a1ad17dd5989a957c59e7a2e89cb7df8639a15556593cb382f000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xbc62a61e0be0e8f6ae09e21ad10f6d79c9a8b8ebc46f8ce076dc0dbe1d6ed4a9", + "transactionPosition": 21 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001ee034", + "to": "0xff000000000000000000000000000000001ee031", + "gas": "0x1b3a043", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f385181e8182004081820d58c0a7a02e8dc005044807e4888bd0823f61ae169669e7a27a3b7e3912b8fba0eea593c30732f4480a1fa0e285867e448306b2a2ddea74ce0511ace188bd92cee9e2352a2f1b70a1903b268fd124d1e8e6f92eefbd3bc7a0fc42a477ace39972b9290963cba107a2a981b7c74c1a3dcb6be6a2b7a01e8224501b0a4371f1a3970c0664b313bfcf7be778165db0e2e4bf634da61561da28de009a0cba108ff65d334ac272e4b3ab6e8e26fc23cc09439290bef48866abadb58407af3dced632acf3a71a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000000000" + }, + "result": { + "gasUsed": "0x1696c22", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xcfeff915c7a3e06a7aafbb9f1a20ef023615aecea6cb6b004dc87ead55f63702", + "transactionPosition": 22 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002af885", + "to": "0xff000000000000000000000000000000002afa9a", + "gas": "0x466a958", + "value": "0x1a7b47481750efae", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a0001928c5907808a218c1f6baaa41a2cdb879b1b7bf7cc7a382348970e0520fea435085566ff96b13118cf3c9e35bcac55bee865ff570fa7ec2b266e385a473c7e0e77b1080bb1392cd15fa24f39afd648cbf45b2b99d23aa58e9da9e75476fd4617cee6b3bd4708bf6b5afc9237e1977040f8e0bc63c5d825fbb6c3e471f073a6a96f27f9c30b98658dc8e49f6b7aa65d02aef573b613b579c462df8e45632b979c1baff7445e5645163020e17829f0aa7effd318343387715eb7a7eeed87e366620522a4dbac8e916703773123ad4a1347f8e59285eaeb6db1d236a503cbcb2c19a9ac9c6d2cb110adc7685a38df6873db056779f3a7b06cbac8650260481fdc488b9e2b75a7a3676f237768f3d45dc824ced53e6e34b8b004a457f28eef44cadb4334afea05145b1ea8103363bf1b88db266f14971d72b024f9b7f7b8ed9fa4100c388eabd162af274f019d2ef4bd90d53a903ebd5ca39e0478f6173bbd8a576648f2291f9eede03ce9c41aae4d56489652d0a74e7275f599a8aababb97676b5538ad1960cc8260a4e8a6b218ecbd54c8b5b91457ae56d3cf199e740499ddcd1eca823550817a43c1e53d4e407a7e7443f4f038e337ab1e959c101179efd603f2f9b7e40e6255be393e233ccdf601f2e423e898c286776a0712c4db8ed22e8619923849828d10f7cdddbce7e3bb2d51ecd9c3401ac3762fc44f4848f25fc2e8f22059e43ce8b9d39e628ddee9330ba22cb55da67aab985e6022e877568011d3e3eee84df1cad6cc924a426ae8c338140777f6e592c7872a85357931e586ad3f3684c884994495d20c30c5ec5f6de0244355de4b1fde1117b1068f64372ecdcda183e8215032a2d18157d59eb1aa0e09bb1e85fbcc0ab978e1860f6f457cb821a266bdfd197c53516264feb015c8562144cb4fa2c8a6918c6d5e08d3c050136f2905d373c219149dd6c37662da580148312067cbc440a9adb3ecd5df0cb01eb6ad1d4fcd7399b3fe43bcb7397abb7fe9323a71a2a290ab2b0e955979de28827c98e38fbc7c65d63a22e4f6dcd5077e182801e7821d59408cfb01bf70be7a5d063a48b51fa3c6899da9e902cb9305a68f74f4e32ecc6186130cec8daf78f4d40ec869286130ed41228373e496471376ab5646ee44b6f8ab4c4c9210557b7ec166fb4582fc8bdf2fe12a80e37cba8cabf8c7456b75a305aa0aabb6dc1e040ca088a28672bcad1a17c4b661611cae0b5d968c04df8388a06e69fb302cde54f5a1619c0275b1d7349a41561ce6476a8ba3bf3ab1b4f291dfa72cff5696f4203c733f78f8168ffbb2c97392db60ab160a6f42f1c3b8957e24104283deb670393aa20c5cac99bb54489620f16e0838fdb5b7f92515929745b58c2769d26374cca5d94cfb14e133fd3df40497a1fa9ac70a9e9a295ff659b04aaf81a5436dabde72e10af28a00a8f05cd6b3e6bc75847ca7515fd27d4b2cee97e21594d4a5b659a0504e77ccfcbeb082043f73dbc1484e5504b09c048c8a3a6c2db1278ac2205c78a48263e9ada4646dec7d27f28392667d3fb49dc101a5d1b6aade897bb92c692b956b9a83d7d2abfbdac1707a9d369cfc5260ce380f67c3ae6337e34c272ea646632f2c73465b1a13b358cdad9d4583a59cc3be1c8f2094282560aa83435074686f6abec5b4c895452ae74f92d9bbf6e9ec9728d07c397aef8611d09e75c00dfc297af2d724877127d5abca2f6a530ebc768617ad077d4712b26fba3cab83c6c74a7119a6623cb9a712eddadbd7811fd3948337dbdaa311f9891f96a324c20b56ef6d73450c5d3804dac0e51fc07fc82eddf441c9412e13efb701115362bf844ceb88793a131e136a0ca9935b7025a6682afbb8685c9b058c833dcd81e93e6dac958a31f5b942c4578ddb058ae644dddcb43fd9f785510c7a2bde7532b3f84b48e565cfb0ec1f55284ef355df662f091a401a52122823b46697eb141523b39dd9eeb73e10df2793a4e2107ee299405800df9e95340723b18e0d6c46fd7c4236ceaf34594a4c652b22145361f277fc5b3427d47e5b6c51db78e5b00050bc616c2e6efb54eced079a1a7186c080ccc3bab5989e0df45115d314a95664fff63ec61f73ec34c22033105410d1158e44456ba982984508a8c650d912c1d3fab14f7a12935ea1142eccb0748ddd33f1bcc07cd5614e24257ad9a656cfc4a8589042de2b29aa641b0e59b8fb2d83a0600a72c43d44b0da9f7df90985b27787686d14dfdfba29c895dce4ea9b827f5dea7d12d0536e36a90f98cd8e9078ab343ddc20a35dedf9e46010513eca0141bde7b2ab975abe920e201e7c55375eb7c1fe56b6677eed219a59d85c72e04ca348fae0171c2b4b33dd80e743954b80945cbc97c225e28be27d67c7425a9d0331f74919002b5cc2a120f51725bddf5bd8661f5a6c5909561f579536577473a6c8fadc7ca5b0a415d49e78441c3cc0d3b4e26d089bb515fb1a47d7a54814349b6e1cfebd137c2cfe94d3bd2ddcf7ae976a61f32638e55e0e290b6fdf379fda9428124380187eea5fc2a7ef55c7135b432d95b4916e594c3331dd28419a263202c48deb804ceb2119646cd56f1a907a9b4e723d582d1762c0c667b4ad1fe7887531540f19018896f660d57efbeece42880a36772b04b2b438adfa4b3f8c5c2474cad4d99d2e55a165ae536fd7713f3564464fcc1e193c170a0965bc4f0d1bfa0000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x70ac08", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xa714c6be95aa4ef44699b9098e264c0adead1caeffac75013c413a7a9798dcad", + "transactionPosition": 23 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002afa9a", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x4271824", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008338808821a002afa9a1a0001928c811a045b25725820ebf616e5e6a85ffef1fe235d974420b8a1c0a70d4fa60d21d2215f7e2aac05ff58207bb5ec49d8afc9b6444096fb05e348b164acb4a9305a4fa411def29c62edc61f5907808a218c1f6baaa41a2cdb879b1b7bf7cc7a382348970e0520fea435085566ff96b13118cf3c9e35bcac55bee865ff570fa7ec2b266e385a473c7e0e77b1080bb1392cd15fa24f39afd648cbf45b2b99d23aa58e9da9e75476fd4617cee6b3bd4708bf6b5afc9237e1977040f8e0bc63c5d825fbb6c3e471f073a6a96f27f9c30b98658dc8e49f6b7aa65d02aef573b613b579c462df8e45632b979c1baff7445e5645163020e17829f0aa7effd318343387715eb7a7eeed87e366620522a4dbac8e916703773123ad4a1347f8e59285eaeb6db1d236a503cbcb2c19a9ac9c6d2cb110adc7685a38df6873db056779f3a7b06cbac8650260481fdc488b9e2b75a7a3676f237768f3d45dc824ced53e6e34b8b004a457f28eef44cadb4334afea05145b1ea8103363bf1b88db266f14971d72b024f9b7f7b8ed9fa4100c388eabd162af274f019d2ef4bd90d53a903ebd5ca39e0478f6173bbd8a576648f2291f9eede03ce9c41aae4d56489652d0a74e7275f599a8aababb97676b5538ad1960cc8260a4e8a6b218ecbd54c8b5b91457ae56d3cf199e740499ddcd1eca823550817a43c1e53d4e407a7e7443f4f038e337ab1e959c101179efd603f2f9b7e40e6255be393e233ccdf601f2e423e898c286776a0712c4db8ed22e8619923849828d10f7cdddbce7e3bb2d51ecd9c3401ac3762fc44f4848f25fc2e8f22059e43ce8b9d39e628ddee9330ba22cb55da67aab985e6022e877568011d3e3eee84df1cad6cc924a426ae8c338140777f6e592c7872a85357931e586ad3f3684c884994495d20c30c5ec5f6de0244355de4b1fde1117b1068f64372ecdcda183e8215032a2d18157d59eb1aa0e09bb1e85fbcc0ab978e1860f6f457cb821a266bdfd197c53516264feb015c8562144cb4fa2c8a6918c6d5e08d3c050136f2905d373c219149dd6c37662da580148312067cbc440a9adb3ecd5df0cb01eb6ad1d4fcd7399b3fe43bcb7397abb7fe9323a71a2a290ab2b0e955979de28827c98e38fbc7c65d63a22e4f6dcd5077e182801e7821d59408cfb01bf70be7a5d063a48b51fa3c6899da9e902cb9305a68f74f4e32ecc6186130cec8daf78f4d40ec869286130ed41228373e496471376ab5646ee44b6f8ab4c4c9210557b7ec166fb4582fc8bdf2fe12a80e37cba8cabf8c7456b75a305aa0aabb6dc1e040ca088a28672bcad1a17c4b661611cae0b5d968c04df8388a06e69fb302cde54f5a1619c0275b1d7349a41561ce6476a8ba3bf3ab1b4f291dfa72cff5696f4203c733f78f8168ffbb2c97392db60ab160a6f42f1c3b8957e24104283deb670393aa20c5cac99bb54489620f16e0838fdb5b7f92515929745b58c2769d26374cca5d94cfb14e133fd3df40497a1fa9ac70a9e9a295ff659b04aaf81a5436dabde72e10af28a00a8f05cd6b3e6bc75847ca7515fd27d4b2cee97e21594d4a5b659a0504e77ccfcbeb082043f73dbc1484e5504b09c048c8a3a6c2db1278ac2205c78a48263e9ada4646dec7d27f28392667d3fb49dc101a5d1b6aade897bb92c692b956b9a83d7d2abfbdac1707a9d369cfc5260ce380f67c3ae6337e34c272ea646632f2c73465b1a13b358cdad9d4583a59cc3be1c8f2094282560aa83435074686f6abec5b4c895452ae74f92d9bbf6e9ec9728d07c397aef8611d09e75c00dfc297af2d724877127d5abca2f6a530ebc768617ad077d4712b26fba3cab83c6c74a7119a6623cb9a712eddadbd7811fd3948337dbdaa311f9891f96a324c20b56ef6d73450c5d3804dac0e51fc07fc82eddf441c9412e13efb701115362bf844ceb88793a131e136a0ca9935b7025a6682afbb8685c9b058c833dcd81e93e6dac958a31f5b942c4578ddb058ae644dddcb43fd9f785510c7a2bde7532b3f84b48e565cfb0ec1f55284ef355df662f091a401a52122823b46697eb141523b39dd9eeb73e10df2793a4e2107ee299405800df9e95340723b18e0d6c46fd7c4236ceaf34594a4c652b22145361f277fc5b3427d47e5b6c51db78e5b00050bc616c2e6efb54eced079a1a7186c080ccc3bab5989e0df45115d314a95664fff63ec61f73ec34c22033105410d1158e44456ba982984508a8c650d912c1d3fab14f7a12935ea1142eccb0748ddd33f1bcc07cd5614e24257ad9a656cfc4a8589042de2b29aa641b0e59b8fb2d83a0600a72c43d44b0da9f7df90985b27787686d14dfdfba29c895dce4ea9b827f5dea7d12d0536e36a90f98cd8e9078ab343ddc20a35dedf9e46010513eca0141bde7b2ab975abe920e201e7c55375eb7c1fe56b6677eed219a59d85c72e04ca348fae0171c2b4b33dd80e743954b80945cbc97c225e28be27d67c7425a9d0331f74919002b5cc2a120f51725bddf5bd8661f5a6c5909561f579536577473a6c8fadc7ca5b0a415d49e78441c3cc0d3b4e26d089bb515fb1a47d7a54814349b6e1cfebd137c2cfe94d3bd2ddcf7ae976a61f32638e55e0e290b6fdf379fda9428124380187eea5fc2a7ef55c7135b432d95b4916e594c3331dd28419a263202c48deb804ceb2119646cd56f1a907a9b4e723d582d1762c0c667b4ad1fe7887531540f19018896f660d57efbeece42880a36772b04b2b438adfa4b3f8c5c2474cad4d99d2e55a165ae536fd7713f3564464fcc1e193c170a0965bc4f0d1bfad82a5829000182e20381e80220301293ec00df8e1cf2d9bf338279beaaa9a9a471701f60f290da4576068acf01d82a5828000181e203922020d56877a200472eb74a6521299547efbfe80cce790d635b75516278eb1731941900000000000000000000000000" + }, + "result": { + "gasUsed": "0x2ea25d1", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xa714c6be95aa4ef44699b9098e264c0adead1caeffac75013c413a7a9798dcad", + "transactionPosition": 23 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001c17d8", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x1c5e5bc", + "value": "0x2db18102f13817", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000054400ebaf70000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x160c586", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x52aaaa21fb1cee8882aef391c310f8a26e6dc9a5d3cb968f2f384477dd6851a9", + "transactionPosition": 24 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff000000000000000000000000000000001c17eb", + "gas": "0x1b32788", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x13a470", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000128345008c8ab4014400d8af70814400e3af700000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x52aaaa21fb1cee8882aef391c310f8a26e6dc9a5d3cb968f2f384477dd6851a9", + "transactionPosition": 24 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001f3b4d", + "to": "0xff000000000000000000000000000000001ee777", + "gas": "0x199d203", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285108182004081820d58c0962920987d861c8b39327edda8e2adeb06abdb019d3aee70b32a296b43255088b551b860b2047e2362f6a9a8912ab69ca9ba5d09d7bcbfa7d5876af95f05fae5c17c6addc4914812aeb2480cd0fc42494879621ee9917e51828638cbb37f8ac50fb1113b1867527112595aa44ec24261ace85a81af4ca83ad5f3f2cbf1968c7b0172351332ff5b9163b9347f898f329294387bcbb1c4f82a2ac478d1db646a0be71d297d2fe1969e37bc5540e90cd7978d45bd21f3e042c45afd6ee8eb2dab1a1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" + }, + "result": { + "gasUsed": "0x154c312", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x8bfc485414cfc17776059a5ea837757f4df29dde3956b025bffb9dcc9706b3ff", + "transactionPosition": 25 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce37d", + "to": "0xff000000000000000000000000000000002ce3ba", + "gas": "0x41f9a33", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000007878219656359078089b3567e087d9f038414ae554aea4607bc0558961eca169c7a8d57e5bb6408861bedd976ad9c30ac5cba0d0da3af1de68cef5b46465cc730f3ced2fbdb031d392b950ce381b7f12967fc57998aa6f668cc8c5b553fb27ce9dc831e13666d13c007a2516ebe2ab906bd22cbab92d58f874b6f98a4680aa6d8790de18a3090df8b0ae3c90adf836c7b7b3f39830623867ea9c0e007f23be47549243aa4ce36277271a70e527ed4241d8496a27925d45e6898914cf1fbd2d118751b5b6cd292adc18891ab78a9153cacda89d3019f1ca81569cc4570725c70fde9d293bea660ce216b720bf0d27bba7121f33bdb9fb450e385b2d68d890711e47e0e7c98f0e38e0a4cbbaf8137be683ec840c651f4051b00c3ed7e825a6a05fcd81b98502c8c0d280b33fef4c8fe2c0d8dc8bb3d31f96558004f6a437dcd9c4c784ec5f19f9718480de4cbd8c354687bf9c80f0056eca127a37a5a80cd2f268ad24e5e7506b627ef5989354ac09beb5e95e9e202b4c0675b6ea01843afd38ce840a0a97d3083771e956a1040fd7bb21ff1040cd3d025beb0e32ce10166acf3365941e7606b9d021df6ad162f58daa88591e69d94ae13330dafe128e8fc8098ec8011d88e40058cda8de05fc93cbf3b1e81abd599f3ba177f0908627e954fb703a027c72c0bcd4fb9085f59cd9bf2237fb7476e866cc5b9d52fa6269f912f49f7bec55000e8e9166c10f6b0a587b9075cbbb06e9129ee4cf2ad0d222bcff3d3db550ea803bd3d993feb24128f4f27081caf42c65f39bdba76215f32815ed5db1df935bad67101314cade9087e8db56416dda958e20cb1b3ddd5df3739d66d06564f1acf725706aedd6c2a9ec15ec1371690e9581a75f29ff5a09a629e45ae13e6d5d94a5bba0242a2bb4ddabda70b62a3717a942dab67d4112d8b7325aa5cf54233f8c686e9d6b275123dfd791f93db568f8cccb0bea7cf8deaee33696314f092b04b6c8ae4b26e64d3fa436bb49d87205719324c2196db84b758440ee9ebf8b38f60799c26ff8e6237d92f183d94e3213cbe5285b8a4deeb80bc450899c97a0c3383ad17bcdf337599a7461bc3cd7a7b82116d715109cf7564dedc7ff071d502452267776e485740886090a92ebbf802e37ac635966fd8ce8157b93a0fb442f73207996136049fdcd81be00bbb015918683d16427df44576b27ebef8471bbc5fd1bac62006314c1117086f3352c36a7c68abe2054f70d999155445a87431d0cc42df77a1a1d909b92e78cebe4260e86821b1015ab8072b72b255072a7b87b1afbb78f2604c783d84e4a586f248d26720f5402046a48aec8b8999fb958fcd5c1ce873a3b96c95c9508e349d0df5608404fb0a0a9e459c746eb9fb655f4a6d719d3117042c76e202cc3b1d530c8a6f313f2d160d193596244aacf5e704055004579c2a83c5963d8ab595604d10bf98e9d80681c698bf8b9bcde33df3bf3169ab754f5f1fd862adf9250aa422e1611ebac1eba3cc793662894288a724fbd791e89dcdd7a3bc74ff9aee5294be60e991748a2fa432205cf2226cb232967edb4999a3bf0643be318de1296f684b86cceb1dd56b2cdc486c8ec1a6d788568b835318fb355fdac8f9a34cd68725d7cd120372a7f636002fb17f1d10ddb0e674a7e86b98391a77d862325b5195109ebfdb3d8a1e27a7f79d0f7c567cad351826467a63ef149cf5dbfab8399a4012da12af9d4ff6598b0ae54c05775dcf85c0191d38e339e222ead9376c8ee915df379c5b1b42abe15048667290fe02ce9783cc46f10d35bd600d530292992b08ef959940dabc733c9e60fec0ccb388a427a3bf07ded414b478c91d2026df0a52cf2c25aeea9849c86480cfff3600c43174f5d4451df4962a4d9cebb9e2f11797679d4d28533baafa05c253c25a4f62ab3d0190278e8c20fd72662a023e60e0e3eab027090e07716dce1251cf730690b2bf033236d3fe537e153201455032f691e2516a02c1e0a999ceb02c5709dd672c70897838f57a4f6a854ae13c157d5b006677ca44af39133b0663ee2e2beb1fe4a68a846ce3cc2114bf80f91e187da1f0b250fddfdfde59eee0b656da275deb9490dbc7289643c91c94500771e373fdc338251f35567d0bf69a1281300e5f8f5f2df9c3ae790e5d8493c9176ae7048496383654e519118d249be0e47252aeeeec73ce3954722f4ecbfd6dd0a931e35970085692b3c1592022fa557733d6125793516ca85a7b27816862c7db7ecf74a5c01a50caa3997f023a836c410178c402c610b48b97854c8c3b2522a3c73a176c08f86a3d9bcd31c513d54107ed0e87ecbe6a8eef753d7ef748b49ce8e07f0240214d85463e7cb2cede2738ae1ae0ae628abe17f62b3e75cafa9b5065e5469a13bd04e1c7f96924d7fa6464191b9ead0479821d0114ba27f1cdbc1cb935e1fed086c8a53dc38245a373bc68cdd8f01a84d9c5fe093ff831c7ab0606d1179e52877caae79c9112cc5af723d1d6bfaa0515b8313eaa7c65daef498853daccf0345999610f6aa633d04805b14c1a5a34b9c3a75f758f3004e62464fe3f23255148ee02029002108728d91a4fb0f8af2a2c3a44f3d97c810e03a926116f125a42c9d8da8c93da016e97f3e1b50a2fb838b23eab8b279f95fcc21cedbf474b3457ac3a3d791f5d188b694285ec2d1169ef8015203e11a8f28ab520c57b37b9390cecb600000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x695660", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc181546e031e0c986a730e2567075d746fa9a148c12c16b15c36e689dba86a87", + "transactionPosition": 26 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3ba", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x3e72861", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002ce3ba196563811a045041cf5820cf92e6b2348466d49f180699709bf0afcc372ea7d8543074c0b5c724a7ecb6a7582039251fcab982839627fe199c46354a2e52a24e2f438c66c568c62466f8aca8ab59078089b3567e087d9f038414ae554aea4607bc0558961eca169c7a8d57e5bb6408861bedd976ad9c30ac5cba0d0da3af1de68cef5b46465cc730f3ced2fbdb031d392b950ce381b7f12967fc57998aa6f668cc8c5b553fb27ce9dc831e13666d13c007a2516ebe2ab906bd22cbab92d58f874b6f98a4680aa6d8790de18a3090df8b0ae3c90adf836c7b7b3f39830623867ea9c0e007f23be47549243aa4ce36277271a70e527ed4241d8496a27925d45e6898914cf1fbd2d118751b5b6cd292adc18891ab78a9153cacda89d3019f1ca81569cc4570725c70fde9d293bea660ce216b720bf0d27bba7121f33bdb9fb450e385b2d68d890711e47e0e7c98f0e38e0a4cbbaf8137be683ec840c651f4051b00c3ed7e825a6a05fcd81b98502c8c0d280b33fef4c8fe2c0d8dc8bb3d31f96558004f6a437dcd9c4c784ec5f19f9718480de4cbd8c354687bf9c80f0056eca127a37a5a80cd2f268ad24e5e7506b627ef5989354ac09beb5e95e9e202b4c0675b6ea01843afd38ce840a0a97d3083771e956a1040fd7bb21ff1040cd3d025beb0e32ce10166acf3365941e7606b9d021df6ad162f58daa88591e69d94ae13330dafe128e8fc8098ec8011d88e40058cda8de05fc93cbf3b1e81abd599f3ba177f0908627e954fb703a027c72c0bcd4fb9085f59cd9bf2237fb7476e866cc5b9d52fa6269f912f49f7bec55000e8e9166c10f6b0a587b9075cbbb06e9129ee4cf2ad0d222bcff3d3db550ea803bd3d993feb24128f4f27081caf42c65f39bdba76215f32815ed5db1df935bad67101314cade9087e8db56416dda958e20cb1b3ddd5df3739d66d06564f1acf725706aedd6c2a9ec15ec1371690e9581a75f29ff5a09a629e45ae13e6d5d94a5bba0242a2bb4ddabda70b62a3717a942dab67d4112d8b7325aa5cf54233f8c686e9d6b275123dfd791f93db568f8cccb0bea7cf8deaee33696314f092b04b6c8ae4b26e64d3fa436bb49d87205719324c2196db84b758440ee9ebf8b38f60799c26ff8e6237d92f183d94e3213cbe5285b8a4deeb80bc450899c97a0c3383ad17bcdf337599a7461bc3cd7a7b82116d715109cf7564dedc7ff071d502452267776e485740886090a92ebbf802e37ac635966fd8ce8157b93a0fb442f73207996136049fdcd81be00bbb015918683d16427df44576b27ebef8471bbc5fd1bac62006314c1117086f3352c36a7c68abe2054f70d999155445a87431d0cc42df77a1a1d909b92e78cebe4260e86821b1015ab8072b72b255072a7b87b1afbb78f2604c783d84e4a586f248d26720f5402046a48aec8b8999fb958fcd5c1ce873a3b96c95c9508e349d0df5608404fb0a0a9e459c746eb9fb655f4a6d719d3117042c76e202cc3b1d530c8a6f313f2d160d193596244aacf5e704055004579c2a83c5963d8ab595604d10bf98e9d80681c698bf8b9bcde33df3bf3169ab754f5f1fd862adf9250aa422e1611ebac1eba3cc793662894288a724fbd791e89dcdd7a3bc74ff9aee5294be60e991748a2fa432205cf2226cb232967edb4999a3bf0643be318de1296f684b86cceb1dd56b2cdc486c8ec1a6d788568b835318fb355fdac8f9a34cd68725d7cd120372a7f636002fb17f1d10ddb0e674a7e86b98391a77d862325b5195109ebfdb3d8a1e27a7f79d0f7c567cad351826467a63ef149cf5dbfab8399a4012da12af9d4ff6598b0ae54c05775dcf85c0191d38e339e222ead9376c8ee915df379c5b1b42abe15048667290fe02ce9783cc46f10d35bd600d530292992b08ef959940dabc733c9e60fec0ccb388a427a3bf07ded414b478c91d2026df0a52cf2c25aeea9849c86480cfff3600c43174f5d4451df4962a4d9cebb9e2f11797679d4d28533baafa05c253c25a4f62ab3d0190278e8c20fd72662a023e60e0e3eab027090e07716dce1251cf730690b2bf033236d3fe537e153201455032f691e2516a02c1e0a999ceb02c5709dd672c70897838f57a4f6a854ae13c157d5b006677ca44af39133b0663ee2e2beb1fe4a68a846ce3cc2114bf80f91e187da1f0b250fddfdfde59eee0b656da275deb9490dbc7289643c91c94500771e373fdc338251f35567d0bf69a1281300e5f8f5f2df9c3ae790e5d8493c9176ae7048496383654e519118d249be0e47252aeeeec73ce3954722f4ecbfd6dd0a931e35970085692b3c1592022fa557733d6125793516ca85a7b27816862c7db7ecf74a5c01a50caa3997f023a836c410178c402c610b48b97854c8c3b2522a3c73a176c08f86a3d9bcd31c513d54107ed0e87ecbe6a8eef753d7ef748b49ce8e07f0240214d85463e7cb2cede2738ae1ae0ae628abe17f62b3e75cafa9b5065e5469a13bd04e1c7f96924d7fa6464191b9ead0479821d0114ba27f1cdbc1cb935e1fed086c8a53dc38245a373bc68cdd8f01a84d9c5fe093ff831c7ab0606d1179e52877caae79c9112cc5af723d1d6bfaa0515b8313eaa7c65daef498853daccf0345999610f6aa633d04805b14c1a5a34b9c3a75f758f3004e62464fe3f23255148ee02029002108728d91a4fb0f8af2a2c3a44f3d97c810e03a926116f125a42c9d8da8c93da016e97f3e1b50a2fb838b23eab8b279f95fcc21cedbf474b3457ac3a3d791f5d188b694285ec2d1169ef8015203e11a8f28ab520c57b37b9390cecb6d82a5829000182e20381e80220da354058660436f1563af7b6a0642c182b1a6fa48602b441b5b47c19c1b0c60fd82a5828000181e203922020185214c7c8cb8fa5f08e57348acba88012ee10f72b1fa2e048d495d152598b08000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2ebb8b6", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc181546e031e0c986a730e2567075d746fa9a148c12c16b15c36e689dba86a87", + "transactionPosition": 26 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce37d", + "to": "0xff000000000000000000000000000000002ce3ba", + "gas": "0x4af0e25", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000007878219656f590780970b4083f06e6772b24fde9600f285c9f542ffb9b93d15ac32a9cfaa002ed367b4d8ee029ac689e0b2a9f797a52bbdd1b01687a9ceb8c9790e00f14c1a9463b0f2caa20f079f8b4aaedd577aa0e21dd971bd3bc56a3eae10200b2e1c2270170508c50c10a32fccc9206146f1d57109fc723b6aef3a814c10ff21d9f9f57e30fb5c88eddaf18dc525847f6ac1519e200e8bfa5521554b9e6519e998b5c41b099e076efdef54142293a4164be42f89a6c006e9ca0a718ebf402aadb270d9b48bbaa30f8cc5d9558d81f61871b4fd006879593d13363fd3ef2d0a8589603037a53d315f2cadbba24eeceee4e6a5b43df62493eec2ba6c2da36e7ef21140e9d7b9f2623466cf2af2d3dac9370b4e7df3439beac4b58b9021ad7c3e8aa5fcd8d76eb21157ee86adaa6ca9fafaae6a986de700eb4446e710eb8a07d95b29f39cff7c35fa5d39f985203e26e0a739506473a53390d4a05c2a890168ae9dde4a0cfc34e7a215e16c83148c671d7f56105ad34ce38de2454a5c6807c422b7d640e4577b718e435b0ee978fdbff427bb8a9aece830bfa5146061849d05b881c9b7212fcdf7798f773347f8b9a0d7a70a2cb83902dd84f882f7245624ba8c44b1d06f98f5159e4f51494105c0b26830321f349f0dd436d1726d00041b0a490a404a689cd9c61949cb8c1624cb83b5ef7ccbbae5fea806b2ff6d9b32c3b4e48ae0046404f2c072676a7d7922467a625948821dbc2c81b8da97d3a187b329f5e86e899fa0a200fb5cb3e083dcf0010a77a3096e3b61afa89d10c834fbd8ec58cf4beef1d1f878aa3ddfdaf747853e9fc63f9f7f2e12e84bc85e453ef19d6b4ed18958fe3405aba887287def1c8dc15777b265c0a42780acf9897858c07cc9b2b9b02b6c90fc3b77a01dd974deb58864d6e31e0ebe2574c9a9283806ea6356b71bd4262fe608740cdfc4631ec56e1c09db0bc46c89698e2eb0e55e81ef73c3c8879b25a1f362a90543c74acd4cbb35cbe9d35765405457b626d5151ee48d91ec4e03ab0429dd9feab2c461f67dfc7ec78211aec693a2815fddd400c8846d224b869565060d2f2c87b753b8df856328e65ddcd3f60638749b6943989ac46cb8e85f33228497c5d2267c75282bc7d3fc69047799cd70f359964a95667d91e13f7d30daebe8bcae1024ace8f8e551f174538b37ca05841dd4a46063b760df8e6f3630953d716c5c410a4d573152fd5ad0360d81d57a051394c739b4e64c0f90679bb3ac490541788cdfc4f6bc54cb95114f05d625530420cdaca32574e660e7f0f9862b12e4ce7efb8543cc1dd745c2a7673f526fd938ab10b82b1a9c725ed7b83573b304ee6d2c13a782368d77f770fd62aec69bf981c3db02fd32b885661ad86de36b04daa7235ca2c7a0ac6f54a43a6af26add753ca2b6991bf2b4450645fff88b7dd39e73437bde00bad496297eb4d7b8522b8c2f2d5b375f43d8b145d5f1eb5c9525f15d8f91095bc1098415496f0570ddbc02173225aeb99cea9b0a4815f7f748043799771a1439be8fbc024c7931242d48262cc08aaa3b158d7fa8a1a53658d6ff4c902b272edcac0ea9650d2956725308dd3b45924f9ac61a1333a09d1ab64b9e3e0d7722b25d972258a46640ad2fd637d28880df353921218d8dc81615f8e43547b9c9759d3c2252e3aae6f07e001483bb5b5bd8a69c0ba553292ad1d8c14af76e7ced6b19a5d2457ae64bf6dba77b17a7bdcd130fdddf49f2f290b8c667b7f2badb370c1277166fb98f700cd6dd05dd895da055a09669e3467ac5d6cfcba2872f834c1834d610436fe6dfeb22f1026a113ea69aa671af6976925cbd5dd0f99babe9b950cf89a275ef1a28c541c2f342222326b9fce88df14f5e2cc597bd120cd518c7228d6c8a8c6f42db9078b54bfbdb3d6985a5fff3878608b15d4c5edeeff9657f7ae9bb515da83879a97566a1de5ea47c8f8db7fa1bdbb0ad3e8a89806e028a8a54c61ca914dc001ffa741069ac048a0c6afdf37b308c514bdc40556ba4351b1d340cb76431035265fd4bab3cf003b2fe67f019815f28d1402e4380a89dfab1e0b90274c861b288fd381ac83f48cdc4b5218d12dc7e41c6fcd86ab860d59c960db092d456e19d0d2ccfe7233fc7baea842a410c1bfaab6fb587d6fc26880ee860b6aef390991b1b242301e28680c6d65a39d49d7c7e25a8c1411571a66010a74f87851a7314b07731bbf30c6d786ecf42709360b3fc1b12c450115f35e707b41f258073d287a602b6fd57389348b8fec9366c80350ac96b03a10b0f8ad4c09455c906b681a9a1f9405d32fd8f08fd5fa9e9ff70b2b4a0fca4cb1dd659d39e9e99c630f2d3aa056f8f3acc239497f45643e0a7b6d86e7922129429caa5d71b00a88e3aefea943d8efc66f97ac160d570d2834c8114aeb1232010ca35826faa4cf786aeda3204f74c456407ae72a09bad8eb9261510bfabe03ef8c206645f36edd78572babe7ab11f7d26cd0bbd07c868eaad8e659ff329cfa502aa61081506313f76a4f81d100fd693b37c8877c9a6b543a8ee45e826669d7326b15681f5f83af7fb13b323cc649316cbccfa93e76756718bbb0a0e11ccfd5335af216c8ea21ba41976cfd65caa7c0072bd2f2df78fe32bbfa5612057cbf2c059d946ca4f5480043ca4d79ec3f1af8d36c47b0e1cb7a01e897485b1a76c00aad500e378da9ef789b500000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x661c29", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x2388a8d8981d196c52b6b731e06991d9a502910cbaeaa5396a156b882a6f6097", + "transactionPosition": 27 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3ba", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x479cbfe", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002ce3ba19656f811a045041325820cf92e6b2348466d49f180699709bf0afcc372ea7d8543074c0b5c724a7ecb6a7582030f7dea42fec628a29ba26a0dab17df120f5f9b2740e0ca4ba1c332d86c1eea2590780970b4083f06e6772b24fde9600f285c9f542ffb9b93d15ac32a9cfaa002ed367b4d8ee029ac689e0b2a9f797a52bbdd1b01687a9ceb8c9790e00f14c1a9463b0f2caa20f079f8b4aaedd577aa0e21dd971bd3bc56a3eae10200b2e1c2270170508c50c10a32fccc9206146f1d57109fc723b6aef3a814c10ff21d9f9f57e30fb5c88eddaf18dc525847f6ac1519e200e8bfa5521554b9e6519e998b5c41b099e076efdef54142293a4164be42f89a6c006e9ca0a718ebf402aadb270d9b48bbaa30f8cc5d9558d81f61871b4fd006879593d13363fd3ef2d0a8589603037a53d315f2cadbba24eeceee4e6a5b43df62493eec2ba6c2da36e7ef21140e9d7b9f2623466cf2af2d3dac9370b4e7df3439beac4b58b9021ad7c3e8aa5fcd8d76eb21157ee86adaa6ca9fafaae6a986de700eb4446e710eb8a07d95b29f39cff7c35fa5d39f985203e26e0a739506473a53390d4a05c2a890168ae9dde4a0cfc34e7a215e16c83148c671d7f56105ad34ce38de2454a5c6807c422b7d640e4577b718e435b0ee978fdbff427bb8a9aece830bfa5146061849d05b881c9b7212fcdf7798f773347f8b9a0d7a70a2cb83902dd84f882f7245624ba8c44b1d06f98f5159e4f51494105c0b26830321f349f0dd436d1726d00041b0a490a404a689cd9c61949cb8c1624cb83b5ef7ccbbae5fea806b2ff6d9b32c3b4e48ae0046404f2c072676a7d7922467a625948821dbc2c81b8da97d3a187b329f5e86e899fa0a200fb5cb3e083dcf0010a77a3096e3b61afa89d10c834fbd8ec58cf4beef1d1f878aa3ddfdaf747853e9fc63f9f7f2e12e84bc85e453ef19d6b4ed18958fe3405aba887287def1c8dc15777b265c0a42780acf9897858c07cc9b2b9b02b6c90fc3b77a01dd974deb58864d6e31e0ebe2574c9a9283806ea6356b71bd4262fe608740cdfc4631ec56e1c09db0bc46c89698e2eb0e55e81ef73c3c8879b25a1f362a90543c74acd4cbb35cbe9d35765405457b626d5151ee48d91ec4e03ab0429dd9feab2c461f67dfc7ec78211aec693a2815fddd400c8846d224b869565060d2f2c87b753b8df856328e65ddcd3f60638749b6943989ac46cb8e85f33228497c5d2267c75282bc7d3fc69047799cd70f359964a95667d91e13f7d30daebe8bcae1024ace8f8e551f174538b37ca05841dd4a46063b760df8e6f3630953d716c5c410a4d573152fd5ad0360d81d57a051394c739b4e64c0f90679bb3ac490541788cdfc4f6bc54cb95114f05d625530420cdaca32574e660e7f0f9862b12e4ce7efb8543cc1dd745c2a7673f526fd938ab10b82b1a9c725ed7b83573b304ee6d2c13a782368d77f770fd62aec69bf981c3db02fd32b885661ad86de36b04daa7235ca2c7a0ac6f54a43a6af26add753ca2b6991bf2b4450645fff88b7dd39e73437bde00bad496297eb4d7b8522b8c2f2d5b375f43d8b145d5f1eb5c9525f15d8f91095bc1098415496f0570ddbc02173225aeb99cea9b0a4815f7f748043799771a1439be8fbc024c7931242d48262cc08aaa3b158d7fa8a1a53658d6ff4c902b272edcac0ea9650d2956725308dd3b45924f9ac61a1333a09d1ab64b9e3e0d7722b25d972258a46640ad2fd637d28880df353921218d8dc81615f8e43547b9c9759d3c2252e3aae6f07e001483bb5b5bd8a69c0ba553292ad1d8c14af76e7ced6b19a5d2457ae64bf6dba77b17a7bdcd130fdddf49f2f290b8c667b7f2badb370c1277166fb98f700cd6dd05dd895da055a09669e3467ac5d6cfcba2872f834c1834d610436fe6dfeb22f1026a113ea69aa671af6976925cbd5dd0f99babe9b950cf89a275ef1a28c541c2f342222326b9fce88df14f5e2cc597bd120cd518c7228d6c8a8c6f42db9078b54bfbdb3d6985a5fff3878608b15d4c5edeeff9657f7ae9bb515da83879a97566a1de5ea47c8f8db7fa1bdbb0ad3e8a89806e028a8a54c61ca914dc001ffa741069ac048a0c6afdf37b308c514bdc40556ba4351b1d340cb76431035265fd4bab3cf003b2fe67f019815f28d1402e4380a89dfab1e0b90274c861b288fd381ac83f48cdc4b5218d12dc7e41c6fcd86ab860d59c960db092d456e19d0d2ccfe7233fc7baea842a410c1bfaab6fb587d6fc26880ee860b6aef390991b1b242301e28680c6d65a39d49d7c7e25a8c1411571a66010a74f87851a7314b07731bbf30c6d786ecf42709360b3fc1b12c450115f35e707b41f258073d287a602b6fd57389348b8fec9366c80350ac96b03a10b0f8ad4c09455c906b681a9a1f9405d32fd8f08fd5fa9e9ff70b2b4a0fca4cb1dd659d39e9e99c630f2d3aa056f8f3acc239497f45643e0a7b6d86e7922129429caa5d71b00a88e3aefea943d8efc66f97ac160d570d2834c8114aeb1232010ca35826faa4cf786aeda3204f74c456407ae72a09bad8eb9261510bfabe03ef8c206645f36edd78572babe7ab11f7d26cd0bbd07c868eaad8e659ff329cfa502aa61081506313f76a4f81d100fd693b37c8877c9a6b543a8ee45e826669d7326b15681f5f83af7fb13b323cc649316cbccfa93e76756718bbb0a0e11ccfd5335af216c8ea21ba41976cfd65caa7c0072bd2f2df78fe32bbfa5612057cbf2c059d946ca4f5480043ca4d79ec3f1af8d36c47b0e1cb7a01e897485b1a76c00aad500e378da9ef789b5d82a5829000182e20381e802209ce6925c121712eda1a12f4df52b5a3d3105f320dc6b45f24c785776a953de4cd82a5828000181e2039220207ff6f6be1300fb4908c0daf51e3d7d0425283612424a721db42365b52ea8cc06000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x361d537", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x2388a8d8981d196c52b6b731e06991d9a502910cbaeaa5396a156b882a6f6097", + "transactionPosition": 27 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000021ef69", + "to": "0xff0000000000000000000000000000000021f07d", + "gas": "0x1afc9e2", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285068182004081820e58c0b13a7cf6ffb355576e128fb8aee41b36616a208be66d052ac70553d944dbe0f1b0acc2540132e6e73d2768ef30811b87a0fa8aafd877070dba2c069689f4ee35e6bfbaa13cbfaabf1fb4993ce394f46d869176762f568fd424489bd4f30f1393039b7e812f687b929ad1239207ac71e60dbd48891e13f11eeaa0ca96e8552f2dd050d9717c63e3a1d9e101baf351083baf7e27633b6eab89e3fbeabd50cb6e355d36fc8a22137e9687355072fefc0d1c4291d9122bb90d719155ddf81db1dd2b1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" + }, + "result": { + "gasUsed": "0x1665f4e", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x3cf0131db87f6dfaeea3932633507f9dff31e2c8f7679e0eb5205035cf910373", + "transactionPosition": 28 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000021ac60", + "to": "0xff000000000000000000000000000000002174cc", + "gas": "0x180d49d", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285078182004081820d58c08b865932f85aa47714a2504c2449ae644e1e707b02cfa87f65050ead44fffdf335551663226b76a111eb07fa6f925cc1a2a3e2e0adc244bfe617b684dfa7e7e3b3b50156599a634395500da0cb12066f3376acd9ede2debe33e4c947a7c1aa710af15c15c5af34cddf7e3eb2dba604767d0ff52d42c2f96d16bb7ca5212ed5d43383279eea49778963a1fe2ef07ffd5fa66de8142d0af0bb17aaa80803d95dff6feff0b487353f7879bf9f090949d38260a4fbf6e5557bd5714ce8d4b0410bbd1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" + }, + "result": { + "gasUsed": "0x140ce4b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x256951a7f0727529f3eac30506f6961b9bb393ef925d1f2d92ae118855bc62aa", + "transactionPosition": 29 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000129271", + "to": "0xff00000000000000000000000000000000129273", + "gas": "0x1c8bfc9", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f2850e8182004081820d58c08f79c809bb284bc44d5dbea91400a0fa579c80d3a67a42aa097fd6d87f87ad57618a343124258c40eb83694be6b6fd1c8fbb50bc744e3bb07263fcf016c2b01cca117a25eb6fe38e8502f6d4f5eeb61df3e96e085f2fdf8aa1b67cc6b28de7b7165f3bb0a76cd3f86416410c209b10c43eea9cefd8dcf6da457e6d51e3921920cefc97215219538eab13d665987d0bf0856d7030ab5b71078c7b3ee750aabfba4f8a95711aedde2e313594761bdcf6e44332b66beeb9f956386f9c820a1eed401a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" + }, + "result": { + "gasUsed": "0x17a611b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc6547d24edca37263cea22782639104718c6ab47802a579a7c593b50c03c5897", + "transactionPosition": 30 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000024071d", + "to": "0xff00000000000000000000000000000000240720", + "gas": "0x4dea67f", + "value": "0x249707e1ba2b041", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782197b81590780b1f9d380ba870fa437208ac15878ab89d9f34543419b3e34eb4c0ffcab1dd0992538a2d9071e62187bb0ff89a3e2af7e80975593af8b408966223d147eedca680aebdd1e5bab0e28ef8339ffb595adbaf18b2d96f245274e15cfdd799db6e3a4034db1b7497bd552b7f38cd4b215e241c9988f3640325e3e4001cc21d2cd8bfcc05f2e21f8845d235d866232c134342f99eaf59c7ebc23005f57b27254bb8d7d7648a4364ad5521203158f4a2aca6046c19beb35782af2b2f3b05e4054cc5a5982fc8035f2b2e5bad7763f27a2ff9045af7149ec036d283304f6d2f24e63438b862089e498d000357170027730c997aa9882ac929ff2054cafece481a559624985d64edbf24eba8e157e7b98d14f2bf9a39a53e88862cdc4c613ccebcfb497600591a0a9018199adc75cd6d4b3c20e910d8e20aea5c1eb09609581e8ae4ab6fdeb211c974a031d6e54ce46df51beecbf879b41106c7414eb1cc5b04fda2c51a3531c7f04709c9e22f613c3d3b7ad4f5e7e781f3b42545baa78bc2493253841bcad24670ba011f5ba63fcc93e2d067f4fd0f1b614244aa5ef3841c10b7cc1a74cc81c9782d0ce1ac66177215374e7a1aea72a75e2e2bb9da9a6192b5e83f3bf67d6d965cc6a71aa5d830712576370ab75f0b7b6fc30ffb3cf538d1f9ee8447b1612b6a9c367ed3f20311c673c413c51a5a4b17bcabf9387231a5c2114b7b53f8aee48ac87a5ad6c6423c5ee5b14c54a4aa25edc9889e8246fc3b1d57dcbec40489d316220fc885f960f9a0f1b726b170a0bb338f6025eddd39e8dda03087a123f8932ffe6f36555ef77d5fac549d71a600f443ec3ad1f148037da3e1a8174cafe06935278299e757623eb47f0135f0b75b68438c64e93676fca8fdb911d7fa4768c03a37ad1301eccf1590446ab692118005081a29cfb985c9d924f5bd657bfac0e68a82a191a5ffc09122189493329c1ef4e363e9bce42884fac89c5b1e253d6ae3ca4d306ec9e2ec762950a9752d00291c5c57417006d9aba659a30d9b2b331ad8b860adc960e7597697e7c5bbf6995629d11e3c48b7b260af7a0505eada5628158b9e64913ad94aed332e8102eddad06c33064c68eb46662ea33655673f00ed13a5d5ee4f59943c4cfb1cbef7773ab83d50e98c6098e431b0a5ecd0e1a48d8740818809c0bda28094d6cd0999cf27c3166eba8f0842e81b4fc28f3e3340b3c0a321942f6d75cdd91ed3fba60b2cb7b2d5278c2f4e7a5ee24def2a35054b8d3091d554c985a2d53301b7558373ef8ce80bca45cd25e1a5357811d816a6ebc50fe673d5273453407f058311bfb9bcdd0d7d08d58a6da2ab1bcd5916ce7b2bb5ca34d875957c53712a9b1950220e8ce327d3c8a1d308dae2830620e9dc5e1f1d385fba934ff856b4627fa732ac1ddaa43b572089da4a6bee77be2cc1326fd910edc12ec05eeea0930b31806eb6143e89e024d51e1f90829f4d0189d29c414b84e0efc6a6cb9aef21257c6652c05ee743127c77d5946e48aedcb71a8ab3bb5ad4e815d882e9d397cc6d3b39644939d3085afb44969e77ff50629a356bf1caba25d9eca119afb0db5c351018d98b6b7de8d9ccea9fdd725295f738c2c8e9522d95ea2171db0c927976321c3651b67da188e509b4c64368ba8989efff9eecbaa2dd4b3e24fb0181adae49c248f6f523a5ff98a4c2d53ecc8042a8ff6863f41174cd04958fa488513cb38afb23a50f922cf247701b63ed1bf3b76d22eb64b38f39280195c59321bca0f4988251f886e16e9d6cb28eb13b1bcb33a5defe754ad4fa7554e54b9e3d1bb419f69b8e8aae47ed6e090541503f8c5ba540fc8eecd09755f1304266ebe5add110b0c10ecd0b6cc31313fb341c4beb9ff6b6801baa267f8be3f99545966ea7d36df8d78d8a7b764e2f142e7143043ba3a3fe3ad4e1172975e7e5a16920a21ed3e173b0fd4a2d178f124829c312bb34b8a086c661db0cf539643f06f9321f0ddab3443a54041dc26fef93072b60025182b4c319ceeb27490c3fb1895be83f081812a7e442f041c803636dc7bd7af7f91772485de16a3a70a6423441af2d578060152c6978b7f5eaf265f96aa593c6b8988ef6abd7f0c211685ebd15f4691d81a9b61458123e8f3222dae3d7eec64d3d793f8bcb89ff0957ca775a89e2143167d7cb39ebd4ced7f5ff6d5777e07feb6be110c0236f316ac919c2e4fc2bbab76ea38720a16a5e5b2d4bf40a8578daf93a7b78394ca172c71be8eb9d9f7872a31e25ce6ae6905b2761cc870eac7122db2f6a2462a0f6d230c3ea41b162f17567030cef113fdcde321e3b5d33a77caf6b4429ba578f598c7a76dc604661233e1d55b99359bfc5f19d67a64b6b04d36f99502fdb41c7946ac73b44c9ec05bbdcba1f76f1ecc660226fbdc1e225854866849e918e849736f04ac6c084294a822d73059f9fe80af1b41762b2fc84e140674c1864599dacf5ed6ad074e1095366a4918b0dad7abba9b24263bbf5e995ced17f759c6978d54af02a9b9c78d057896ee884d8df1fc163ef03b38d449e7f45cc41d831d132f75032c8665ce7b04b88c28b1ea61f1ace20ff87735ceac8ebff21ce2769aefd6e462c71a55150b775178016c4367d3a0772c6074ccababa6af341859176f0899d58047d4e95913a944acdca04f14c985d11118f0e9c07da419d22a1b3b249c00c61d0016ebd48c00000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x924748", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xbac6d7be578578bc26e152bd6d6daa4a9e87f38bdf6b42eb34a1fa0436188dbf", + "transactionPosition": 31 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000240720", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x47d5eee", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000082c8808821a00240720197b818058204fb7698ad0ac99ea2b79cfa2feaa9b606fb14a18877cfabe989d204b529fa8b958201e32feaddd3941fa6bc24f9608e4aaf72e91fb8c668ab31bd76c8a22f8c57243590780b1f9d380ba870fa437208ac15878ab89d9f34543419b3e34eb4c0ffcab1dd0992538a2d9071e62187bb0ff89a3e2af7e80975593af8b408966223d147eedca680aebdd1e5bab0e28ef8339ffb595adbaf18b2d96f245274e15cfdd799db6e3a4034db1b7497bd552b7f38cd4b215e241c9988f3640325e3e4001cc21d2cd8bfcc05f2e21f8845d235d866232c134342f99eaf59c7ebc23005f57b27254bb8d7d7648a4364ad5521203158f4a2aca6046c19beb35782af2b2f3b05e4054cc5a5982fc8035f2b2e5bad7763f27a2ff9045af7149ec036d283304f6d2f24e63438b862089e498d000357170027730c997aa9882ac929ff2054cafece481a559624985d64edbf24eba8e157e7b98d14f2bf9a39a53e88862cdc4c613ccebcfb497600591a0a9018199adc75cd6d4b3c20e910d8e20aea5c1eb09609581e8ae4ab6fdeb211c974a031d6e54ce46df51beecbf879b41106c7414eb1cc5b04fda2c51a3531c7f04709c9e22f613c3d3b7ad4f5e7e781f3b42545baa78bc2493253841bcad24670ba011f5ba63fcc93e2d067f4fd0f1b614244aa5ef3841c10b7cc1a74cc81c9782d0ce1ac66177215374e7a1aea72a75e2e2bb9da9a6192b5e83f3bf67d6d965cc6a71aa5d830712576370ab75f0b7b6fc30ffb3cf538d1f9ee8447b1612b6a9c367ed3f20311c673c413c51a5a4b17bcabf9387231a5c2114b7b53f8aee48ac87a5ad6c6423c5ee5b14c54a4aa25edc9889e8246fc3b1d57dcbec40489d316220fc885f960f9a0f1b726b170a0bb338f6025eddd39e8dda03087a123f8932ffe6f36555ef77d5fac549d71a600f443ec3ad1f148037da3e1a8174cafe06935278299e757623eb47f0135f0b75b68438c64e93676fca8fdb911d7fa4768c03a37ad1301eccf1590446ab692118005081a29cfb985c9d924f5bd657bfac0e68a82a191a5ffc09122189493329c1ef4e363e9bce42884fac89c5b1e253d6ae3ca4d306ec9e2ec762950a9752d00291c5c57417006d9aba659a30d9b2b331ad8b860adc960e7597697e7c5bbf6995629d11e3c48b7b260af7a0505eada5628158b9e64913ad94aed332e8102eddad06c33064c68eb46662ea33655673f00ed13a5d5ee4f59943c4cfb1cbef7773ab83d50e98c6098e431b0a5ecd0e1a48d8740818809c0bda28094d6cd0999cf27c3166eba8f0842e81b4fc28f3e3340b3c0a321942f6d75cdd91ed3fba60b2cb7b2d5278c2f4e7a5ee24def2a35054b8d3091d554c985a2d53301b7558373ef8ce80bca45cd25e1a5357811d816a6ebc50fe673d5273453407f058311bfb9bcdd0d7d08d58a6da2ab1bcd5916ce7b2bb5ca34d875957c53712a9b1950220e8ce327d3c8a1d308dae2830620e9dc5e1f1d385fba934ff856b4627fa732ac1ddaa43b572089da4a6bee77be2cc1326fd910edc12ec05eeea0930b31806eb6143e89e024d51e1f90829f4d0189d29c414b84e0efc6a6cb9aef21257c6652c05ee743127c77d5946e48aedcb71a8ab3bb5ad4e815d882e9d397cc6d3b39644939d3085afb44969e77ff50629a356bf1caba25d9eca119afb0db5c351018d98b6b7de8d9ccea9fdd725295f738c2c8e9522d95ea2171db0c927976321c3651b67da188e509b4c64368ba8989efff9eecbaa2dd4b3e24fb0181adae49c248f6f523a5ff98a4c2d53ecc8042a8ff6863f41174cd04958fa488513cb38afb23a50f922cf247701b63ed1bf3b76d22eb64b38f39280195c59321bca0f4988251f886e16e9d6cb28eb13b1bcb33a5defe754ad4fa7554e54b9e3d1bb419f69b8e8aae47ed6e090541503f8c5ba540fc8eecd09755f1304266ebe5add110b0c10ecd0b6cc31313fb341c4beb9ff6b6801baa267f8be3f99545966ea7d36df8d78d8a7b764e2f142e7143043ba3a3fe3ad4e1172975e7e5a16920a21ed3e173b0fd4a2d178f124829c312bb34b8a086c661db0cf539643f06f9321f0ddab3443a54041dc26fef93072b60025182b4c319ceeb27490c3fb1895be83f081812a7e442f041c803636dc7bd7af7f91772485de16a3a70a6423441af2d578060152c6978b7f5eaf265f96aa593c6b8988ef6abd7f0c211685ebd15f4691d81a9b61458123e8f3222dae3d7eec64d3d793f8bcb89ff0957ca775a89e2143167d7cb39ebd4ced7f5ff6d5777e07feb6be110c0236f316ac919c2e4fc2bbab76ea38720a16a5e5b2d4bf40a8578daf93a7b78394ca172c71be8eb9d9f7872a31e25ce6ae6905b2761cc870eac7122db2f6a2462a0f6d230c3ea41b162f17567030cef113fdcde321e3b5d33a77caf6b4429ba578f598c7a76dc604661233e1d55b99359bfc5f19d67a64b6b04d36f99502fdb41c7946ac73b44c9ec05bbdcba1f76f1ecc660226fbdc1e225854866849e918e849736f04ac6c084294a822d73059f9fe80af1b41762b2fc84e140674c1864599dacf5ed6ad074e1095366a4918b0dad7abba9b24263bbf5e995ced17f759c6978d54af02a9b9c78d057896ee884d8df1fc163ef03b38d449e7f45cc41d831d132f75032c8665ce7b04b88c28b1ea61f1ace20ff87735ceac8ebff21ce2769aefd6e462c71a55150b775178016c4367d3a0772c6074ccababa6af341859176f0899d58047d4e95913a944acdca04f14c985d11118f0e9c07da419d22a1b3b249c00c61d0016ebd48cd82a5829000182e20381e8022004dd1fca6c24f41664d592050ba2076cd6039cda423fda286e09f819521aab4dd82a5828000181e203922020077e5fde35c50a9303a55009e3498a4ebedff39c42b710b730d8ec7ac7afa63e0000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2ed1ebd", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xbac6d7be578578bc26e152bd6d6daa4a9e87f38bdf6b42eb34a1fa0436188dbf", + "transactionPosition": 31 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002d0792", + "to": "0xff000000000000000000000000000000002d0798", + "gas": "0x4925b8d", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000787821962a95907808265626909a4ea50de70b5e4dfaeeb5b403aab71f3409491deb25fb3c4449f50fe2bc1a5537e4b2608e03546eaa9d29687514caf1220dcb30c1e835079e713548f6740668c61e52d683dc8b67070abd42b15a531ad8b40175096f86a2c8b2ec702e738164a93493f95163c8c21e7e52868bf0fe9c5b00f8990f3f7ceada05f43383b91396b367551323e598bbdab72e18520442cea081a512f194206860173141bde8125f3e07bb1cf4d5fd549d25a0d4c88ef18b36556d3bc244e193bdd09d2875d98ede100bede4df18c21c96fb3cd453a1f3f93a0fdef627cca54d991ca62939ab2da07ae3aedc77fc02f7adf0bc2ab8fc7a2dbdbbe43822a6614121339caa742d899a8cec7b24bbecb74223453a797fb13d2a02fa5d9e64bea767015e9b204e07f623f6a3fa48d963e1862f6b3621aace85bcf136cfefbd90d7da18acd89a80095c8c5d69e81fb87509d591346c2b64a4b00034d547ae2a2434e2581d941e66b13ee33c24e4c6855b89e714903fa8144a973bb15845b611cc4b35a31b9af98a9dc2008efd63b66010cce307a8d0e0cb4ba546696799bc982100eb2a084d8ba0d2298be4031ee9be274ac56c4a54180db55f3c7d2a08ef23d62f32b77464cff82a20d207dc7d77088d48ac3be042b89b5474e5d3e01707df11c3fdace110f092303e87dbbf1fb68525eff8e9c6e3d2c707b1607df2825f9185347c8ea30bee636482051aac9a3186db603d47dc197b7f6b6641f8f04dc677a706ef7612b0319e64fc6054c264d352df72a8d0e5215e01b5e8df18a5c57f858ee2551ed283394b8a9ba8af2f2e5e2893af0b1eac66f7392bc5c03db6dd4ca92b79a14376f4708a9e02d2754869a5fad8c2053d04c23870d894b1f544bc24c52317b96c2cb3c48819dcef816df07139213c497e0ab81161b3edf503f2b0cadfff3a50ea9dc330014d31667bcaed035df0baf2e5f61d030c0990cb0632297193fd1388b3e1e92e14dfc50a3ba223014083e1b5b0dc441895eb44fb49fe1c05d0fae11eff6345c78a0f5ec196853cd23491bce4f19d4ca991243608a435e5237a4f42e2809912c934374d56394f5f43be0ac3849a53b5469ceed1885089c11cb32249426f9783e01a233d8bb9214b2156bd17e7e2bee7bb38520aad22aee9b7c1632703135717bba0f2f96df3af123c738c90bd19d8965bb21530f87ee8e8e0d3d1b080c456f910c499416cf83efa02a2f15f1a8fb49abc46f09456a809d1cb602dd97c45c8f35b1cd44c41a030d883d30764fd55953aa8c9d01a706cffea8a758bde4b13eb25338eafced8bfd2cdfe50d008a55849dc87554ad043e4598700c1c8909f1fe87ada76c4928a51c4a1cc4a70d4ffce0b1f0ab0aa1a71c8d8f59d3b1fb23a3293b55984d2f0bc0a9fbd1b5c2aecd8c50bd298d17f3940f287d128e62e09785a742f37ea7dca45bdb4e4aeb1885f4492f87799a30600051f8246b7b51fa9c67b2862019515e8bfc96177d185c55c69650e9fa4109ca4118f54cba2d2c425b4a59b60af44bcc0d183103185d7018f4e7835311a615e4162e6988e8722c752a221a87a4821fc2b7347cd18953e62a394972f913aea13a7af23a080800aee625613c3e82a45b1b788e023e6954c2f01d1cf0e6036c9a2ccf3e62c76eaf4795ff9f2546ae0347681aada949ac906bd3a1de1e07a9a62edba95110aeefbc36681894891a4a834826ff1a83cef05e707616fc89690fff9968798fbda3a53d84ef6c8a1a14e215ba888759a872c6ce00a5574c6de5eaabf613c83cf931a259230c371f0d99ec4716b8ef8901a254b12960c073f3dbdda8f9cab5a901cd8c0ee25127a43497f48e7d137d9113e3e3e6703db8aa3ddf0259121a747e3af4eaa99bd81bc89e8f01a9ec7ffef3d16003bfa686999882c2f04945dc1a23af1ee3454e20af05849310a51820069ad852446beb0315bc621a37a20db633b68659b4efc5e9dfcf9633ecc623d691dbccea398ec18581df9172eb1111f374974f1f455833dea61076684404d22b455a0cda3dd693170362e5709df55aabc08e74bd72bc667864f7d1e43d8ae4ba7131cc95584d0b0fd50e4468be82a9ecdbd22e196d47626ddc3aab7ed44f075e6b75123877609e5ccb0f06e880927d1d7ef767abb799e76335a6731719a0c45b970919652ec1345c6aeb3cba067490778aa13114dc3ace9e9c344059e00a41138130670c8aaa8e9caeb84bfd93ad31f17c8f8adf749f9952dc66992720ed9a7c418b7997c6b7333d17d625156c3401379625a57b221291f34b70d860cf19f7ab19fd6f368e5f8a5b6ae9cde87e77036e3a3419868e9da66c4586a15124dfb129a843552fb4ee6b547a330860e4853c1e6756166583bad1cbc0f1237b2cb83b3360b0e4cc99f573fa80b8ff45553e90ebf37e81d7e5e607e0588938e7688c35c074b450f658b799876b7e747f916e4b34e5c528ead2f62bd8b5fed53a3109018120998b9ed887ecff9f3b4f9bafb07aad375e2932572cc4dbfc74eb914a9312da02e733d82a0ee714c267539a6b098d2b4b3d5c72a78b94cdab5bfc68c211d416527daf7ed999906bb2ba6d6a12f721ac588d2ef19aa32ff39a3911e16934a67666bca132a0e2cef9145053becfa4cb0998ec5a101d15276d79029516d3091078d61fc8f4ac3a6329bf79474af2680c599507a9682f2416968e14b2d39b00000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x97deb7", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x1c497ac8ca436d3bdf10d78a0b639383046a81cbe079c6af5939c4e235a8cfa0", + "transactionPosition": 32 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002d0798", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x42b55c1", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002d07981962a9811a045b28e258205e3d639571a49f742b43f2f1c00218f4564577b58a3472c5e3d98a8e0d7838ec582019705c311b3d19f529f3c2161e7b3a08ffba8b20e0a434e7dcf4c04ea43f79345907808265626909a4ea50de70b5e4dfaeeb5b403aab71f3409491deb25fb3c4449f50fe2bc1a5537e4b2608e03546eaa9d29687514caf1220dcb30c1e835079e713548f6740668c61e52d683dc8b67070abd42b15a531ad8b40175096f86a2c8b2ec702e738164a93493f95163c8c21e7e52868bf0fe9c5b00f8990f3f7ceada05f43383b91396b367551323e598bbdab72e18520442cea081a512f194206860173141bde8125f3e07bb1cf4d5fd549d25a0d4c88ef18b36556d3bc244e193bdd09d2875d98ede100bede4df18c21c96fb3cd453a1f3f93a0fdef627cca54d991ca62939ab2da07ae3aedc77fc02f7adf0bc2ab8fc7a2dbdbbe43822a6614121339caa742d899a8cec7b24bbecb74223453a797fb13d2a02fa5d9e64bea767015e9b204e07f623f6a3fa48d963e1862f6b3621aace85bcf136cfefbd90d7da18acd89a80095c8c5d69e81fb87509d591346c2b64a4b00034d547ae2a2434e2581d941e66b13ee33c24e4c6855b89e714903fa8144a973bb15845b611cc4b35a31b9af98a9dc2008efd63b66010cce307a8d0e0cb4ba546696799bc982100eb2a084d8ba0d2298be4031ee9be274ac56c4a54180db55f3c7d2a08ef23d62f32b77464cff82a20d207dc7d77088d48ac3be042b89b5474e5d3e01707df11c3fdace110f092303e87dbbf1fb68525eff8e9c6e3d2c707b1607df2825f9185347c8ea30bee636482051aac9a3186db603d47dc197b7f6b6641f8f04dc677a706ef7612b0319e64fc6054c264d352df72a8d0e5215e01b5e8df18a5c57f858ee2551ed283394b8a9ba8af2f2e5e2893af0b1eac66f7392bc5c03db6dd4ca92b79a14376f4708a9e02d2754869a5fad8c2053d04c23870d894b1f544bc24c52317b96c2cb3c48819dcef816df07139213c497e0ab81161b3edf503f2b0cadfff3a50ea9dc330014d31667bcaed035df0baf2e5f61d030c0990cb0632297193fd1388b3e1e92e14dfc50a3ba223014083e1b5b0dc441895eb44fb49fe1c05d0fae11eff6345c78a0f5ec196853cd23491bce4f19d4ca991243608a435e5237a4f42e2809912c934374d56394f5f43be0ac3849a53b5469ceed1885089c11cb32249426f9783e01a233d8bb9214b2156bd17e7e2bee7bb38520aad22aee9b7c1632703135717bba0f2f96df3af123c738c90bd19d8965bb21530f87ee8e8e0d3d1b080c456f910c499416cf83efa02a2f15f1a8fb49abc46f09456a809d1cb602dd97c45c8f35b1cd44c41a030d883d30764fd55953aa8c9d01a706cffea8a758bde4b13eb25338eafced8bfd2cdfe50d008a55849dc87554ad043e4598700c1c8909f1fe87ada76c4928a51c4a1cc4a70d4ffce0b1f0ab0aa1a71c8d8f59d3b1fb23a3293b55984d2f0bc0a9fbd1b5c2aecd8c50bd298d17f3940f287d128e62e09785a742f37ea7dca45bdb4e4aeb1885f4492f87799a30600051f8246b7b51fa9c67b2862019515e8bfc96177d185c55c69650e9fa4109ca4118f54cba2d2c425b4a59b60af44bcc0d183103185d7018f4e7835311a615e4162e6988e8722c752a221a87a4821fc2b7347cd18953e62a394972f913aea13a7af23a080800aee625613c3e82a45b1b788e023e6954c2f01d1cf0e6036c9a2ccf3e62c76eaf4795ff9f2546ae0347681aada949ac906bd3a1de1e07a9a62edba95110aeefbc36681894891a4a834826ff1a83cef05e707616fc89690fff9968798fbda3a53d84ef6c8a1a14e215ba888759a872c6ce00a5574c6de5eaabf613c83cf931a259230c371f0d99ec4716b8ef8901a254b12960c073f3dbdda8f9cab5a901cd8c0ee25127a43497f48e7d137d9113e3e3e6703db8aa3ddf0259121a747e3af4eaa99bd81bc89e8f01a9ec7ffef3d16003bfa686999882c2f04945dc1a23af1ee3454e20af05849310a51820069ad852446beb0315bc621a37a20db633b68659b4efc5e9dfcf9633ecc623d691dbccea398ec18581df9172eb1111f374974f1f455833dea61076684404d22b455a0cda3dd693170362e5709df55aabc08e74bd72bc667864f7d1e43d8ae4ba7131cc95584d0b0fd50e4468be82a9ecdbd22e196d47626ddc3aab7ed44f075e6b75123877609e5ccb0f06e880927d1d7ef767abb799e76335a6731719a0c45b970919652ec1345c6aeb3cba067490778aa13114dc3ace9e9c344059e00a41138130670c8aaa8e9caeb84bfd93ad31f17c8f8adf749f9952dc66992720ed9a7c418b7997c6b7333d17d625156c3401379625a57b221291f34b70d860cf19f7ab19fd6f368e5f8a5b6ae9cde87e77036e3a3419868e9da66c4586a15124dfb129a843552fb4ee6b547a330860e4853c1e6756166583bad1cbc0f1237b2cb83b3360b0e4cc99f573fa80b8ff45553e90ebf37e81d7e5e607e0588938e7688c35c074b450f658b799876b7e747f916e4b34e5c528ead2f62bd8b5fed53a3109018120998b9ed887ecff9f3b4f9bafb07aad375e2932572cc4dbfc74eb914a9312da02e733d82a0ee714c267539a6b098d2b4b3d5c72a78b94cdab5bfc68c211d416527daf7ed999906bb2ba6d6a12f721ac588d2ef19aa32ff39a3911e16934a67666bca132a0e2cef9145053becfa4cb0998ec5a101d15276d79029516d3091078d61fc8f4ac3a6329bf79474af2680c599507a9682f2416968e14b2d39bd82a5829000182e20381e802204c7ab7b39b702b0f85ccccf1ab3be3cecfbcca6270f318e87139e54034536273d82a5828000181e2039220203e1051280d09281ef100474edd98804234de5c26e1f9b4f2fe44fc6b0f764b04000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2f02cf2", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x1c497ac8ca436d3bdf10d78a0b639383046a81cbe079c6af5939c4e235a8cfa0", + "transactionPosition": 32 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002d0792", + "to": "0xff000000000000000000000000000000002d0798", + "gas": "0x526a0ff", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000787821962ba590780ad996281f9ca4a29d9500e079f0250a570058f543c381fd8ff5207dec9eb29a21cf732e00a9eb5fc8ce8b92c68151f3ea5a8dcee1ce14acbc24ae6bf934d64e15815bc9f77f315e05c00432ebbadd65e756658ac51f723ab9fbc7f18d4ddef6b17d65d1d29f5c20bf130c8961f470eae522d4d7d4dc28fd743b26f887c9a9839228f2aa8617759e82bbf05b981dd0c2b8445502cb8b61250cb5b8d01c79b033712bd91da570437900ca7f3162e50b1761db4a01a33a1772e8e7650754c3f6a3ba979537cdf00b7754517d53fc5e18d882ae7117ba605ccf1d721e3ea5140a73690ee33308bf5a4fe2cd26c63ab034bfeb90f7df81b484f729a56493faf1040f97c2c85ec1020fd161cf9e0c8c1c065bfe31807b8784d5fd0f68c698f91229f700987a0ce6a73bd01fc4d460a72cfdd968493b612fa80e40a57b149e549d1efd97db9c8d1536af9b1e70fba2d395c7d50a514b2278efb333df538fe508f53d4cc292d2af34a41caf2eaa162222154d676730b10b10f9d118a064b8315418de4dfaaa8676c0d845e271c4f6d2f0a39db3f400434354a85d5fc8bc2bf7809da7623eef18779dfafa0e608c00871be55b43891059115ebbaddecb8b8d2862999e8b8803111718f8a23effe8ea2d666bed91ee98a3c36d6b89c9c90faf1576703b1e90b9b4164c1ab5c4daee51b26f1e556b4c2fac68125c293a77808e780c08bcfff3939ebd5eec5effa30f49d5860ca1bc890f610404430af070a43862d00ffad5de51d8701138d0cc46b856104fd792a08ba470d441992e282877b7ccf31844480ae2b679357e12c96b95620b64bcf38b27944f9ee4bf8768c9966973d3f40f6575e45a2dec3509de997e50445251a16608f299e2d8b3e71618942a15fc35ea5eb48da6a6600f1fffbf7e5b953b27f857e9773ca7ffc9dbc14ec275e60471c2bd60d07b74585bc5a34b93f36658600b57bcd4901d436374891e76826790619d555ca2dd736aedc7150f3f237345e77508e8dc3dc4310b36cad0af9e65a7a105c4b688eeca266d381d70f589348de350d0f19e7560a321282e17f954a6196322945b60efc03e30afef8d77d3fc1dd2b37f0d412d070edeed492eceabc80e650839f7b0e35ca378c689644dcc4245e08fc2fb1b6005fcce45165107ef7595ac4238af11527076d10e0fe0d0f09e7cacf37a60d9f7ed0259573911c85967b143e472b1079e14ce53f8bc32c7379a667792baca4475d11ffca642f2d780b1d14fef9e9334679d2a12e88b20870a1b7c6320ea9825b4e43cb92c048d3e2380f28232eaceac0872bc1f7a105fa8791d91832003b25e6cc7a9c7b4aeb3110a2a5ee2c973eb5a80b91a73fa6f229913b4105fba4130b66fee5180beca7baa641a2c4e5c2e6cf634bc48d90ac29df2255b199ed35668aaf0da3623882bc8205f2ca636ca57d421362ecb5ef30116a0116cd11ed69d8370738c0189ffdc4643ccda19121bd491092f5ae8a4fc41011c632a77ccd63ce37ce06ea0b6f1fe85e8708752916d3e4452933112c24c0cfdfd277d6f8380faba68b7895890a935554aa91a5b36e1530c9cf70dcece8ae88e0c5a6a57393b525df9776e959c1e2c3dde4a29bf9fb75fd97c09aeffbe6db0470e4a2643e67a3b853a8b316a486746132475b086318fb4544aa997ea1b029277e735e1b5594c143811c2b516042ac4dc03ca4fbe35b8ebaa09a29077e3781855a0c8d2145fc8265ace6acd485b54d45637233c4b802c20909601743f4ec91a511dd1c9a40151c1cee99ace0e96fc9899a95167bf257a296870409f20db7cf21d196c17d3b129a77ac87501ed12703bd1f3099f7c1a13522148184103faeac28ab678d0f0a147ecf4df1f500be9247273f7509964b9030579626584f64f121fa58c2553f3f9b4ce40f222fd6f0e6ce54474094db8dedd92f9fd120ab7e9ac7cd8b5c54380cecbf1aaed48e2168c1d6103c663abbadc136128e5ea5d92a9f20b9829c623d416a7bf3fafefacb698389f86beb29622dcca8a1196f72bfd67519413bbd81d5301435d8a591acbb2ad327ecec80ddda5c4a5ab366565db6b8c5fe50e2149a23d54c3969b397ca76981b100d57242378214addd615783e11d4be7c9581bdedafe3ea2d75960bb1f45337e4a8468ad1392e72b77bb73f9f72878441834c67488d6d094731df676f4f51ec0deed3c2d0669a02accd31a7bedd6fa9832cc62038aaf19253b18e9fbdacd86b15cfef36e9a6d23de4ff82b8ef728903b755295073b6c082b64793278b973a0c3059ace8e6e374fd0a4c0c81e8c40acfb96043d56e361c1c51542d0eb119cb806c19291c07e70e5ec730c251c32dfde23d16db87e8abc640fe068234cd9cc8327c68102028218fa8e804694dce870992a7a0b93763c76da0dc8973872c1bec2eacf36350d6fc6ce3e83380539c5c28ff24eb52827157e585468cc0a89a934dbb6b15c5bc889979f6ebddc9fb7e1fc68d5ddeacf15de8fa67f741acb10f452e47d04592810872d8d48ca85c4aea240fe825d6ccaec9faac8ba8a8b91d60a645e0656da4c0dc0cde1bcd1218c301e16881f782d91a57aa5e063c03762184110973924c9ecf4ce91c56691500f9251d3e528dd75bdde769d4f8e04aa7330362cf1bf2f1029ef25f8eb27059fb9d30d438d77cf63f364baf4744d243acb5966b79c5c35f74e8695c5b58469d00000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x97d42b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x8ade285038462b2ece31e2ee0ff1af1448e9cf8e07520f04618b5b26436a73af", + "transactionPosition": 33 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002d0798", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x4bfa5bf", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002d07981962ba811a045b2bd658200614498463ce77b23bbf18bfd324b0fd4d5b102634806020ccff3f8c78f955b158208eb133c33fa726942d694d350b629cf534788e290162780485829b4a70160907590780ad996281f9ca4a29d9500e079f0250a570058f543c381fd8ff5207dec9eb29a21cf732e00a9eb5fc8ce8b92c68151f3ea5a8dcee1ce14acbc24ae6bf934d64e15815bc9f77f315e05c00432ebbadd65e756658ac51f723ab9fbc7f18d4ddef6b17d65d1d29f5c20bf130c8961f470eae522d4d7d4dc28fd743b26f887c9a9839228f2aa8617759e82bbf05b981dd0c2b8445502cb8b61250cb5b8d01c79b033712bd91da570437900ca7f3162e50b1761db4a01a33a1772e8e7650754c3f6a3ba979537cdf00b7754517d53fc5e18d882ae7117ba605ccf1d721e3ea5140a73690ee33308bf5a4fe2cd26c63ab034bfeb90f7df81b484f729a56493faf1040f97c2c85ec1020fd161cf9e0c8c1c065bfe31807b8784d5fd0f68c698f91229f700987a0ce6a73bd01fc4d460a72cfdd968493b612fa80e40a57b149e549d1efd97db9c8d1536af9b1e70fba2d395c7d50a514b2278efb333df538fe508f53d4cc292d2af34a41caf2eaa162222154d676730b10b10f9d118a064b8315418de4dfaaa8676c0d845e271c4f6d2f0a39db3f400434354a85d5fc8bc2bf7809da7623eef18779dfafa0e608c00871be55b43891059115ebbaddecb8b8d2862999e8b8803111718f8a23effe8ea2d666bed91ee98a3c36d6b89c9c90faf1576703b1e90b9b4164c1ab5c4daee51b26f1e556b4c2fac68125c293a77808e780c08bcfff3939ebd5eec5effa30f49d5860ca1bc890f610404430af070a43862d00ffad5de51d8701138d0cc46b856104fd792a08ba470d441992e282877b7ccf31844480ae2b679357e12c96b95620b64bcf38b27944f9ee4bf8768c9966973d3f40f6575e45a2dec3509de997e50445251a16608f299e2d8b3e71618942a15fc35ea5eb48da6a6600f1fffbf7e5b953b27f857e9773ca7ffc9dbc14ec275e60471c2bd60d07b74585bc5a34b93f36658600b57bcd4901d436374891e76826790619d555ca2dd736aedc7150f3f237345e77508e8dc3dc4310b36cad0af9e65a7a105c4b688eeca266d381d70f589348de350d0f19e7560a321282e17f954a6196322945b60efc03e30afef8d77d3fc1dd2b37f0d412d070edeed492eceabc80e650839f7b0e35ca378c689644dcc4245e08fc2fb1b6005fcce45165107ef7595ac4238af11527076d10e0fe0d0f09e7cacf37a60d9f7ed0259573911c85967b143e472b1079e14ce53f8bc32c7379a667792baca4475d11ffca642f2d780b1d14fef9e9334679d2a12e88b20870a1b7c6320ea9825b4e43cb92c048d3e2380f28232eaceac0872bc1f7a105fa8791d91832003b25e6cc7a9c7b4aeb3110a2a5ee2c973eb5a80b91a73fa6f229913b4105fba4130b66fee5180beca7baa641a2c4e5c2e6cf634bc48d90ac29df2255b199ed35668aaf0da3623882bc8205f2ca636ca57d421362ecb5ef30116a0116cd11ed69d8370738c0189ffdc4643ccda19121bd491092f5ae8a4fc41011c632a77ccd63ce37ce06ea0b6f1fe85e8708752916d3e4452933112c24c0cfdfd277d6f8380faba68b7895890a935554aa91a5b36e1530c9cf70dcece8ae88e0c5a6a57393b525df9776e959c1e2c3dde4a29bf9fb75fd97c09aeffbe6db0470e4a2643e67a3b853a8b316a486746132475b086318fb4544aa997ea1b029277e735e1b5594c143811c2b516042ac4dc03ca4fbe35b8ebaa09a29077e3781855a0c8d2145fc8265ace6acd485b54d45637233c4b802c20909601743f4ec91a511dd1c9a40151c1cee99ace0e96fc9899a95167bf257a296870409f20db7cf21d196c17d3b129a77ac87501ed12703bd1f3099f7c1a13522148184103faeac28ab678d0f0a147ecf4df1f500be9247273f7509964b9030579626584f64f121fa58c2553f3f9b4ce40f222fd6f0e6ce54474094db8dedd92f9fd120ab7e9ac7cd8b5c54380cecbf1aaed48e2168c1d6103c663abbadc136128e5ea5d92a9f20b9829c623d416a7bf3fafefacb698389f86beb29622dcca8a1196f72bfd67519413bbd81d5301435d8a591acbb2ad327ecec80ddda5c4a5ab366565db6b8c5fe50e2149a23d54c3969b397ca76981b100d57242378214addd615783e11d4be7c9581bdedafe3ea2d75960bb1f45337e4a8468ad1392e72b77bb73f9f72878441834c67488d6d094731df676f4f51ec0deed3c2d0669a02accd31a7bedd6fa9832cc62038aaf19253b18e9fbdacd86b15cfef36e9a6d23de4ff82b8ef728903b755295073b6c082b64793278b973a0c3059ace8e6e374fd0a4c0c81e8c40acfb96043d56e361c1c51542d0eb119cb806c19291c07e70e5ec730c251c32dfde23d16db87e8abc640fe068234cd9cc8327c68102028218fa8e804694dce870992a7a0b93763c76da0dc8973872c1bec2eacf36350d6fc6ce3e83380539c5c28ff24eb52827157e585468cc0a89a934dbb6b15c5bc889979f6ebddc9fb7e1fc68d5ddeacf15de8fa67f741acb10f452e47d04592810872d8d48ca85c4aea240fe825d6ccaec9faac8ba8a8b91d60a645e0656da4c0dc0cde1bcd1218c301e16881f782d91a57aa5e063c03762184110973924c9ecf4ce91c56691500f9251d3e528dd75bdde769d4f8e04aa7330362cf1bf2f1029ef25f8eb27059fb9d30d438d77cf63f364baf4744d243acb5966b79c5c35f74e8695c5b58469dd82a5829000182e20381e80220de75946bac8d0de4cc507110766a5f17cc19b30c8d0b0a55cf1b2b9afa65c939d82a5828000181e203922020d3ed4d594376bea8a01dd6414cca63e8f5165f21ae5d3ce86714ccca0f03f504000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x3662a3c", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x8ade285038462b2ece31e2ee0ff1af1448e9cf8e07520f04618b5b26436a73af", + "transactionPosition": 33 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce375", + "to": "0xff000000000000000000000000000000002ce3b6", + "gas": "0x32202fd", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708195c92d82a5829000182e20381e802209d8f62a071341cd01d80033faa7c9ef07f7e1d8fbe534b0f811772b3fda0b23f1a0037e10b811a0454b28f1a0047eb4ad82a5828000181e2039220203c71b16224218e29bc29672e8db69ba0c012113a8b5d8cf2220c261f2a0b4a3d00000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x222e801", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x0ab1e62d5a67bd84621c1419f136c59115731ca47ba1da49e589760d38c95b84", + "transactionPosition": 34 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3b6", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x3169cd6", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x0ab1e62d5a67bd84621c1419f136c59115731ca47ba1da49e589760d38c95b84", + "transactionPosition": 34 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3b6", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x305d78e", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x0ab1e62d5a67bd84621c1419f136c59115731ca47ba1da49e589760d38c95b84", + "transactionPosition": 34 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3b6", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x2f3c21d", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a0047eb4a811a0454b28f0000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x489dcf", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e2039220203c71b16224218e29bc29672e8db69ba0c012113a8b5d8cf2220c261f2a0b4a3d000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x0ab1e62d5a67bd84621c1419f136c59115731ca47ba1da49e589760d38c95b84", + "transactionPosition": 34 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce375", + "to": "0xff000000000000000000000000000000002ce3b6", + "gas": "0x4abaf3a", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782195b555907809884d69eeaf1026b549be04d95a7ba703a48494ebdf41d17bc31dab9d7c1a4eb9b8cb0b88cd2e55617338b8c5a58d8feb896df6dfbac66c6fbc56fcc0f17a40fb03339bdbeaa70f0c1de65480e3ddddf1ae71d64dedfd200c263962df45b20761265f747786fac48c4e890695c371113ec4175b2234fc81296ac9116c9601332b99d0206321e58b16bb0d7a69e49a26a96ee1f0b811031d79fd51dd6d0e4498cc7f65e385df4287d72ed0a4a4dbe019a545abb5ebb5f170058a18293f41ed1228286e4110fdf37e6e04aaa156bcff40c7038ed380fd8eaddc864faf7813ea3349785566d1f911b529990df437c2a4f929622ce6571923fb7087485996562785a046a44d48847dd949522a274d3fbef4bf000b9c9979d64df5d77c0efa6c5e4680e457e02379f7d9da2a35de7dce57aecdbc696d4f2cc02d802117a371bd456af945be6d3e9e2146b25c30b7da50ce4dea9055fd1c429ed8ff3b6a7d14f16fe20d8f9ec8802abd3b8f61083c1f05cf3c31369d29938ab21e1a0aefc1f5f2eef4c8ccf3cd9d89a930697c9c9e0042ac63288dc70b9bd5d86e36fee93d5e2eef36a89e3db13cd6be53c37649a0e287ad71caaa0cd08dc46b5c93952d6a9540408017688facad9f4e7c94a24f80a7e8abe7352222a51a82c80a5cf3b0d11f1b071d20b5c67f2c120b8a7f722abc21fab82893fbc206501e4a5b8fd3e881ab39b89ec931bbf3dfc2f9ffa139c9a518552ffa4b9547d88a53cc7947f6c8145c33ae6feb9bfa3d7ed81a7df09827e16033ef88d6990fa158a56750d1f554de9786bb2fa83fb593f11b7747633ac9978c6991256e5fb0ca9a7a70b26fad4d27baa338e8eb3ef2cc3321361a7b44b84b5a5093dc6a04883c5843d2125797cf4be06297cf765eab5b11a2d2c5f3ade90a787ea41b236e1c4291bd547de7c16ed4046576ba515ba1786d16a9d7e55ba802c43702e1a7f4eb842b4a0a96772ed3ccf9a8d53283d5f2a31fde784532cfe8cff41802a72a3255f6998ab70c87afe02271b5ca1ac137350f68636af6a540f0bfaccf2074b0e3b562d7aa01af153d118c2375f053e916152723a1dc3bbc16c0d8fcf9cd2e494f71540f0610e12a6f871a3848b4b96fb73d259e4108ce519dc0fb10ebf6a83aa9fb407a9578cb12ae2eebc43362358255c093c66dfc22d6a2340d292b41a6a303c116579537134ee370eeec1ba522f131d702130b5f3a49defa3941176ad0789abeee3becc738ecea0140ef69f574cf0bfa214a9a4a9bae8fc78831207cd8784c917b967b502ab4a36fa814aa55e50915ce6adf77fba178c69d94ccd5b949a7af66e74de04232563e2fdef2338b83cad860e8b29259888c35159f8e64bfa34ddd87b32450c04770f20e6db09c137fb9016382987673cd00c284cffc207b47c83f6d6e290c287d4a1567d94a012043c4a51d3403467b4a21a7c6276f7239dde0966df4c194be5951b3c2f99480ab17f11463d9625bf27656d0b8285d3dadbdd73ecddaae62c825f621bdafcc7af4370d13be788e472b2975d7d55689341f9baa6e149e46b6826ca829a349172faa749f97f3d6ecbb7af40de6b28129aed0ff96febce04bb4ee7be532bda99fbede436a027b1c665bfa7675607fb57a5372ad9152ce057aa429ddf38fa3c9bdd8fd6206c49ffa21fe8f295eba5f54ab9fa4d609075877e8502fca77deaacc884564262bb091c2673a1ec8799887adcfb4e704127113eafa4c3d9ff3e65999049c92d2807c3799be3393ab73d781f5b8e7c20ec0c06428765120f54fd38e51b4d1e1c20db2e34f516ff69206330cb7acbca9eacb1ca8150b1cdc5389e70ffdc7d51957acef211a7eaa79fd9f77386681dc5e38f49dfc57f3825574b15b15656fb0945c7a681fa170a9710061fd7caf7abaa47cae606ac96cb7f1fbe0068c2e8f921be971d95596059a93fb8deeba5d4a62cb47e8dc1dce320e84038526c7c060e9d417f76f0f73bcb76f75c645230e5a90700c828274e2014c7518d2e1ddd46aa16c747153524b5e96cdebcb298c811e54753b3f43334537d058ae0dd56e20305133cfa46a5cc7006fbbea66a8b984cc8544ea5a76f0c2ed9791bd26ea64647d4eb00661f3e95693b61f2bc7ff947076ede2bb5091c147ab4909cb5621327875ba61e68975ee5e03d45e8ddbaae869db24ff4f70adda4dbe7172e4b36c6964a0ace19a86e08fb21568608cd852cab0449596ce4b9e49db0cd51a50f2b1ca224b9c96b8c1cd106aeb48c98ef00715f28fada0f7ba49177a3d335b7d009cbd88d6efefa71054423c1ac2c035ccea176b1042ee93168ca564503be614e44cabe8e122f183ee2cd6f49ad5413a4baa3213bc12c22518c6e87dcc93894b68ae158744f9f8e6d8e85a2bd79d8ffec811994db429726802997f02547c9adc375c68b458b373e9b8f78a7929fe22894ac92778dbc2efca07d79ff57b299a85fe25de4a6d01408bfe07dfef673cee97649565b439306e36bafc9baa89916b7ddb6dee4fc1912aae243fa0aef0c2b63e1850f22049737a5f8d2dfd273da4036dcfb1e9bcca5afa48401ac3823b00b4e5b16793a859abdf9da123b1ede0a42cb2c9005550a95039882814e58f6baa6311999faab45021217ab8b95e78306b6a172ec0c8ede1d7ce86879ff48851d8f8b03d000d04532155db191b8368833e7917bcb257ffcf64f821b00000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x699ac9", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x5391369997f0d1335d275d9f74b26e1b6633b7949375e065d75ecf3e9ee1b478", + "transactionPosition": 35 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3b6", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x472f623", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002ce3b6195b55811a0454b2585820ce4b423b5779ab9911bd426632a925b6a83466ce6e076590b106211eaffebb5a582047a51e4e53644247c4ad14e95dcf51070bc8748691de862db56543447a9993eb5907809884d69eeaf1026b549be04d95a7ba703a48494ebdf41d17bc31dab9d7c1a4eb9b8cb0b88cd2e55617338b8c5a58d8feb896df6dfbac66c6fbc56fcc0f17a40fb03339bdbeaa70f0c1de65480e3ddddf1ae71d64dedfd200c263962df45b20761265f747786fac48c4e890695c371113ec4175b2234fc81296ac9116c9601332b99d0206321e58b16bb0d7a69e49a26a96ee1f0b811031d79fd51dd6d0e4498cc7f65e385df4287d72ed0a4a4dbe019a545abb5ebb5f170058a18293f41ed1228286e4110fdf37e6e04aaa156bcff40c7038ed380fd8eaddc864faf7813ea3349785566d1f911b529990df437c2a4f929622ce6571923fb7087485996562785a046a44d48847dd949522a274d3fbef4bf000b9c9979d64df5d77c0efa6c5e4680e457e02379f7d9da2a35de7dce57aecdbc696d4f2cc02d802117a371bd456af945be6d3e9e2146b25c30b7da50ce4dea9055fd1c429ed8ff3b6a7d14f16fe20d8f9ec8802abd3b8f61083c1f05cf3c31369d29938ab21e1a0aefc1f5f2eef4c8ccf3cd9d89a930697c9c9e0042ac63288dc70b9bd5d86e36fee93d5e2eef36a89e3db13cd6be53c37649a0e287ad71caaa0cd08dc46b5c93952d6a9540408017688facad9f4e7c94a24f80a7e8abe7352222a51a82c80a5cf3b0d11f1b071d20b5c67f2c120b8a7f722abc21fab82893fbc206501e4a5b8fd3e881ab39b89ec931bbf3dfc2f9ffa139c9a518552ffa4b9547d88a53cc7947f6c8145c33ae6feb9bfa3d7ed81a7df09827e16033ef88d6990fa158a56750d1f554de9786bb2fa83fb593f11b7747633ac9978c6991256e5fb0ca9a7a70b26fad4d27baa338e8eb3ef2cc3321361a7b44b84b5a5093dc6a04883c5843d2125797cf4be06297cf765eab5b11a2d2c5f3ade90a787ea41b236e1c4291bd547de7c16ed4046576ba515ba1786d16a9d7e55ba802c43702e1a7f4eb842b4a0a96772ed3ccf9a8d53283d5f2a31fde784532cfe8cff41802a72a3255f6998ab70c87afe02271b5ca1ac137350f68636af6a540f0bfaccf2074b0e3b562d7aa01af153d118c2375f053e916152723a1dc3bbc16c0d8fcf9cd2e494f71540f0610e12a6f871a3848b4b96fb73d259e4108ce519dc0fb10ebf6a83aa9fb407a9578cb12ae2eebc43362358255c093c66dfc22d6a2340d292b41a6a303c116579537134ee370eeec1ba522f131d702130b5f3a49defa3941176ad0789abeee3becc738ecea0140ef69f574cf0bfa214a9a4a9bae8fc78831207cd8784c917b967b502ab4a36fa814aa55e50915ce6adf77fba178c69d94ccd5b949a7af66e74de04232563e2fdef2338b83cad860e8b29259888c35159f8e64bfa34ddd87b32450c04770f20e6db09c137fb9016382987673cd00c284cffc207b47c83f6d6e290c287d4a1567d94a012043c4a51d3403467b4a21a7c6276f7239dde0966df4c194be5951b3c2f99480ab17f11463d9625bf27656d0b8285d3dadbdd73ecddaae62c825f621bdafcc7af4370d13be788e472b2975d7d55689341f9baa6e149e46b6826ca829a349172faa749f97f3d6ecbb7af40de6b28129aed0ff96febce04bb4ee7be532bda99fbede436a027b1c665bfa7675607fb57a5372ad9152ce057aa429ddf38fa3c9bdd8fd6206c49ffa21fe8f295eba5f54ab9fa4d609075877e8502fca77deaacc884564262bb091c2673a1ec8799887adcfb4e704127113eafa4c3d9ff3e65999049c92d2807c3799be3393ab73d781f5b8e7c20ec0c06428765120f54fd38e51b4d1e1c20db2e34f516ff69206330cb7acbca9eacb1ca8150b1cdc5389e70ffdc7d51957acef211a7eaa79fd9f77386681dc5e38f49dfc57f3825574b15b15656fb0945c7a681fa170a9710061fd7caf7abaa47cae606ac96cb7f1fbe0068c2e8f921be971d95596059a93fb8deeba5d4a62cb47e8dc1dce320e84038526c7c060e9d417f76f0f73bcb76f75c645230e5a90700c828274e2014c7518d2e1ddd46aa16c747153524b5e96cdebcb298c811e54753b3f43334537d058ae0dd56e20305133cfa46a5cc7006fbbea66a8b984cc8544ea5a76f0c2ed9791bd26ea64647d4eb00661f3e95693b61f2bc7ff947076ede2bb5091c147ab4909cb5621327875ba61e68975ee5e03d45e8ddbaae869db24ff4f70adda4dbe7172e4b36c6964a0ace19a86e08fb21568608cd852cab0449596ce4b9e49db0cd51a50f2b1ca224b9c96b8c1cd106aeb48c98ef00715f28fada0f7ba49177a3d335b7d009cbd88d6efefa71054423c1ac2c035ccea176b1042ee93168ca564503be614e44cabe8e122f183ee2cd6f49ad5413a4baa3213bc12c22518c6e87dcc93894b68ae158744f9f8e6d8e85a2bd79d8ffec811994db429726802997f02547c9adc375c68b458b373e9b8f78a7929fe22894ac92778dbc2efca07d79ff57b299a85fe25de4a6d01408bfe07dfef673cee97649565b439306e36bafc9baa89916b7ddb6dee4fc1912aae243fa0aef0c2b63e1850f22049737a5f8d2dfd273da4036dcfb1e9bcca5afa48401ac3823b00b4e5b16793a859abdf9da123b1ede0a42cb2c9005550a95039882814e58f6baa6311999faab45021217ab8b95e78306b6a172ec0c8ede1d7ce86879ff48851d8f8b03d000d04532155db191b8368833e7917bcb257ffcf64f821bd82a5829000182e20381e8022034ec77824ca12b84726221d0f555f3f2259556b034481de7444a5c4aa8bf1c50d82a5828000181e2039220206228c3dd993b0b2ae7a15517e751ab3c3eccbdbf66fae8352ca91edbd307b200000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2f32441", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x5391369997f0d1335d275d9f74b26e1b6633b7949375e065d75ecf3e9ee1b478", + "transactionPosition": 35 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002d0717", + "to": "0xff000000000000000000000000000000002d071d", + "gas": "0x465036e", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000787821915ed590780855ab01ab68d7f7911622a692d9f5e153cd7fa0c876e34bdc0e919b9dad348d36a5ca7da172d7861dc9d374691ff78598f681fb630f618d5ec6dd97e4d8a4e0fdf3f470e0a270b498470e6077d62ee727c3befcea4bd75f78fd5e2baaef1154100a718f8cdf3f51606b032345be315e76db25515c551449554df8986c672cc635509291c62e177d32302c0e4786c892890efc83438b702777075fbefdf555bed7bb2247ccdbf5180e39269a95e03be6be89dc54665ef8739adbd1deb19b654a2a38cbfbe57591f81c1270883e1cf67fb9aa629166cc561ba617833a693a205500021a0cd11a4819df33dbab56d16b9c78963c2673b2c7a4ac8de6d05ef318cf273434c9a2abe2e0f052e1862d2fc1e6e61f3edb2402094f81449ce1dc7be7e320a53970a2da7e93ce09803d02b5d281d2a903e95d032a668d2b1f841f6ba1f6929e8ba6c1bc2b5529fa2ae454358fd6a8ae66ebcfae4fad3caf51d07ccac6b7eb6e0cce37e5f33b934b9f3804572d36e8890793e24685e0824732a9303cae07aa3cc5acf931554fa5a27cbad77cbd038aa052d7f480450aefd205ced4eb9061e7284158d0885a359c90ec391a13b7dcdaf1813dbfdd47dc891733aba9cac4783f3ddcdc48cb6b1921273cd611b6fad160a3fe55116777fcc891e6889bfe15dbc00a040dfd74a3d22cb5e3be517f90ff919d15788b29c50d80bf410e92c7ec237f0612f93184c4e0da45f0b200ab37719b02a6d62d04d153c2db8a667dd2dedfc82351d9337db03a47cc8725600b06867a98a469798098624a596ceedc82cd48f86a62dc8eb7f7e99826b9631a85d6fd16d82cd6d71a4688eee0423c4bb7b3edf2486896cff4d406aa42fcc63f0c43aafa8ec1861ba4d5e518d898f9952017faa76b718ae54e2a09bd55e3ddc6330d3e01496b8e16284d5af1c22adb0869974620dc3e4391b2fb94e86c25a724d51fea1fd2eaddcdb455eabfd8243e1e5433802bc68b8aa42651290ede7c143442f2dda975d26c0d4826866e1087526d18a250b297cea211cb48f13ad95b7d2d2f8828bdd07ca84124c296536e2cd5cb4dd61a1ac2f544e079c716bf706216cb708dbc5fb8230e8898b1bf17e822e198a78d7ea9d7d704bf8f92c0190075422deb51286b5f282571527ba28e18abe3ccc5bebccba440c9fb1de24b5c1104584082f2109f5e499cba9d3dae4f57c0302d38b41d10f6f0f108678011f025affb7a754c400b85cc54aace92ae21baea706d8d76580c57199f7a64cfdf087a38982597b3199af6ca2900ee6652fc62340cc1d60157e7fc31b2c474ae9938c0db0c31a3edb7ad52f086c299551fd5c60b1b6231281eda99598c5a8a9fab4c6b523c25d03415c071d9cfc252129d2fa2319cb9af8525aeff2501cfb25d443952f1f3147bfc1fda017550abb58ebeb17b9327c2c86d059cf04cb2d29966fa689d9e871c45e32f16f322655f5f5556e55bc1a4ae33d68ce15235816886fe7ce919935dd497bfb829c8fef3b22c8e5a1e0bb3e446bff9d0b458dd7531778d144dfd38de1a220e6cda4da4708930f8265d4db9a3fb8a73ae369fa128d49181a04908fc9aed5cb5c6e55caf4786d878ba601cbf32820939679af0c8a99b4fc33d5b343dc3d428258e9a802ed92d6ae7bd6b874198cfbe168738536975ed78666b6cdf8d3155990c913aa782e39d9d6feb1229b4b743e32a2ba22cbaf681945b9eaf70e3e49ec02a3caaf478c3cee83a96c775823d02976408c0633c100b27f8cca832ba01e4b8f9a50d3eb8295abdec2d1a06e4aac519e0ba2c5c16779f01c4dbf6217a55ac9125b4fa5e940fb50eff805cf6aeca0435a52fdf674808e088670c7efbd9c8cf59480dad35b7279c25e0b4a7441e169d78ed79aae7a48f29c98669cdefc6ebb71f66fcdbe1f1132adb2ae7dc3a714a2cb4a4f5865d1ec9ae4c2930511b9f9a16e220ee2afc51d056c7b69e8bcdb3a7ef8e9c264a499c6d1839f4c6161859ab00e06795b03d33a13cbe6e025a7c5ed1b3d6044b714b25ef4228841cb1326e9a198fec9eaae849f1571d7a988f832ab45d6a05106344eb0529b07b4649bdd0592e602e1b2a263ef675e2fb5250d0fc12bcc1482a9102568c9379611cf7ced996cd9a575836ead5474162a2a93c23888edde35d8fe83d4d4c1306feee83f8c39c4f9ad788e5687f67228715d99f512b40ded4a799d620d81c858dd5126a69dfac4864496cc980004071a578e9004bdce44ea1371f6acfadd0b3e1afa6a293e81eb3ab08a58958a5be5d0d5a9347e0b7968fb694d200078e991c2666bc35c2d27000427c6d9d3390ab4c1db1a92554f2891a06e196438556a18b8e861d815a8f1c1bb5d854fb8a06b983efff0ce867571d9d6d2c57315407adbd16ef8874b27f8813a3f8f8729705fce0084eaf6387ecc87a23bac38b25eabdd4067558fa08305e4a349aa2549f25217d5aa7f4246ca354196e56d51032b673f351121a217fbea67c47215c397ad20729eb402e188e616eecfbd8397f67a33c14eaa247741331d54e4d3de3e8755a09ae65f1e1b5e83779f03ade05a0076fa1272324c4ddd2caa3ef289038d11e55d5d6f8f47262d5b441c21930e5d718556d13350cb0153688ca40083ce5d8a6cfbda9f072f0a096806d16c89e721b86e7f8b0a7a26b91aef4164a2581db740dd88536c967ed71cf12f22794ad66e00000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xa3a5a2", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xe01589cddd43f6bceff20e5ae7715922ddb41b97e88b5e4520fa355684aea943", + "transactionPosition": 36 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002d071d", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x3f23511", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002d071d1915ed811a045aacad5820daf497f015b8511702381fed6091635b036cdaf22a594e43cd2d97f95eb90f6b5820cdfd7ccb0815a58ba76c8db18a2801a5fdf6e4f7bffebc8a48086b5769d838f4590780855ab01ab68d7f7911622a692d9f5e153cd7fa0c876e34bdc0e919b9dad348d36a5ca7da172d7861dc9d374691ff78598f681fb630f618d5ec6dd97e4d8a4e0fdf3f470e0a270b498470e6077d62ee727c3befcea4bd75f78fd5e2baaef1154100a718f8cdf3f51606b032345be315e76db25515c551449554df8986c672cc635509291c62e177d32302c0e4786c892890efc83438b702777075fbefdf555bed7bb2247ccdbf5180e39269a95e03be6be89dc54665ef8739adbd1deb19b654a2a38cbfbe57591f81c1270883e1cf67fb9aa629166cc561ba617833a693a205500021a0cd11a4819df33dbab56d16b9c78963c2673b2c7a4ac8de6d05ef318cf273434c9a2abe2e0f052e1862d2fc1e6e61f3edb2402094f81449ce1dc7be7e320a53970a2da7e93ce09803d02b5d281d2a903e95d032a668d2b1f841f6ba1f6929e8ba6c1bc2b5529fa2ae454358fd6a8ae66ebcfae4fad3caf51d07ccac6b7eb6e0cce37e5f33b934b9f3804572d36e8890793e24685e0824732a9303cae07aa3cc5acf931554fa5a27cbad77cbd038aa052d7f480450aefd205ced4eb9061e7284158d0885a359c90ec391a13b7dcdaf1813dbfdd47dc891733aba9cac4783f3ddcdc48cb6b1921273cd611b6fad160a3fe55116777fcc891e6889bfe15dbc00a040dfd74a3d22cb5e3be517f90ff919d15788b29c50d80bf410e92c7ec237f0612f93184c4e0da45f0b200ab37719b02a6d62d04d153c2db8a667dd2dedfc82351d9337db03a47cc8725600b06867a98a469798098624a596ceedc82cd48f86a62dc8eb7f7e99826b9631a85d6fd16d82cd6d71a4688eee0423c4bb7b3edf2486896cff4d406aa42fcc63f0c43aafa8ec1861ba4d5e518d898f9952017faa76b718ae54e2a09bd55e3ddc6330d3e01496b8e16284d5af1c22adb0869974620dc3e4391b2fb94e86c25a724d51fea1fd2eaddcdb455eabfd8243e1e5433802bc68b8aa42651290ede7c143442f2dda975d26c0d4826866e1087526d18a250b297cea211cb48f13ad95b7d2d2f8828bdd07ca84124c296536e2cd5cb4dd61a1ac2f544e079c716bf706216cb708dbc5fb8230e8898b1bf17e822e198a78d7ea9d7d704bf8f92c0190075422deb51286b5f282571527ba28e18abe3ccc5bebccba440c9fb1de24b5c1104584082f2109f5e499cba9d3dae4f57c0302d38b41d10f6f0f108678011f025affb7a754c400b85cc54aace92ae21baea706d8d76580c57199f7a64cfdf087a38982597b3199af6ca2900ee6652fc62340cc1d60157e7fc31b2c474ae9938c0db0c31a3edb7ad52f086c299551fd5c60b1b6231281eda99598c5a8a9fab4c6b523c25d03415c071d9cfc252129d2fa2319cb9af8525aeff2501cfb25d443952f1f3147bfc1fda017550abb58ebeb17b9327c2c86d059cf04cb2d29966fa689d9e871c45e32f16f322655f5f5556e55bc1a4ae33d68ce15235816886fe7ce919935dd497bfb829c8fef3b22c8e5a1e0bb3e446bff9d0b458dd7531778d144dfd38de1a220e6cda4da4708930f8265d4db9a3fb8a73ae369fa128d49181a04908fc9aed5cb5c6e55caf4786d878ba601cbf32820939679af0c8a99b4fc33d5b343dc3d428258e9a802ed92d6ae7bd6b874198cfbe168738536975ed78666b6cdf8d3155990c913aa782e39d9d6feb1229b4b743e32a2ba22cbaf681945b9eaf70e3e49ec02a3caaf478c3cee83a96c775823d02976408c0633c100b27f8cca832ba01e4b8f9a50d3eb8295abdec2d1a06e4aac519e0ba2c5c16779f01c4dbf6217a55ac9125b4fa5e940fb50eff805cf6aeca0435a52fdf674808e088670c7efbd9c8cf59480dad35b7279c25e0b4a7441e169d78ed79aae7a48f29c98669cdefc6ebb71f66fcdbe1f1132adb2ae7dc3a714a2cb4a4f5865d1ec9ae4c2930511b9f9a16e220ee2afc51d056c7b69e8bcdb3a7ef8e9c264a499c6d1839f4c6161859ab00e06795b03d33a13cbe6e025a7c5ed1b3d6044b714b25ef4228841cb1326e9a198fec9eaae849f1571d7a988f832ab45d6a05106344eb0529b07b4649bdd0592e602e1b2a263ef675e2fb5250d0fc12bcc1482a9102568c9379611cf7ced996cd9a575836ead5474162a2a93c23888edde35d8fe83d4d4c1306feee83f8c39c4f9ad788e5687f67228715d99f512b40ded4a799d620d81c858dd5126a69dfac4864496cc980004071a578e9004bdce44ea1371f6acfadd0b3e1afa6a293e81eb3ab08a58958a5be5d0d5a9347e0b7968fb694d200078e991c2666bc35c2d27000427c6d9d3390ab4c1db1a92554f2891a06e196438556a18b8e861d815a8f1c1bb5d854fb8a06b983efff0ce867571d9d6d2c57315407adbd16ef8874b27f8813a3f8f8729705fce0084eaf6387ecc87a23bac38b25eabdd4067558fa08305e4a349aa2549f25217d5aa7f4246ca354196e56d51032b673f351121a217fbea67c47215c397ad20729eb402e188e616eecfbd8397f67a33c14eaa247741331d54e4d3de3e8755a09ae65f1e1b5e83779f03ade05a0076fa1272324c4ddd2caa3ef289038d11e55d5d6f8f47262d5b441c21930e5d718556d13350cb0153688ca40083ce5d8a6cfbda9f072f0a096806d16c89e721b86e7f8b0a7a26b91aef4164a2581db740dd88536c967ed71cf12f22794ad66ed82a5829000182e20381e8022007bba645c0115a8f7d6f67c1f9d2460f73d641ceb84997bb54afe36ed11a9c72d82a5828000181e203922020756cc556bd2c6db9ffad0a86b29297860d63869337c1d5fd0921e71afa592a29000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2f75775", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xe01589cddd43f6bceff20e5ae7715922ddb41b97e88b5e4520fa355684aea943", + "transactionPosition": 36 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000015c8bc", + "to": "0xff00000000000000000000000000000000018a9c", + "gas": "0x3603c9c", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001b685168282044082054081820d590180b9e0650e2791bd2e265cf4ff157de041c454b9813e09d21651b6b132609604b77b8a54feec5a9596f79a7aaa2e96fb50ade24f30190a0cb5fb7fa0d77780812aff6c95f4d15990f648a57fc4b3af6ca33ce17b1b0ba17c59198975f7dc79c231180918651145f631f00234c7697c8ad40d293cb34edd09af17f4f2a1f950aed915d6e16a18540542154198516872226599ef6d287379e87c82cfcc40bb6f1c4226d1876235d0e4c2cf7e5c830e7ecc81df37f42fbf714e393a501b80496549d1b52ce5ca02ba411c32ae4b89878363c6434ef47dc39a36027e01f3ad4c60427cd9e9d65595421a557624fb76b236702e94d4400f3bfe6814a14a0fdf34e98ccce46c551a77eea8722ff0b61baa3de7566e17d752e01d4c6ba769be27e9d4dbbf0364002ac4a50774c06353273215ba88c4a820cb9e4a2e70e7f0f4f12f95977b66fba1bc3d5a9a1549514fc23d4d39d9a6233d894761862d0763d7602eb764f05818d872e2ed5c10f0b9dac5e2540c825b6beef6049f00056ee5eaa72bca70be1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000" + }, + "result": { + "gasUsed": "0x2c387bc", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf62bf0708f9536497bc516be3b6d547abc74b04e46598b8d428d1bb989175294", + "transactionPosition": 37 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b3964", + "to": "0xff000000000000000000000000000000001db56f", + "gas": "0x5d6ade4", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000010185181a8182004e40587e20e402c98ae04030a2159e81820d58c0810a1d1d92f527d5c666cacddb7c8da0c4319d0b04c56b232ffaf060c49cba4ca620bb1159347958d2681a5727efd7e090ed851ae5a9236758cd90dbacd940cc7ee8e431727a501fdf14809cb9d343520a11b67feb31f49da7cf1401fff4a04c10fb9d02be4807a8c83a812bf690ea6a3e7bf377884f8ac908c8e02f4d10bd7adc51480159b52ca97c5e9b9d3ee9c4e98d2b48aba3ea7de3710ef8f20a85aa25feff154a5d62baa62f446ddeebb8ca723c9df71e7b8f4021c48871459b65cc721a0037e5f75820a3a81d9f7df6ebf387d44874769074545c6eaafa2e7d3e3728a8b70a048a411800000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x35c5604", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x36071df471f65c45aa13469b5e27b464ca5467f7a0c32a24b7e35e02ea17953e", + "transactionPosition": 38 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001db56f", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x28f29a3", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f8246013800000000460138000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x15c9767", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x36071df471f65c45aa13469b5e27b464ca5467f7a0c32a24b7e35e02ea17953e", + "transactionPosition": 38 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce379", + "to": "0xff000000000000000000000000000000002ce3c0", + "gas": "0x326ffc0", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000708181870819436cd82a5829000182e20381e8022028d8fa5716e16415633900cf944c3a7c13c89b4b7998357f96b93e52033ab7101a0037e107811a0454802e1a00480c92d82a5828000181e20392202026901d7150173a8f624326ea93f6aa265f7d4bea28071817876b420b6bb1763d00000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2270b3e", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xa105b44c0b659b3818b548210b3ac8c78f9d1320c94d359df1a499b8e92b1d69", + "transactionPosition": 39 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3c0", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x31b9999", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xa105b44c0b659b3818b548210b3ac8c78f9d1320c94d359df1a499b8e92b1d69", + "transactionPosition": 39 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3c0", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x30ad451", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xa105b44c0b659b3818b548210b3ac8c78f9d1320c94d359df1a499b8e92b1d69", + "transactionPosition": 39 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3c0", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x2f8bee0", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a00480c92811a0454802e0000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x488c45", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e20392202026901d7150173a8f624326ea93f6aa265f7d4bea28071817876b420b6bb1763d000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xa105b44c0b659b3818b548210b3ac8c78f9d1320c94d359df1a499b8e92b1d69", + "transactionPosition": 39 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001614ac", + "to": "0xff00000000000000000000000000000000018a9d", + "gas": "0x290f9d0", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285028182024081820d58c0a59157072c783a23fdad72543a912757d0234de28719fa60979b8cfbf9cf4eb5f7819b9e24f2aad13efb0e64f69f20b0b1fb96008c44c1ee0695eb0740aee9ba1106fb832481216b53b6929e9da8f3ece77b6685bedd0ac314a12dc9838f7b4612c874b708ca9d190d31d22f5c362625a6993f9055ca9078f040aedbd1195823a13b92e4f83234cdbd69f7df84741fd89704df8fb5d8455f4c1590aa377ebf35ea6918e47951d1ad4c22f64cec35a81bda8e2063148a98259e3689b711747ff81a0037e5f6582080334418ee2e59dadfdb8d33bf996c9c35f36dbe740e214e308a34ebd796ddc90000000000000000000000000000" + }, + "result": { + "gasUsed": "0x21a9811", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x5baf436ea76a5a6938726489a2b0ac90d8a62225ee0a67bf9440d9b66c8ab007", + "transactionPosition": 40 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001c3bf7", + "to": "0xff000000000000000000000000000000001c3c0e", + "gas": "0x6a72102", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000027a8518258382004082014082024081820d590240915b93366d1917d73ed68ec773c15b05dcfabe838b6aef263760ca3e6db2c6173be57b35130f6fbe36edc2f923b97f74a48c1b20703f76fece172c2f10ee57dcbc723f46b02d971a1407358f025066b292140a32797174b57406603f80bb85f70ab3669c9a27f96f03808f47de7fe08a12149ca62ee727ebe4de79e3f7e547781fc5b6a86d3974c842f134f388418f39b7f528104398df97d94c20d49793e55105399d0e591cab05e828f98ef04f48796e367696e4ecd7c9cf48fd989199905d891f08abd8c6ab58bae7d76cb0a142e875e259f7cfeca88ca0c1cc42798c516abfefd08366536984294f0463ddcb287bac2b2305440a2d11b80bcde36942dbc0a900e8a8edd3f74bd8d63479a011a9cbffbe20eae183469f8a093bcec950ff0a0dffe0a0d2039dd24fbdef242e0d7a81e5d375443ca995272da2733c8f1087552a2572aad7049c15d007e59a5f19befab15b2c01a196d4c131ebf118c4beb33bf1ae866b5f1d89906de32cad27e4c9c9e824108d07fee9d5938c62214a10fbfcad0efef90ae507b4b9b8f20786dfe26ebdb215adda0d3530bef4619c99011340a3f6e294391e5a70cd8f3507e046bf54a27b703a49354359363d301723d6eceb1760bf220ecd15753058e87d3b489fdcf6bdf391a6ceb5c932e9cf9e3e83fe53087f8c9ba3f369567dbe5d597c133534dc3d5961f71fa1cca16614e2871a102aa59af9bc65a9f69e6babb703e41fa3cdb2599140ebd81769ae17febc5700d59e556be7bcc6ab0f245dac5cab7d98f0e0565d1b4d91505ff1f147f1a91aa80d9d1a0037e5de58201bc00903e90ca71cf8a383db0b4d3a038e6c65f02639a026af83a66ff71862a5000000000000" + }, + "result": { + "gasUsed": "0x565a543", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xd88734b87797aaa8445444670a2d6bdd3bbd84d4ec90d3b7b78acae141f7c2a1", + "transactionPosition": 41 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce375", + "to": "0xff000000000000000000000000000000002ce3b6", + "gas": "0x538030d", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782195b765907808776753106795c7ae26573ae19505c7fc8a145840257c67736fc00ed90d79d770f0bcbb1d914234ffc6f522a4aba7ae8893e8fb235797e8c7258a8fcbfedf14a90fe28bbfa9834fdc4e5030caaf9ef6313a756804a9c01e2a4ef6cbee65d16281897d1285160d0504c5e42d119880b6c1967694b390b219079086f2ebb8012e17f3866f16f7be2224bceb0327cf80150ae72b36802a974b70541a9613da97ff18342f052a8a505b89b83f948fc6ae6e467bfdb19b5c9ef2c392bd54052bb1f13877b77cb9ad7cdc31bdfbd2ea960a82b6196e6c6d4fe8dddc50fe40c6f2bb4ceb0ebb1802548bf4f1f16ad3890285c00b89aa1d36745ab91d5ef87d043f15a1d8d1dc392832c578b40f456e6e1e4adbf21363cf9f0905ef9f32a1cb10ba0ac9010acdde70778af71b6054348355503a303951d23be9f4c559390da5e1708098e34f37bb0dcd3033549e902c4c1f3d747b2212f19d3c0f10a10e7176022832c521afc8f6b58407de713dc3cc35940762cfabf4ae2cb11b6d730529f4f37dcf94faac4bf6007443e3d7fa2fc3ed9207926345d2135759822096874eed1156a4040244ca30c55e64954531d1e0ac583b6128ec62a2d5adc76dca441a36884bafc29937bd304187e2c75f3ffc04b522a86e381fe672dc5299f9e53632f01d52c78a81495daee861015440697d3d49cfb4ec14c4371b5931e550168703a4e0cb329cfbf0bfccddb6d4a686b961a5910176a3ab16e4b57edfd8dfc7217bd51836bc9fba749534a635d2ce58b50563087e3a59a0a7e0cc897bfe3444792f04a3f52e375a4c7de5cf5eb1077b9aa09fd2ad2adec73036686e29548174b6515e0845d5c5a32635c25253ee3a4afb699f00ab6c720a3f82349679c5ed59ab01a70c68e4ddf395cc14cb70ed8a1645b9007226c4b733e6136d8493430420a155caaaef3e9f40869873f0d1816baa43388948f8ce129405a8ff08d514ebfd190c519871ee66af8f599911f6f10d0b727a09ad16621d1b804c32e6d906152e8651e754d58dcd2ec572696175f743b426bf5f9bd96996f53083f871fa9628eb93fe223438e39448bf72ad9facc6c2649e29345f3aaa6a26140bed5c6f524137e9789ed17efde3b814e2b16eafe8785fe83fa7d66e53c76932f20cf89053e4651060552134686a70a8faa289a0baab5d8bdf5903621347c6b9d30bf6d6d1c227148cbd69da134d20baf7597b0314737a8c30a00e13362f12a7d49b2b8c673346ed47d70e0c11423cc97f7b62af88afccc4afeaeed0f8059b726785b7e459c4a620145ab96b6ee7d683420761b1f5a96757676fc33b528d5182f7b8e84fb84ab36eba1f39db0d551b59af3258e48317d70a612e79afa2302cdfbc89d99905aa6026d24cdee78ea2d5f3145e9cad4531b2550472914cc5ff89780669bf5b7b90db9f518801c3fc317721469dcdb5ab2aa22df8a55f3db7e7f62119252fda66bb5d1201088b68faccc1403ae074c6f8085f436eacedd09a9286396ad0779ac01c50dfb60ded4892fde0ec55c56b702ec5ec3c886f9ff62e84b88f0252460118d411ca853216ee009b948b07a60cd3f4689135bc857663fe4ed71309e7babe6d0750f15e9225045b506a8dd78ba11138d131c30e7175c9bcf461c1ae51cb232d1ee4361757d729d0cea9676f1e6de815fb305e390c0cc402f1481fffbd8bcf49ae6ab8965941bcad082a712c97975217f189c434cbadd60bce79381f9f1b42de417d9e0ab162027f4c00c09bf0d01d84fbc28a74079d94462bead8c877a09bf978b8e8ec49816123cc244613c0fe60426977348bbe2d75b18f59840e4cba2688992e5713493514c6f2ebbecd2c3650eed7c3018614bec7030211b9638bf78d1b74f2adc51d3c588eb05a58f6faeab355e620c0091ad67fa3b5a861ec0fb700c637680fff5468e99bea0257f74e38ba8fbcdd08ddea960403db1af19ba1df53e371b0ae6af65fc3996effe4e8451b49fdc81c873ba134d06d38ccfc19a589d135ac1501cf693fdcc897b15b044736a99a47f8f69a48d8f58297ab20bb43f986209c0c3d8862b055c66611d040c50a281bb349c82884f39f3d9258febfcfcd6b359a03dcc0455ee5e9fe97e4c89350871a21cb0564fb0ecfb45a3263f10fb0d74bcf8c5920dd677472c45a519f63ef0b1c71344d6f2f4a95c2be6a344b20a8470a0b4eb3b2aea6a1c94c9ebc0fa8ac142432f81361b87e2d73e768b6d636fedf68e02a0481173987dd0f3d0e6707cb517c8e9364bfe2b24521459e1e9ab2fa00932c0ea67ab512532b3870e6c7f5b885911ad4d96308568461d5386d370d9a081573585105bcff4b83867b466b8248a626d41c8dc0e88345145ae881c0a03b9a157e002e822ec3b6ae93e1e35dc7e40a5e9f4f7b828eded0a715ae79d61941cff6bd265efd6cedac3c5db83c50a59ce24ac3779a5055627bc52d2ff33b335cc0e47d517e06f3861bb08d10135516097ec0cb573f3299b58e37cf3a431627b8ddcf3b0e894cab48bc3d16ac21b17ad6488977a2991c31936b6ae1d83b2988de15eb4996850978dd2853d9608b4d7d90e6d3b6198fbd064403012f7918829b806669194b55eead50487d9a89ab7c3e808cb6b6f04613df480eea6bb837b8e94790d5af979fad39284d609c1fc40bcf347466bc24434f617465178d7f89998da9da550cc49d54b18807f6eff00000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x6badd2", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xa96e51f481ad7fa03bf8bb21077c475f6104053a8d553f7be0c7ec6bdbc5f4ac", + "transactionPosition": 42 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3b6", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x4fd333a", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002ce3b6195b76811a045356e5582091f30bc594d5d33c5fdead7467a5c9a5f5d3493a04d21054be0fd86e552edb33582059f127e49b8051719946f330b970a0c841fa53270c5b6e5453ddfa01e9db20155907808776753106795c7ae26573ae19505c7fc8a145840257c67736fc00ed90d79d770f0bcbb1d914234ffc6f522a4aba7ae8893e8fb235797e8c7258a8fcbfedf14a90fe28bbfa9834fdc4e5030caaf9ef6313a756804a9c01e2a4ef6cbee65d16281897d1285160d0504c5e42d119880b6c1967694b390b219079086f2ebb8012e17f3866f16f7be2224bceb0327cf80150ae72b36802a974b70541a9613da97ff18342f052a8a505b89b83f948fc6ae6e467bfdb19b5c9ef2c392bd54052bb1f13877b77cb9ad7cdc31bdfbd2ea960a82b6196e6c6d4fe8dddc50fe40c6f2bb4ceb0ebb1802548bf4f1f16ad3890285c00b89aa1d36745ab91d5ef87d043f15a1d8d1dc392832c578b40f456e6e1e4adbf21363cf9f0905ef9f32a1cb10ba0ac9010acdde70778af71b6054348355503a303951d23be9f4c559390da5e1708098e34f37bb0dcd3033549e902c4c1f3d747b2212f19d3c0f10a10e7176022832c521afc8f6b58407de713dc3cc35940762cfabf4ae2cb11b6d730529f4f37dcf94faac4bf6007443e3d7fa2fc3ed9207926345d2135759822096874eed1156a4040244ca30c55e64954531d1e0ac583b6128ec62a2d5adc76dca441a36884bafc29937bd304187e2c75f3ffc04b522a86e381fe672dc5299f9e53632f01d52c78a81495daee861015440697d3d49cfb4ec14c4371b5931e550168703a4e0cb329cfbf0bfccddb6d4a686b961a5910176a3ab16e4b57edfd8dfc7217bd51836bc9fba749534a635d2ce58b50563087e3a59a0a7e0cc897bfe3444792f04a3f52e375a4c7de5cf5eb1077b9aa09fd2ad2adec73036686e29548174b6515e0845d5c5a32635c25253ee3a4afb699f00ab6c720a3f82349679c5ed59ab01a70c68e4ddf395cc14cb70ed8a1645b9007226c4b733e6136d8493430420a155caaaef3e9f40869873f0d1816baa43388948f8ce129405a8ff08d514ebfd190c519871ee66af8f599911f6f10d0b727a09ad16621d1b804c32e6d906152e8651e754d58dcd2ec572696175f743b426bf5f9bd96996f53083f871fa9628eb93fe223438e39448bf72ad9facc6c2649e29345f3aaa6a26140bed5c6f524137e9789ed17efde3b814e2b16eafe8785fe83fa7d66e53c76932f20cf89053e4651060552134686a70a8faa289a0baab5d8bdf5903621347c6b9d30bf6d6d1c227148cbd69da134d20baf7597b0314737a8c30a00e13362f12a7d49b2b8c673346ed47d70e0c11423cc97f7b62af88afccc4afeaeed0f8059b726785b7e459c4a620145ab96b6ee7d683420761b1f5a96757676fc33b528d5182f7b8e84fb84ab36eba1f39db0d551b59af3258e48317d70a612e79afa2302cdfbc89d99905aa6026d24cdee78ea2d5f3145e9cad4531b2550472914cc5ff89780669bf5b7b90db9f518801c3fc317721469dcdb5ab2aa22df8a55f3db7e7f62119252fda66bb5d1201088b68faccc1403ae074c6f8085f436eacedd09a9286396ad0779ac01c50dfb60ded4892fde0ec55c56b702ec5ec3c886f9ff62e84b88f0252460118d411ca853216ee009b948b07a60cd3f4689135bc857663fe4ed71309e7babe6d0750f15e9225045b506a8dd78ba11138d131c30e7175c9bcf461c1ae51cb232d1ee4361757d729d0cea9676f1e6de815fb305e390c0cc402f1481fffbd8bcf49ae6ab8965941bcad082a712c97975217f189c434cbadd60bce79381f9f1b42de417d9e0ab162027f4c00c09bf0d01d84fbc28a74079d94462bead8c877a09bf978b8e8ec49816123cc244613c0fe60426977348bbe2d75b18f59840e4cba2688992e5713493514c6f2ebbecd2c3650eed7c3018614bec7030211b9638bf78d1b74f2adc51d3c588eb05a58f6faeab355e620c0091ad67fa3b5a861ec0fb700c637680fff5468e99bea0257f74e38ba8fbcdd08ddea960403db1af19ba1df53e371b0ae6af65fc3996effe4e8451b49fdc81c873ba134d06d38ccfc19a589d135ac1501cf693fdcc897b15b044736a99a47f8f69a48d8f58297ab20bb43f986209c0c3d8862b055c66611d040c50a281bb349c82884f39f3d9258febfcfcd6b359a03dcc0455ee5e9fe97e4c89350871a21cb0564fb0ecfb45a3263f10fb0d74bcf8c5920dd677472c45a519f63ef0b1c71344d6f2f4a95c2be6a344b20a8470a0b4eb3b2aea6a1c94c9ebc0fa8ac142432f81361b87e2d73e768b6d636fedf68e02a0481173987dd0f3d0e6707cb517c8e9364bfe2b24521459e1e9ab2fa00932c0ea67ab512532b3870e6c7f5b885911ad4d96308568461d5386d370d9a081573585105bcff4b83867b466b8248a626d41c8dc0e88345145ae881c0a03b9a157e002e822ec3b6ae93e1e35dc7e40a5e9f4f7b828eded0a715ae79d61941cff6bd265efd6cedac3c5db83c50a59ce24ac3779a5055627bc52d2ff33b335cc0e47d517e06f3861bb08d10135516097ec0cb573f3299b58e37cf3a431627b8ddcf3b0e894cab48bc3d16ac21b17ad6488977a2991c31936b6ae1d83b2988de15eb4996850978dd2853d9608b4d7d90e6d3b6198fbd064403012f7918829b806669194b55eead50487d9a89ab7c3e808cb6b6f04613df480eea6bb837b8e94790d5af979fad39284d609c1fc40bcf347466bc24434f617465178d7f89998da9da550cc49d54b18807f6effd82a5829000182e20381e80220653bb701762a54aec57374c36979ae603e219bc00ef3be63554bf4a537c9be5fd82a5828000181e203922020dedeefe8f2842d894b8ef09f16bdb01583ef59df9249aeec829366d436be382e000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x36c73ec", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xa96e51f481ad7fa03bf8bb21077c475f6104053a8d553f7be0c7ec6bdbc5f4ac", + "transactionPosition": 42 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce375", + "to": "0xff000000000000000000000000000000002ce3b6", + "gas": "0x5b8f940", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782195b65590780b0d5b0ae31a98ddcc416abef382cebb9497b1f392ad01eea0da893b1830817c4363a3d8553f7f49433d65d2a2b36460ab3e02973695e6a0dcf8a7226c2de837a3c7780f2cba244e1eca00be2d1e4aaa1be39b9bb68dbaf151ee90bc7f5a499310f690858ed287ee53d27ce21a485938f847db28229e6c4c078d5a0f8dea6bc7303c891bd668e6208356efa1c527805399282f916b8ba3082034aa4b89be8eff9007efa934a7302aea6d85c2008276e7fd39ead1fe418e5671db68e328182462baa5f085e99ea93a2081f63683629c8451beb4a55fde9204d19dcfcda05b9024bfbe4c99cf42bbf21a61ab5a89a431256a122169cf3ea980326ccd701dbb37a04c6d97a850e0d7da22b08a7e4f6ccc717ba53b5950b95abdf294ffbccada1068611c9cf16516df5df034b397b8f55c3411024b16cf1f6a83ff6e1ba079c2aa300dc232c96d2bca93e3c75bf0db07e6bb8886c95035566b4c35e8ffb0730ff4a3f42d38dde63aaa711cced92c376d56f2d848c29b0a979a83acf3c46bb5d123c6cac402b87a4c24d59310f44af1bcc61316c8d778a2da26a6e0cc3c1f0e3053431dcc3bb29021e11ce9bd74bf44676734c8ab00ac0156ae09e3ab82f82f2ca467d3d5ac6153dd388faa6ea8384692559f65ed9ef1948e400c8908f335d494aa8f611fd52acdb72c913f7b0ab498b5dcf487e666499c3507b4a418934b4c26c60da8d2d53b4d17a1497db33596126ff1bc2ab4fccbba06ce6c2b83027d82ef31a7f796464d996d4085bf7da53a2e260c8dca9024f28ae70cce80a23482f4aa19aff838a77344229c7b6884ca2d319e6bfe987e8c025c1c71d892d480c7a55bb46e5c0498fcea2e7f5d06f5ad4caee57fd3da417ee1cdeb660a00519fd939feedf6eef6a2ae2cf57ec6a588dd0415debd6edf03ff259b70b7217c394e0c0b1504eb819c1731560cc7011367e650862035c1eff36d61358aef1cd66bfb345c5c349d986afa0bc31ce62f53d7746b5bbdb027db28352f745320768b01619a152b9f766689c7573bd1770fe7704faa8eff9b8c83c064681e7bf2b345d9def6b7d57a219841a2ed3b613adb9c13ac6ec9595f59691ed05db2274062868c42451cb669e048b3cb9a5929448822ada2029ffa2a42ca2a7b97e7e1cf652314ef151242d982d0204ab49bb8900d45fe57e07484cb008454aa8cf1bbef3b797c6c8bb100c7d7b000f3c347bd0c6ee5f225ab5c579c24ca23d4358f5349423c93e9c6f9056d3faf80241b9c0a3c8b77f18bd364f400a08a50c55cc989646d1de3b8b0c2b243f4529b72c0d1d5cb316b2068588848aa784c2c1b047b63d30507e9cd58cbcd0034390e5ec61ff2990b0b07867011387e9aad8e6b38c236bb93bd7eefd961d696bd64169784ad55e9c5196a0792e070a33dd95fee21939f59b7fd4a7bdb0c3c8c56135e7f8e621ba578e59eb328426864527394bfd080e8e2c06e317693bfb7f9c970dbeddcb5e6ea07f44c1cc364a0d864a645fab0422c0e27363dbc7256ad44eb265b00c671a7c0f20b0bf8904b008bc2ea07490db2fd954cfbe48d137963c1e286d79a8e60f1285003f476ab16b5b28a79ebfa78fd689c276203f4aea3b3f43f9999c81e03a13e9c8faede90f991440dd39a34fe4763db1652a4b7dc274412ee9b836f8aab5c7e02328a2897f8f331a378324e1d2a84cf998435fd6ad631f16b1ff3566def97b6b319c95038d95135c8f1edddd7ceee4083cd9fbfd48539db28007ae00c350bae09355a3d3cd68bc7c91ec4616dff3dd300e8da3861c78cd67b58a49bb4a5c98c4d29b72f3e0fe32193e9047ff4f62b207623f0da9eb315415bb153347b4d7e7989107a2fe1f2c25572e6e7d757b09c5e382d046e5a72934d378ac9298db613281a15611b522725f058106276dd70689b65ccb27b0fbccda99c7b502af389fcc4c441738462efd87439cafb0aab34b7f33d0f44da59b90ea03d7d8e7b7a96b8b78f34ba126936a08a5c915ea8c679aadf08a72b1ac79738ca72f157cadc8246b3332d20bf1c4319c4d0b4a0fc643710084c05a44ee3c6d6316f26b3d5c05afde5b660d6534dc0bb51647a9b980dca0aa2b9430ff689dd3b9fa871d5cf594df3b25776cf0bbfd9a4644bb53c083c7b3845064ae1046b0420475b0a5cd3d316ff15ed581d62456d117021a52e8adacda6ab8365c94578a84139adcdabe3d32694258b687202452117ba61bacab047517a323b2f36607052fa48ffd55dd3ef4a78c65a1441b4f628666b623f76ef4576136ac1ef16d6f98bbaeb14d1428790969175eee0968e9c76aaedacdb1b9d77f898e5aa8bfbf4d053a7cba468090a749f80ed668cb7f4dc7af33af98935de85ea462451634378eebb58acb78f6a1021a4b023ce212d83d60402f28dde712722f72d6438142148231e69e55a1ab5fa192f5a2e068a40d60327422e302b8a560b66f8eb0eb0a94c36bbaba86f38310bc2e8a8c06d9ab87c8af8edb971581de9ac84b8c7371560c9d4471fec715f622b92bc0439c1a3b2ff4a50e2e5db260beda585142eb60851c9b31e0e4254d010fecbf7af7ce9921e5dad628acb23f5406a2f3e9b647944de96e8d6b2e91aadbdf8cd27af2a8800af5f450015eaf88b47a858b5123f718c6378c9c085da31519fc41dfcaca195a0b2de33dfd216dac4b348e4cb4b2811853cdfab8e11ed55a00000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x64fb6e", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x8de36a2a721ac62d59185fc78daabfe4f045335c963be30edd33170cc7d2ca47", + "transactionPosition": 43 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3b6", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x584d9b1", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002ce3b6195b65811a045353365820e8191611dfc1e40b36fb948cae0d899ee686b3cd8900eab1e5d4c9f8502a022a582047a51e4e53644247c4ad14e95dcf51070bc8748691de862db56543447a9993eb590780b0d5b0ae31a98ddcc416abef382cebb9497b1f392ad01eea0da893b1830817c4363a3d8553f7f49433d65d2a2b36460ab3e02973695e6a0dcf8a7226c2de837a3c7780f2cba244e1eca00be2d1e4aaa1be39b9bb68dbaf151ee90bc7f5a499310f690858ed287ee53d27ce21a485938f847db28229e6c4c078d5a0f8dea6bc7303c891bd668e6208356efa1c527805399282f916b8ba3082034aa4b89be8eff9007efa934a7302aea6d85c2008276e7fd39ead1fe418e5671db68e328182462baa5f085e99ea93a2081f63683629c8451beb4a55fde9204d19dcfcda05b9024bfbe4c99cf42bbf21a61ab5a89a431256a122169cf3ea980326ccd701dbb37a04c6d97a850e0d7da22b08a7e4f6ccc717ba53b5950b95abdf294ffbccada1068611c9cf16516df5df034b397b8f55c3411024b16cf1f6a83ff6e1ba079c2aa300dc232c96d2bca93e3c75bf0db07e6bb8886c95035566b4c35e8ffb0730ff4a3f42d38dde63aaa711cced92c376d56f2d848c29b0a979a83acf3c46bb5d123c6cac402b87a4c24d59310f44af1bcc61316c8d778a2da26a6e0cc3c1f0e3053431dcc3bb29021e11ce9bd74bf44676734c8ab00ac0156ae09e3ab82f82f2ca467d3d5ac6153dd388faa6ea8384692559f65ed9ef1948e400c8908f335d494aa8f611fd52acdb72c913f7b0ab498b5dcf487e666499c3507b4a418934b4c26c60da8d2d53b4d17a1497db33596126ff1bc2ab4fccbba06ce6c2b83027d82ef31a7f796464d996d4085bf7da53a2e260c8dca9024f28ae70cce80a23482f4aa19aff838a77344229c7b6884ca2d319e6bfe987e8c025c1c71d892d480c7a55bb46e5c0498fcea2e7f5d06f5ad4caee57fd3da417ee1cdeb660a00519fd939feedf6eef6a2ae2cf57ec6a588dd0415debd6edf03ff259b70b7217c394e0c0b1504eb819c1731560cc7011367e650862035c1eff36d61358aef1cd66bfb345c5c349d986afa0bc31ce62f53d7746b5bbdb027db28352f745320768b01619a152b9f766689c7573bd1770fe7704faa8eff9b8c83c064681e7bf2b345d9def6b7d57a219841a2ed3b613adb9c13ac6ec9595f59691ed05db2274062868c42451cb669e048b3cb9a5929448822ada2029ffa2a42ca2a7b97e7e1cf652314ef151242d982d0204ab49bb8900d45fe57e07484cb008454aa8cf1bbef3b797c6c8bb100c7d7b000f3c347bd0c6ee5f225ab5c579c24ca23d4358f5349423c93e9c6f9056d3faf80241b9c0a3c8b77f18bd364f400a08a50c55cc989646d1de3b8b0c2b243f4529b72c0d1d5cb316b2068588848aa784c2c1b047b63d30507e9cd58cbcd0034390e5ec61ff2990b0b07867011387e9aad8e6b38c236bb93bd7eefd961d696bd64169784ad55e9c5196a0792e070a33dd95fee21939f59b7fd4a7bdb0c3c8c56135e7f8e621ba578e59eb328426864527394bfd080e8e2c06e317693bfb7f9c970dbeddcb5e6ea07f44c1cc364a0d864a645fab0422c0e27363dbc7256ad44eb265b00c671a7c0f20b0bf8904b008bc2ea07490db2fd954cfbe48d137963c1e286d79a8e60f1285003f476ab16b5b28a79ebfa78fd689c276203f4aea3b3f43f9999c81e03a13e9c8faede90f991440dd39a34fe4763db1652a4b7dc274412ee9b836f8aab5c7e02328a2897f8f331a378324e1d2a84cf998435fd6ad631f16b1ff3566def97b6b319c95038d95135c8f1edddd7ceee4083cd9fbfd48539db28007ae00c350bae09355a3d3cd68bc7c91ec4616dff3dd300e8da3861c78cd67b58a49bb4a5c98c4d29b72f3e0fe32193e9047ff4f62b207623f0da9eb315415bb153347b4d7e7989107a2fe1f2c25572e6e7d757b09c5e382d046e5a72934d378ac9298db613281a15611b522725f058106276dd70689b65ccb27b0fbccda99c7b502af389fcc4c441738462efd87439cafb0aab34b7f33d0f44da59b90ea03d7d8e7b7a96b8b78f34ba126936a08a5c915ea8c679aadf08a72b1ac79738ca72f157cadc8246b3332d20bf1c4319c4d0b4a0fc643710084c05a44ee3c6d6316f26b3d5c05afde5b660d6534dc0bb51647a9b980dca0aa2b9430ff689dd3b9fa871d5cf594df3b25776cf0bbfd9a4644bb53c083c7b3845064ae1046b0420475b0a5cd3d316ff15ed581d62456d117021a52e8adacda6ab8365c94578a84139adcdabe3d32694258b687202452117ba61bacab047517a323b2f36607052fa48ffd55dd3ef4a78c65a1441b4f628666b623f76ef4576136ac1ef16d6f98bbaeb14d1428790969175eee0968e9c76aaedacdb1b9d77f898e5aa8bfbf4d053a7cba468090a749f80ed668cb7f4dc7af33af98935de85ea462451634378eebb58acb78f6a1021a4b023ce212d83d60402f28dde712722f72d6438142148231e69e55a1ab5fa192f5a2e068a40d60327422e302b8a560b66f8eb0eb0a94c36bbaba86f38310bc2e8a8c06d9ab87c8af8edb971581de9ac84b8c7371560c9d4471fec715f622b92bc0439c1a3b2ff4a50e2e5db260beda585142eb60851c9b31e0e4254d010fecbf7af7ce9921e5dad628acb23f5406a2f3e9b647944de96e8d6b2e91aadbdf8cd27af2a8800af5f450015eaf88b47a858b5123f718c6378c9c085da31519fc41dfcaca195a0b2de33dfd216dac4b348e4cb4b2811853cdfab8e11ed55ad82a5829000182e20381e802204d0dbb2063ca111c6cf0067d447609ce7c03d44c821d8fbb2c417e96ea7b5a18d82a5828000181e203922020c5455567d46fbaa53337932443d470d07b100bed66689795774541eac6bfd92e000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x3dab44b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x8de36a2a721ac62d59185fc78daabfe4f045335c963be30edd33170cc7d2ca47", + "transactionPosition": 43 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002af885", + "to": "0xff000000000000000000000000000000002afa9a", + "gas": "0x4ffdb6b", + "value": "0x1a7f287357826fcb", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a000190f1590780aa6dd1b6cde27d943f35448a588dda962b90bb5c2d83fb2fc6b05c8879362c7e817ed132379a5d26eedc593a3c606d21b1ad6dce5e114d940ed6339ad15a75f332154afdfcf7b12d95582ee4a14202d965a951d4188d7c229f08116f19110bf10ffe6f3131684f4b81a83d8f134ba8b934c9fbba9de485da6314a6e2b4506ce66faabd643095fc29368353eccc4666ccabd8aa5e770a7a983fc357e401b33944c70ad9ca78bc9cfc28c89557f36c9211906843f318dc25c061094b004bd12cba8e086520ffd326f3770543b454bc2dde31928e03115ae3ec7827456b9d1686d4221d1bbd908594ae4765699cd49f5d35b683c356421c3b3d84831c9f848ede29dd011f0b7b2b19cd4247ba549fc81ba77a935573ecb2a8e51fe4e0cbcf360a3a19cbca7006ede49b7b6520c744849e6b4daa1f79d178a9247fce67547d3b07d2e19762f3bf18dba2a6f5889aa872a843948cd7ef996705951974d2335c4b8f5162f93695a1babd67b5452e4bc9ec673031a23f3e19238e1ce953df0d6932a2b5a9a4cfdf9ec7023cb3a3532211daee7979736978bf3dfaba75b44e9890fc1e324a6ff5740bb5d5a91b57d21795a62843af5e1fdc25d90e2b087d36c3d646f526a2e9336f937d80f9e03a855a08878f80da10ea8167836870d3b8abf9ebf2b5b60f001ad41bf0e78dd27b48569c587335e59482402570c61740c114af91f1b163699498bf6b0cefd1d1d211328ce8f0cd831770938798626b188d47787e3169c103fd8acf60aafbad26c69259c269e55aef8ef27ce606e69cace5bed0e8570864881b582727c096fab89d33c0e6d2d22ac2b3d02974110bd104a36be0f1d20bf3ed4d3c4c3fbdbb4165f534eb2adf466587effe414558bd05d694c75eb071cacaf21d67d7782d4bc7b42deb129286a96d00de11b5ffbe2f07502224b99541637018c19fd37d110a3636addac50bc20bfd8c408d014e4f41c3f6caaccdeb47057447de642bfd04b7a240d526879cd484ecafb45f07e4c4728a6df049534316e76ab3b751101a606b8f7741c30508c86d68075d24bde13a68276dd9c64f2bdc20798f8d3ff9052273f3407b8a255a5de6303c2a2a2501a1f0271a3996dbad35959ce3f69169329593faea319dd23d1aaa66adb32fc8be99377176e47de9538a55b5fc6f02ed74a494dd4546953b7cc772b649ed17a4c728ecb4d63ea885293926751387f8e6f1779acbc0c8a4aebbea14b51dd4e651f8b2055c6b307c041fca08e590f50bfd1066f7e074b7f98fd4000fc8a10872fd115e4c74a21c9129fb0230ae27f4af10bdbcd02cfe96792a10628b0018ef8cf7984ab0b2fa34a9464404500c8e2d6945935a22a534962ea8ea448da54d79b8e53d56b9c2c7ad7de5b9003476d530f1bc1f7a3371a4e1dddffe93974eb03383e315632977426589e4a13022b55ad361150a48c42c35ba475848dd8a6ec957c7c1a6525830eea4ca05175c3cfb0a5c1faff0338023961101aa2cf5458b1ec14e1c958ee0a8048acac6cdde743043847c2c8951d708a00247e19a8fcc1f90325533a773ffbe795e78498ffc3d81267c2ca13eb7b9fe362ac30518537653ec47bf3bf172ec0acd9a65d9e8c16022a6ced9030033964a3505894f0621c9be0268328a4826c7596ea3f21a6f404126dcdfda7063e9e70175d012424b890043a619d74f99734052b262f7983646b7c273061c898341b4ad3a46e0c825790b4f8b43181669311936f36b7595c13f46f111c1b49bc1b18c9fc55bedd45667483a7effc18e808f5159a09530f142669318d5ad90c9c2c768351373af5cb789cd938c40735aee33285036aadc2f692dc762caeedf5f870d215c7c1b5fecd512b83dce65badc32776ee0ae289eef405ad38683de41cf605690a078d504cea2c2e7436e3b87f2bf9a89f06ca8a9a79b91cc449dabb10818c178334db4ca84d41a17d390d07ac237423365864ca394acd3a1c17dec6bea0542ef51aca5929e4ce6cc4a422d79008b060ee30734f93bfd0d2b5114f4a49c2479f2d2a2761bd7f60fa028a66c3206a393a6025cb26c546c1244b6ddb13e7bfde0de9441da50655ba2d00085ec2d9ccdb2d58068b8e55d04aa4c4283b7b8626ea6e7458cdcf6ba6100be0dda7d2eb19d2b9afc2bb6ad54a1f444e388e5e43d5160fd81ac0fd1235f8479fdc8594e65b5e45e2227348206fa42fa56fe1449825b054c1ad68f2b8420b5e669af6f79d3d9f6c36e5d1d7e9dd9a04377fc49a38e8dbc54d5a45e50a9f5504911666d08c6720967d47e27b6b680731a22100bd337afccac15368ae597a8ac55a8c548fe999c5c2c1767d04c91e547032e4fdcefde964a4901c34e6f374c0a77d1afe3a5ba1bc85a66ca17f3bb12192f0029ca4e402547b2f9b60f67925f14c7ea6f30eab4d4a75110969326dc7631ae6090e665147ff29b6b0aaa472ec0fd57937813cb55ff2dc4b47d73775022ed80e10e2aac5bbd886f1c91a09dad3ae74f3aa71db848302c08145f59d5e56a476f9ad63326fd249bb7083b4a77e5e576685ef161c65bfc3c2beb424ecf1b320c2471171cec1f8c27b04c1f685823d84a3aaf4f74b2f1a613064a75500b84f7c3635836b17380ef09f24ab3dd708af2705201af775ea0422215d6ff0a616ed0e6d31a94aad6f55cc65fd60336380f28870b93cc861d9b2ee43293063c27620892065a0000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x7544fb", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc0408073d9e8c991bfcb7a2e0205c700a68359ed40dcf6912fbc02d2623971ce", + "transactionPosition": 44 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002afa9a", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x4bbae48", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008338808821a002afa9a1a000190f1811a045acda85820b24b3e1b897f42b136eab69c226a2ae7647adf103dc5fe05e67ef22daa0e65f8582074fea458846979d6f5d8383b77701ee74e32ded56a348a283c139f3019f64153590780aa6dd1b6cde27d943f35448a588dda962b90bb5c2d83fb2fc6b05c8879362c7e817ed132379a5d26eedc593a3c606d21b1ad6dce5e114d940ed6339ad15a75f332154afdfcf7b12d95582ee4a14202d965a951d4188d7c229f08116f19110bf10ffe6f3131684f4b81a83d8f134ba8b934c9fbba9de485da6314a6e2b4506ce66faabd643095fc29368353eccc4666ccabd8aa5e770a7a983fc357e401b33944c70ad9ca78bc9cfc28c89557f36c9211906843f318dc25c061094b004bd12cba8e086520ffd326f3770543b454bc2dde31928e03115ae3ec7827456b9d1686d4221d1bbd908594ae4765699cd49f5d35b683c356421c3b3d84831c9f848ede29dd011f0b7b2b19cd4247ba549fc81ba77a935573ecb2a8e51fe4e0cbcf360a3a19cbca7006ede49b7b6520c744849e6b4daa1f79d178a9247fce67547d3b07d2e19762f3bf18dba2a6f5889aa872a843948cd7ef996705951974d2335c4b8f5162f93695a1babd67b5452e4bc9ec673031a23f3e19238e1ce953df0d6932a2b5a9a4cfdf9ec7023cb3a3532211daee7979736978bf3dfaba75b44e9890fc1e324a6ff5740bb5d5a91b57d21795a62843af5e1fdc25d90e2b087d36c3d646f526a2e9336f937d80f9e03a855a08878f80da10ea8167836870d3b8abf9ebf2b5b60f001ad41bf0e78dd27b48569c587335e59482402570c61740c114af91f1b163699498bf6b0cefd1d1d211328ce8f0cd831770938798626b188d47787e3169c103fd8acf60aafbad26c69259c269e55aef8ef27ce606e69cace5bed0e8570864881b582727c096fab89d33c0e6d2d22ac2b3d02974110bd104a36be0f1d20bf3ed4d3c4c3fbdbb4165f534eb2adf466587effe414558bd05d694c75eb071cacaf21d67d7782d4bc7b42deb129286a96d00de11b5ffbe2f07502224b99541637018c19fd37d110a3636addac50bc20bfd8c408d014e4f41c3f6caaccdeb47057447de642bfd04b7a240d526879cd484ecafb45f07e4c4728a6df049534316e76ab3b751101a606b8f7741c30508c86d68075d24bde13a68276dd9c64f2bdc20798f8d3ff9052273f3407b8a255a5de6303c2a2a2501a1f0271a3996dbad35959ce3f69169329593faea319dd23d1aaa66adb32fc8be99377176e47de9538a55b5fc6f02ed74a494dd4546953b7cc772b649ed17a4c728ecb4d63ea885293926751387f8e6f1779acbc0c8a4aebbea14b51dd4e651f8b2055c6b307c041fca08e590f50bfd1066f7e074b7f98fd4000fc8a10872fd115e4c74a21c9129fb0230ae27f4af10bdbcd02cfe96792a10628b0018ef8cf7984ab0b2fa34a9464404500c8e2d6945935a22a534962ea8ea448da54d79b8e53d56b9c2c7ad7de5b9003476d530f1bc1f7a3371a4e1dddffe93974eb03383e315632977426589e4a13022b55ad361150a48c42c35ba475848dd8a6ec957c7c1a6525830eea4ca05175c3cfb0a5c1faff0338023961101aa2cf5458b1ec14e1c958ee0a8048acac6cdde743043847c2c8951d708a00247e19a8fcc1f90325533a773ffbe795e78498ffc3d81267c2ca13eb7b9fe362ac30518537653ec47bf3bf172ec0acd9a65d9e8c16022a6ced9030033964a3505894f0621c9be0268328a4826c7596ea3f21a6f404126dcdfda7063e9e70175d012424b890043a619d74f99734052b262f7983646b7c273061c898341b4ad3a46e0c825790b4f8b43181669311936f36b7595c13f46f111c1b49bc1b18c9fc55bedd45667483a7effc18e808f5159a09530f142669318d5ad90c9c2c768351373af5cb789cd938c40735aee33285036aadc2f692dc762caeedf5f870d215c7c1b5fecd512b83dce65badc32776ee0ae289eef405ad38683de41cf605690a078d504cea2c2e7436e3b87f2bf9a89f06ca8a9a79b91cc449dabb10818c178334db4ca84d41a17d390d07ac237423365864ca394acd3a1c17dec6bea0542ef51aca5929e4ce6cc4a422d79008b060ee30734f93bfd0d2b5114f4a49c2479f2d2a2761bd7f60fa028a66c3206a393a6025cb26c546c1244b6ddb13e7bfde0de9441da50655ba2d00085ec2d9ccdb2d58068b8e55d04aa4c4283b7b8626ea6e7458cdcf6ba6100be0dda7d2eb19d2b9afc2bb6ad54a1f444e388e5e43d5160fd81ac0fd1235f8479fdc8594e65b5e45e2227348206fa42fa56fe1449825b054c1ad68f2b8420b5e669af6f79d3d9f6c36e5d1d7e9dd9a04377fc49a38e8dbc54d5a45e50a9f5504911666d08c6720967d47e27b6b680731a22100bd337afccac15368ae597a8ac55a8c548fe999c5c2c1767d04c91e547032e4fdcefde964a4901c34e6f374c0a77d1afe3a5ba1bc85a66ca17f3bb12192f0029ca4e402547b2f9b60f67925f14c7ea6f30eab4d4a75110969326dc7631ae6090e665147ff29b6b0aaa472ec0fd57937813cb55ff2dc4b47d73775022ed80e10e2aac5bbd886f1c91a09dad3ae74f3aa71db848302c08145f59d5e56a476f9ad63326fd249bb7083b4a77e5e576685ef161c65bfc3c2beb424ecf1b320c2471171cec1f8c27b04c1f685823d84a3aaf4f74b2f1a613064a75500b84f7c3635836b17380ef09f24ab3dd708af2705201af775ea0422215d6ff0a616ed0e6d31a94aad6f55cc65fd60336380f28870b93cc861d9b2ee43293063c27620892065ad82a5829000182e20381e802206742e56094a25e9cb8ccfd548669001e885fdb05e5a3a32e414f33401d1ece41d82a5828000181e20392202072f2ba4a22ea83fd98e49f73658227ca7d4026d6c093aefc824f23e5a5acc71c00000000000000000000000000" + }, + "result": { + "gasUsed": "0x371397e", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc0408073d9e8c991bfcb7a2e0205c700a68359ed40dcf6912fbc02d2623971ce", + "transactionPosition": 44 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001282b2", + "to": "0xff00000000000000000000000000000000127dd3", + "gas": "0x1d3a22b", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285078182004081820d58c0941484e4cc646ac06322666cfc99a0ed9ad905db567a1cc54211cdce6f42ee3dee60352b2be6a23aba9bba8e794ff0698928e0a8c7f0bd2ad8eadff2ce5e9750579598a700988bd54af7e4667fdd1ae926725280acf36e1ab80cc1de1601e8da010c876138e492f250edc21b8b59635fe8a8f7c7337b6c142ef29b76757b5145e1e9882e664b43e8a10ec295667d9cb2a6b2de7f8c3b15035cf8c7865767b31d6e2f2dd243b61e8abbc333640b050bb8524f7c22950fcf01306c1fdcef61e1541a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" + }, + "result": { + "gasUsed": "0x183032f", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x8643bb35c2d74917f7c4ccc8b797edc5c71e1d27e5cf731220ffe82a3533e984", + "transactionPosition": 45 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000015c8bc", + "to": "0xff00000000000000000000000000000000018a9c", + "gas": "0x2eafd6d", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001b6851682820a40820b4081820d590180b2eb828eedbe33cad6f19452c4a64757c82c436f25cd9eaf8e04e32b77ad8322faf0b5c2faa9b927cb1f9c8e0bf7e943883c08d938f92ab436ad40321f8cea79f55f2011801a09c8728506c0d208f370617e3559354b5cbccc6e9e37493999a604e8c9020ac8f8ae7ec6318c8f907748b717f990f97a5f3bdefb656bd8e1ab058e90e1ae54d200c60da283573c6462db83164589345e177a4f12c229c60965ce0345ca9f4f375464dd4c4b22e5fe3c4b93c4a566e55f4ae5d2f888c22cbdfb3aac072a09b8d53f522a24aaef5d50a5cd405d81980c744454908cc3198eae1549d77e8f2f038dbc7600bae3709738870985f222ce07642e324042fd34bac9d89c17e327a10f5e6f1b1e15af4e85682eb5737255793ac3502890b72403550aa5440c8560e2634fc9d63ed0dac44bf3d1162666cc01adea437adc6ad2b5bf16c3e16c850c6d914054f5eff597510445ccd98600f5f6e75e683e13a942bc4c3ff3d26f7dce9ea5ad9281809f01018ea7c7a93991336e2358ad44f5094686cb6ea4441a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000" + }, + "result": { + "gasUsed": "0x27952ea", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x0cfb366083cce141ec4cbcb7fbc844164a6d689001b4fa1a5233ba49ac2a60b8", + "transactionPosition": 46 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000015c8bc", + "to": "0xff00000000000000000000000000000000018a9c", + "gas": "0x367a5bf", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001b685168282004082014081820d59018083cfb7899f78e9abf3b68fb49960e041030f4a1111d343aaf21b64aff5cb100e76bd22d19c9b785283ebfe8678bc4df1b758afeba700e80c3821f5cdcbcdcb4203a09c02283c38eed8602a4fd85cf2b7ace368e0870aae6f09269d4e2f84861b1737f0af1660fd760a0bccf250bc131510d151ee15ce9ca7949bea9f981dca6bfb9f1c1201a464982345ea31d5188afa8eea3801c8edacef9734027b0f51aeb2abb58399a43c27189a368f4baf04c151cec24704723ef65ec928f465d7673501b5e895e3968a207457fa85d74a28b0254d13d8ac435c1413fba0e64155ab4344403690d1191e9240699cd5798d27d7cd95e88ad5077892b556df1102216b13fcbd8b10a2225da004b1fd134292441bf70a721f2a846a15d6850866f59017271e016aa391968b25a1ba3ad81fd844601bfd2b57d7a40a2fc036f457dba80b4ebb2399677fe3419026d308eb9ab3e49ba08e0087e610c6f3f02f14f180549c697687f4bd3f0ea6d51ad0f238a320adc310b8a86f661a3ec01b30664a771471383d1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000" + }, + "result": { + "gasUsed": "0x2f3564d", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x6e808c2937deb1c22c515ef2023ee58c101a4576db5d5e6ac159ac61ba036e6d", + "transactionPosition": 47 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000015c8bc", + "to": "0xff00000000000000000000000000000000018a9c", + "gas": "0x39ef4a2", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001b685168282064082074081820d590180afa14917d4144b56496b6cc74319d2098d173c50b0da49bb9512bed6dd7595601cfa80897e804817c0d2478207f71d2587dd46bfcf6b13cf6521add3b7652f47e4639b19027f26aeda2f1b3722110d54cd360c30150a3bbee48dc463e3e663cd0c21b12ee7ca34be107976d7b795d7dd4112d82dc88b762fe79698d0125e512e1a44c16134cc62e91459213c2fd61bad9562f6f79e1886637d4643ffdb1c6b73d81fa1c9340db4bd820e616bb6f6b95ab0a3ef8ad4a66493ee9a309a9e0a7809a0f7cbf76c61839805069b4e4877f198f05b1635a20c99068ab282c61cc8f3bc39f7ee82a63bf4b31edee162fd63aa4491f4a1f7ca6315e8d9821e782ce077ebc413911b951bf6d428541ac0953fa6ad6b0b6ea69baf7385e9c6c3aa4a649d4d09cd4b65224f6e84399d0833ecd50cc64e2ce13f38eb38e9988d7f1191f67e85e80460abb16b217cd2d086157df72c71875a36560a72f5fa0e8bf4575d9de99122fb62eefa0dcfaaedbff79ef30a2a9d9406468359ee9481cd403de8fd3de2321a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000" + }, + "result": { + "gasUsed": "0x333798d", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x3208378ba92f068351f798d2e18b7515acc2cb3706ad08828a25fa04bd8d5176", + "transactionPosition": 48 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000015c8bc", + "to": "0xff00000000000000000000000000000000018a9c", + "gas": "0x2df8316", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001b6851682820c40820d4081820d5901808b127976370cee8e21810fef9e01d393ed9157f129f195e89183a3b3986dd47d6b1fc50411f55fa62e3e8a21b87432f5b4083668c65d107e89a712a69228bc8f9b8434cfb6cce55292911f7259ad33dc27b52ce79130347f460b6cd9af4a1fc201fa009bfdec83897255da344d1f87b4549066a6d1480d2e45d78817a48496d9170fea27163562954584ad441d094b90b70ad4eda0a538df1aa2c98ad11c53df1527b388ba6d985f184749d68c6755f589aa44158cb62b25a216a65a6d34707c8e1964f519655f563b7d8176595a9962081e4080e7cc1222eb61b8fbbef1148bea9dc54cfccf8ad8a8219e4743f42d1cb8d068aee2c128a2e7078d273ceb80b1b5d0be48e05c89220f166e09f24a92d4fc1653d899044f27801cd7be7b60516b082a25bf8e584af5d2ab269526b640e2a9b15d8bef2746851aa32ff49440a5d422503b60e4891e4406dca42d2df507f6a69222360c3a8d4974bd664bbb84996665e0e006c354d5270219b23a91b13f61b74352e5c977ef9a334ca6e8dfef7c681a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000" + }, + "result": { + "gasUsed": "0x2c63cfa", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xa92fbc3e483686a903d03dad832850c63d219c087a9560d8dd11846aba05d051", + "transactionPosition": 49 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000015c8bc", + "to": "0xff00000000000000000000000000000000018a9c", + "gas": "0x277b6fa", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285168182084081820d58c0a00fc058f003e9ce71e5c7f0110c27df98ac84029077d1a9b2744f7e0151cf7f37003c1eecdaca3938bdbd868a115882aff1ebd7e304411ef8bae7829e72f002b092325c39e5b2e81b27daa449d1e68e653f07b8bedfe8280056265a1f69de540f5bbb84169d1bc2066279ff20e66708357d446408e3da7c4b690b0f87ab40dce7a3f32066eccc99e74612d6ece3570caef37ce3559cfdde7e2415c47ee44bcc21d4f14974bbb91207ee9bc4597be61fea4b52f2f210f58bd1ee7ca856fc56461a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" + }, + "result": { + "gasUsed": "0x22bd49a", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xac6b89882cdc6aee6b38cd3e2c2ddaa63c2d4f48c431cf8298e24c5d95af081a", + "transactionPosition": 50 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000015c8bc", + "to": "0xff00000000000000000000000000000000018a9c", + "gas": "0x279471a", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285168182094081820d58c0a16986848d181bf1de1caebee43c1eccd53a589850e2a21b63e3fd4c7157be80c013abb17ffeb957d5064bb729f24be8a8a1f9bc526100fde11a14cb850d2773204d90747c7eeec3943bb2ab043b1c7427245198329a88920423807b1d378a1d05ecc5761e6fe291dc6f1a826f8af0d213406ffa54176aa3954fbdc6875bf14dab7c3b3e37973d09e7c2501bd3b8a3dc8a9f1a14e911342e61f958afb3765d537f4442254d53cf384e210b8f22e897d2fb2da104c1cb7167f1b1dcddc216105b1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2381302", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x66969ead995791added4e5ea61637c9f338e94808ab77f703ff227e63cd158a9", + "transactionPosition": 51 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000000c4d75", + "to": "0xff000000000000000000000000000000001536f3", + "gas": "0x210e371", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001b78518228282004082014081820d590180ab8892f0b4c2c9054daf7a9ed12d1b5a3a216bf4c540c2d12ff2f19b40e14a2aaee5a22012b97888bf5e150a69e6561f850dbdc6a8bce7a292800a139bddff40a363999f600294b6b7490984d83af7f92bb98c27df4490661b5bfb662ace46f20c62a9fc0db2401dc84f0548f8cb6c2bb8d57ec3d520d49aea2dc2a6416dff3072b2b901948d6474c794484e2e0fc90b95856412e33b02c965f56065eaf18a042545cf1094f16c16f0a70f671d11276a73633fd4aa4e0ff4d261d07298950c25b3b9dd698ea90ef14758e2d760a8339b6e893c97bb5a0797c9b0a199bb6dc7fd82dcdb58c3f4e8e7b525823cc28430d280d85fd1c4cf59019d8387b0edaf3c136d04bdceba76e8942efb9ec1f642d2db2f61e50e5c6917ef2485e272d4ef047712b5b7e6ae6c4621ea2cff3dabab4cd71c0bd559460e39d5f6e571006c3286bf72ec724e9bd5d9f8ccb0abd1557ef7408c3cc690e1e7ba8f4b0fb87d447b6e66cb34b90c3150ba3a2bd623fcc09ebefcabf62aa3bd01c63308c2428272ace8b01a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d69000000000000000000" + }, + "result": { + "gasUsed": "0x1b7284d", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x957227ab77c764b14f4df2b5f2a60da060bd4da8f8f9f8ae87a67f2188f134a6", + "transactionPosition": 52 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002d0792", + "to": "0xff000000000000000000000000000000002d0798", + "gas": "0x5a9136f", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000787821962b1590780a3e5852016727b45737aa29963044c46e1db6470429aacf34f13901c0f14737eefdbd5397827482d83c172d144687b9a88ea422d78dbbd983dfc149f18e56512cc94e94c81b34d5dab65edf4f2162c96a23f9a7e64389a6d5687930180c5777116996e4921375c5c20f43a201b95a204a461e4641476649e5cca5e164c74a8bd4a6598ded80a57ef8b1fba959de531e8a27a73a56ac06909e5a71852b04441cabb5675d2012b93285232c8e631d8bd76659134bcceabedadeca603df88a21c3db6640598a40b0df2473d6fc3869229a85efc0636dfedb4d34eca5b74904bbd5ed3af1cbb1f1a87643ed8414835ee9eb8a4825f62c74d7e750e5ec9fc5578548b78103536969c97d1f58ddf1acf39527e335827f8a78dd2d0a25419032819f99b03b66aa0fb3abb3f03068f34f03f0a8659bb6f1c16b80534eeacd0eaf88493a0ae91dfb25e5f06a4dd152b29a2c6498fb22755da0e514ab417656a676ca94d3b621810634ac162decd849c9488542d6d2fc73f293cd670884391b243947c42e6a310fca6b6f11683aea97b10d7040cbd42cd18c3c7b1f74ea9465037a82639898baf55c881719afd3b515668eaa6d42b8cf88178ec5656af49760c56f3384ca50305479afeb610aa3c73637f1b5f454a8eb775bee088a34fbf1008a303afd43d114b09ae243b41895bd07cba783646fc19a54f9e1026fc5ec214c6147795dcc334b6836bed237f75c03f70f81bea01c386742a25f408582d0cd4feee4b1d842aa45169e841cf4047bbb3774cf4e94989255905aad75a10ed978c9643994b39deaebb37a3a9d085a5161d592c536992b66e7ed7652b41676254a339e4cf630f7c1f8d067d558ea51a712e19f02829003589c84b321cd305f2b22dbf4777c6a04df233f59e49794948b97539b73bb1e801cd4c6d55b2462cee225939279de6b5d509a6c1ae5eec57cd9ef5eae92ecb9a6d83c1afce2371e1bb740975f4aeb53b57e6c8c3306d35541cd8bdb0e4ded06ff1aafa1fda68a352fa08ac3f2d3d878030af72503dc5edafa040e3f86d4dd393c84b75622c2579867f2aacf2b0fcc871d6a29ea710940a052fa9fcd7b88e61b629b41569616cf37756928a313e6c6e22e4e49ee9d5ff55bdae517a645d5e4ae3258b97737f906aa1f033d50e76e7d7994e9e2472ee6bfdb2b87ba54d0d85d81417674264deb6d747a2de5d3e8719ce7e6f0c510da24e1e3bb2c83810e9dfe19c5d69908183132dc368ecf12f57515b980bbf22bcf06fb059167910897ab200517795dcb31ffdb04466e78d000310859edb2e5cc76e85f6bbf79f1aba4667429b24b25e698ac37f20e24ac9fea4e7ebac408106c36d4939a70a027a838f3f561e41f3b084a773980f2803943d3145a5a5742fb373dd991e3b29f2746ee4820e19a1b52dbe9dac79b36204f52a85368cb507d93b7ec6a4935258ffacd2111459d57ed8b959e19627453384708da76ec1176404ad527184daee6523f76681a6958aa405e33d7335204474a9cc5ba2a7dbf1d556629c54ef419af663dc6c87b3166b1db21db1a772a94e119da2d83a9923bf6d08e50660df19f2ea19ba1408458ad5163911d0d5053993569f7c2feb04379c4db9cb6dc5bc2304138b9aafb4ae1165651a181bf252936af0c1450d9906bac56a379f27153b12424a645b5dc22f646e9483d5e9212adc5ef5755c491ee76be107d2cd0b0945a7d10aa5433be44fc36d806a83e064d88f9b1ffa85ca9428f1483c08ee4519fc35692df29f69fead901ba40966c150d96c54e7bfac3bd02a543d881d3609fb7d3956b942df9da3fe417ae9a6dcf2454eb7987ff5437f12cd8d77e15cdd292bd02d7480edf5b28dd6667ab3b0fbad18f5081b4171b6b3c92d8c3090b6d7f244272825fef7d2c3ba44c68ede30cdf681567a43a18d24d65948f09884933aa0eef904bb940b3dd1a92bfd67e6abc7eeb148aad962836b622007653043443474e44c2042c2e353bf99b556853d26a4d314e715bbbdc9073ffb98ec6cc40fa010c562c6b123ffff8cabfb1038a29a489899ef8d6dbe2031f073ab9f4d82582df6660dacd347dd6d207174f18db4977c669d3ae04f6d9fc5e1b190358c7155c1afb1f18e022d0207aaa724aaafc2484478ba1cd7abfb18046cf04a59eb36a313ab0e6cc23799943432d828ff486adcd3a6cd078cc8a931fcd0bf2c392e70daa6ac9c1def1e3a249fc6db7c451beab38b40738eba8e40bfc26b0b9580aacce29745135024154074bcd74774c8173f388b9a41ea5bbf36f8aec182dff1506f097f5a7aeb71749767b80b1ad9898d977c9f250439c8baf8114f94a7ec61f4ccd958f4680144ad1558c0e075d3b0d8a4aa38307191765bdadf1c62fb19b0e5b995bed7898195d30ddc393b2cd516976ea358e10882af8013947be9d0a82803e18d12c5f3d90e382a0d336413382e47014735c63bc04063941fdf5a8a5d1b57738a501ee1b50b185033926bc90480e0b8a991a693a610fa869245301c023fd291efb61b60d1af0f0f2e2080e1c17ee21ed1a4c6aa53c232949a1a0e2d325d0d3309c7b7de30811c7b1e2872918b3c2cc4f055e2b9afa7ade34b7462b95a8ed657248bd379407c57e0c0243aefdaa54b0597cc4206a85b6db894d534e00b91c07d7942e29107bb4dd5db5749e790e7a57bd23d50af35116d1c15f38e7ade6baa7000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x91d33d", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x4de7b42d21f0a27c46254aa5536ddc1ba165322f56ad47655d256a8fdbcc0839", + "transactionPosition": 53 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002d0798", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x54822a8", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002d07981962b1811a045b2a6358203fd3e68cc4519573cd488191e2be8fb213cc957966f7ab315c24afd373103850582058eff1bd5d4366dbe11ce747891d3e24826b623b4459903b6ab586fda7f31209590780a3e5852016727b45737aa29963044c46e1db6470429aacf34f13901c0f14737eefdbd5397827482d83c172d144687b9a88ea422d78dbbd983dfc149f18e56512cc94e94c81b34d5dab65edf4f2162c96a23f9a7e64389a6d5687930180c5777116996e4921375c5c20f43a201b95a204a461e4641476649e5cca5e164c74a8bd4a6598ded80a57ef8b1fba959de531e8a27a73a56ac06909e5a71852b04441cabb5675d2012b93285232c8e631d8bd76659134bcceabedadeca603df88a21c3db6640598a40b0df2473d6fc3869229a85efc0636dfedb4d34eca5b74904bbd5ed3af1cbb1f1a87643ed8414835ee9eb8a4825f62c74d7e750e5ec9fc5578548b78103536969c97d1f58ddf1acf39527e335827f8a78dd2d0a25419032819f99b03b66aa0fb3abb3f03068f34f03f0a8659bb6f1c16b80534eeacd0eaf88493a0ae91dfb25e5f06a4dd152b29a2c6498fb22755da0e514ab417656a676ca94d3b621810634ac162decd849c9488542d6d2fc73f293cd670884391b243947c42e6a310fca6b6f11683aea97b10d7040cbd42cd18c3c7b1f74ea9465037a82639898baf55c881719afd3b515668eaa6d42b8cf88178ec5656af49760c56f3384ca50305479afeb610aa3c73637f1b5f454a8eb775bee088a34fbf1008a303afd43d114b09ae243b41895bd07cba783646fc19a54f9e1026fc5ec214c6147795dcc334b6836bed237f75c03f70f81bea01c386742a25f408582d0cd4feee4b1d842aa45169e841cf4047bbb3774cf4e94989255905aad75a10ed978c9643994b39deaebb37a3a9d085a5161d592c536992b66e7ed7652b41676254a339e4cf630f7c1f8d067d558ea51a712e19f02829003589c84b321cd305f2b22dbf4777c6a04df233f59e49794948b97539b73bb1e801cd4c6d55b2462cee225939279de6b5d509a6c1ae5eec57cd9ef5eae92ecb9a6d83c1afce2371e1bb740975f4aeb53b57e6c8c3306d35541cd8bdb0e4ded06ff1aafa1fda68a352fa08ac3f2d3d878030af72503dc5edafa040e3f86d4dd393c84b75622c2579867f2aacf2b0fcc871d6a29ea710940a052fa9fcd7b88e61b629b41569616cf37756928a313e6c6e22e4e49ee9d5ff55bdae517a645d5e4ae3258b97737f906aa1f033d50e76e7d7994e9e2472ee6bfdb2b87ba54d0d85d81417674264deb6d747a2de5d3e8719ce7e6f0c510da24e1e3bb2c83810e9dfe19c5d69908183132dc368ecf12f57515b980bbf22bcf06fb059167910897ab200517795dcb31ffdb04466e78d000310859edb2e5cc76e85f6bbf79f1aba4667429b24b25e698ac37f20e24ac9fea4e7ebac408106c36d4939a70a027a838f3f561e41f3b084a773980f2803943d3145a5a5742fb373dd991e3b29f2746ee4820e19a1b52dbe9dac79b36204f52a85368cb507d93b7ec6a4935258ffacd2111459d57ed8b959e19627453384708da76ec1176404ad527184daee6523f76681a6958aa405e33d7335204474a9cc5ba2a7dbf1d556629c54ef419af663dc6c87b3166b1db21db1a772a94e119da2d83a9923bf6d08e50660df19f2ea19ba1408458ad5163911d0d5053993569f7c2feb04379c4db9cb6dc5bc2304138b9aafb4ae1165651a181bf252936af0c1450d9906bac56a379f27153b12424a645b5dc22f646e9483d5e9212adc5ef5755c491ee76be107d2cd0b0945a7d10aa5433be44fc36d806a83e064d88f9b1ffa85ca9428f1483c08ee4519fc35692df29f69fead901ba40966c150d96c54e7bfac3bd02a543d881d3609fb7d3956b942df9da3fe417ae9a6dcf2454eb7987ff5437f12cd8d77e15cdd292bd02d7480edf5b28dd6667ab3b0fbad18f5081b4171b6b3c92d8c3090b6d7f244272825fef7d2c3ba44c68ede30cdf681567a43a18d24d65948f09884933aa0eef904bb940b3dd1a92bfd67e6abc7eeb148aad962836b622007653043443474e44c2042c2e353bf99b556853d26a4d314e715bbbdc9073ffb98ec6cc40fa010c562c6b123ffff8cabfb1038a29a489899ef8d6dbe2031f073ab9f4d82582df6660dacd347dd6d207174f18db4977c669d3ae04f6d9fc5e1b190358c7155c1afb1f18e022d0207aaa724aaafc2484478ba1cd7abfb18046cf04a59eb36a313ab0e6cc23799943432d828ff486adcd3a6cd078cc8a931fcd0bf2c392e70daa6ac9c1def1e3a249fc6db7c451beab38b40738eba8e40bfc26b0b9580aacce29745135024154074bcd74774c8173f388b9a41ea5bbf36f8aec182dff1506f097f5a7aeb71749767b80b1ad9898d977c9f250439c8baf8114f94a7ec61f4ccd958f4680144ad1558c0e075d3b0d8a4aa38307191765bdadf1c62fb19b0e5b995bed7898195d30ddc393b2cd516976ea358e10882af8013947be9d0a82803e18d12c5f3d90e382a0d336413382e47014735c63bc04063941fdf5a8a5d1b57738a501ee1b50b185033926bc90480e0b8a991a693a610fa869245301c023fd291efb61b60d1af0f0f2e2080e1c17ee21ed1a4c6aa53c232949a1a0e2d325d0d3309c7b7de30811c7b1e2872918b3c2cc4f055e2b9afa7ade34b7462b95a8ed657248bd379407c57e0c0243aefdaa54b0597cc4206a85b6db894d534e00b91c07d7942e29107bb4dd5db5749e790e7a57bd23d50af35116d1c15f38e7ade6baa70d82a5829000182e20381e802200c82b78d2956e18f54a7f16aeb3aad9d317d478d5b3212fefcc83d184999b859d82a5828000181e20392202096242ea129b2d38f1a44eb5dda71f9cd9f697abd137478959f807b834680c202000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x3db1b4c", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x4de7b42d21f0a27c46254aa5536ddc1ba165322f56ad47655d256a8fdbcc0839", + "transactionPosition": 53 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce389", + "to": "0xff000000000000000000000000000000002ce3be", + "gas": "0x2fb7db3", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708193efdd82a5829000182e20381e802207f4d07b116b6300ab7afb497429e9b845c6b81d1a4aa8b3f7dee0fb9c2d5ad5a1a0037e10e811a045365671a00480bc5d82a5828000181e203922020a2e68e2e80ebeaca7ed536bb450bb1c4a8577e2209135546f2287009cc45671b00000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2042bc7", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x8912c515d71922148eec6cd24d28673f3920b413bf2ced0ae6847f79c001d58e", + "transactionPosition": 54 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3be", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x2f0178c", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x8912c515d71922148eec6cd24d28673f3920b413bf2ced0ae6847f79c001d58e", + "transactionPosition": 54 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3be", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x2df5244", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x8912c515d71922148eec6cd24d28673f3920b413bf2ced0ae6847f79c001d58e", + "transactionPosition": 54 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3be", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x2cd3cd3", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a00480bc5811a045365670000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x4888fe", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020a2e68e2e80ebeaca7ed536bb450bb1c4a8577e2209135546f2287009cc45671b000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x8912c515d71922148eec6cd24d28673f3920b413bf2ced0ae6847f79c001d58e", + "transactionPosition": 54 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cf0e9", + "to": "0xff000000000000000000000000000000002cf0eb", + "gas": "0x3f8e7b6", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708194760d82a5829000182e20381e802205bd2105f384ad53a57500cfda364fb6d48557fd75a9da12488bd97950b8e22031a0037e0db811a0453be491a00480bc7d82a5828000181e203922020133508dc0c6fc1cdcb063ef40e4cb11f4165e5d52f9bf41ca2f9ceb1f1e5701800000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2cee852", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf698e990a7ea335ddf046c92759be8d87ced39bae42e1295308137ce727281d5", + "transactionPosition": 55 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cf0eb", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x3ed818f", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf698e990a7ea335ddf046c92759be8d87ced39bae42e1295308137ce727281d5", + "transactionPosition": 55 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cf0eb", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x3dcbc47", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf698e990a7ea335ddf046c92759be8d87ced39bae42e1295308137ce727281d5", + "transactionPosition": 55 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cf0eb", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x3caa6d6", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a00480bc7811a0453be490000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x4887a6", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020133508dc0c6fc1cdcb063ef40e4cb11f4165e5d52f9bf41ca2f9ceb1f1e57018000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf698e990a7ea335ddf046c92759be8d87ced39bae42e1295308137ce727281d5", + "transactionPosition": 55 + }, + { + "type": "call", + "subtraces": 7, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001bac42", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x13c70235", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000eb8181828bd82a5828000181e203922020edaa33053d8a5f916356a3ff18ad24fbda73e61e7d6bdd4486427e9bb61f180e1b0000000800000000f555010f29c20971f6e29cead00b57277fb5975fd83f914400a29f74783b6261666b7265696234616d7a696b6578787072753432747579626c7a35777736776636776a6236737935367a747863636b62696d647374627966711a003814d61a004f81164048001b6a51c29fe8e040584201667d08b321901df7ed569ed7f84c82d3b69c25b4bdee7b916eb19319500c778b06b89684eefd98e18bf8f5086c272b4c5fc53ac344a1b17e9d226612077b346f00000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x8c4173b", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000982811a045b5762410c0000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", + "transactionPosition": 56 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff000000000000000000000000000000001d0fa2", + "gas": "0x13b36e85", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000014c1cb970000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000054400c2d86e000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x13301e", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", + "transactionPosition": 56 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x139f99ae", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", + "transactionPosition": 56 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x138ecdce", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", + "transactionPosition": 56 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 3 + ], + "action": { + "callType": "staticcall", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000021f2a6", + "gas": "0x137bc7fd", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000ea825841667d08b321901df7ed569ed7f84c82d3b69c25b4bdee7b916eb19319500c778b06b89684eefd98e18bf8f5086c272b4c5fc53ac344a1b17e9d226612077b346f0058a48bd82a5828000181e203922020edaa33053d8a5f916356a3ff18ad24fbda73e61e7d6bdd4486427e9bb61f180e1b0000000800000000f555010f29c20971f6e29cead00b57277fb5975fd83f914400a29f74783b6261666b7265696234616d7a696b6578787072753432747579626c7a35777736776636776a6236737935367a747863636b62696d647374627966711a003814d61a004f81164048001b6a51c29fe8e04000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2e9853", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", + "transactionPosition": 56 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 4 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff00000000000000000000000000000000000007", + "gas": "0x126d96d9", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000c26ddbd50000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000064500a6e587010000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2ce23f", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000104f001206c3fe6bb46656ea1e89d8000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", + "transactionPosition": 56 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [ + 5 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff00000000000000000000000000000000000007", + "gas": "0x123e221e", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000d7d4deed000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000067844500a6e587014200064d006f05b59d3b20000000000000584d8281861a001d0fa2d82a5828000181e203922020edaa33053d8a5f916356a3ff18ad24fbda73e61e7d6bdd4486427e9bb61f180e1b00000008000000001a00176c401a001b60c01a003814d68000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x378d0ea", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043844f00120654f8b6172b36ea1e89d80000500006a8d15c1acd58b4e5110000000000520002f050b9c93842f9b9dcb15680000000004d83820180820080811a034d18b40000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", + "transactionPosition": 56 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 5, + 0 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000007", + "to": "0xff00000000000000000000000000000000000006", + "gas": "0xf2d40e0", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000de180de300000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000006e821a85223bdf5866861a0021f2a606054d006f05b59d3b20000000000000584d8281861a001d0fa2d82a5828000181e203922020edaa33053d8a5f916356a3ff18ad24fbda73e61e7d6bdd4486427e9bb61f180e1b00000008000000001a00176c401a001b60c01a003814d68040000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2439f4c", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000d83820180820080811a034d18b400000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", + "transactionPosition": 56 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 6 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000021f2a6", + "gas": "0x4c7ce06", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009c8258948bd82a5828000181e203922020edaa33053d8a5f916356a3ff18ad24fbda73e61e7d6bdd4486427e9bb61f180e1b0000000800000000f54500a6e587014400a29f74783b6261666b7265696234616d7a696b6578787072753432747579626c7a35777736776636776a6236737935367a747863636b62696d647374627966711a003814d61a004f81164048001b6a51c29fe8e0401a045b576200000000" + }, + "result": { + "gasUsed": "0x9858a", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", + "transactionPosition": 56 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cbdce", + "to": "0xff000000000000000000000000000000002cbe59", + "gas": "0x5b6dcdd", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782196cb85907808da266f0d295889a05be62ca726ac93cae2bf1976516514387a04f6c161b95f727f97fe04494e7eed2533dfa309ff933835dfcf8a37564e3ebf5e4f2a7c0048aa707c303814028b15d44a34184d6e88f422defb35d333642a4592eee09398954097c2bb5290d2d989947f61ffcfbb57ff4efd12c5f0bdbe44e97487e45aeca92cdfd5678303f0b20617dc9fb65ecc734986207f6e60aeb1426702cebcba9b1e3e99b1f05d922b877818dcea601b115ee1794c04f76b666de9bf218f78f3bcb44b1c2f38368f86d53ce36b72bf6517d1a5f289da6799cbc2619403ea09b6f2fe13d598586938debb8a2e28762e65540b2b5401175c6e0533f20065b308444fcdd2462733270fba4adff7c4f1e9ac2c311b0c4e2fc6304f26ad4f1700a849eecaa0778bc7133031c1de707450c8fb1dc14f13789ed53844ca17939d2bc948ec550d0c18282b578e1228c7fa698c47611c28933cc0ae919ea14bb1aa6f75a01e7c474660eb686047eba0742f543acce9640bb67b163874d4528c7ea10b78fbd2a9180d69cb3e6477e8076fbfc1331194b04165be5e67f5161067e3b462edae40b9696d0d5217a1c20752df202384733c238a2a0b1ed16038f3cc7cbe51848d47d9031302870450538e8dd0022e41fb115ef3a4d9c4581a9229ab5385dce0753fcde17e8ed5bd77e3d3f865bc9f2da86ddff535786f72c93627459d5e4953019b9e71a0a13ff2fb7745b9a770c2ff2147edc8bb388730ff2c7b60645eb02a3dd96c6987d5cfe6f2fc254916d37c4589807faccdeed6595c5ed36d3bee947f5600f74b7737eaf6da28e3ba27ef9bd8b2075819620049af7b0034a55355a032fd807838dde66f7e9fee2aafa916f8c481bc8fca7fd868e1b39433a55bb96819eade540d63a2541f6de1ec9bca1c9f5545ab9b87481738c6868c3173daa50e42d6ca2c70172e512f1a7716ad8fcaa273d14d22466de3b18d9ac596b99fbfe1b50dc78a43042057415f19d79a9fb0243639303eeaccd138b5e8ffd0eaa1afa6418b7acab26e09458079a866b5cb655e0e2c9193a3be567ebdb8be7aa7b7ec4ca5726490798ff39a69bc1d15c8b4db2258173bb90c15ccdd3a38f0761a67a2893b7a176478345af99a4f151d89bf47addc9c19dc0a1fc7b8cfc2777cf6f98690feb3024ee5e393f63430d96d88efd01077fb4793b1a1369c529f05ccb8631a86eda1496c700095d52127504428f372d5dfd5b0c62aeed47698c64685eabd5ca056c3cd59f1c352de9b6aaa4ad2e3cfc3ea8ca119db8b24685432c4b2e6904c8b2d9ebc40585f8e37d77d585bc45b24448685f85b9061254e159b4d19ea81a33a3ee180e1ead0492e74fc2f4223ab2fda859b7c5e5abeb763194281c9b80ec89d0e1ef85bb4cbeb34dd238cc85f4f4b916ff8a3b47a9a18925f84adbf8b425970f6b2fa58789473fa2092f619365699266bdf2c84d80fa2f580f2135ea744abe1d6522a98b018b0c48e390f635a4e53c8df2c17b981d8e9b9ecf20681cbe9b727ec422a107486effcc2eb1255b4b16dd9ffda0d317834a3dcaba0647ffff274bbafb78225ed55d8a91e5fd5593425ec96db174708fae8ef62c34bc529fbe0ce13b52fb99d09288a63914fa099634fcc6af402b29dec3579afaa5136ae438ad32576e0a401d9e9da4dd79dd76992b02dc924d7b83c8b1f01ca4409fe937525b7772962430e8169a122afe5535e4c05af5001c96c548ddcdfbf2e313846f4422efc1b7d6399f022926f0a45475d6a1fc2e8dc9e1da4ceeb78ad725f324a5a495e4af104b244d3aecad990a3bf0538bc13afd284af720833bf6e0be96192104f181450c28bb8da27e81dda480c36dc01f13a33989f2cbeb16b295fe2b2fcc6f964fec34622e66a1ce4cd850d06e17ba099287e812603fdc936768fc2f0d13256fb56cd98b546906dd67c8f9c3724429cd8df899588c71a08404ad8e7443b3d19d3614fc7be29a6c7ed83efbf70d3d642c4673ef2622f45fa6ca7cfba8d0c1c49b40caf850a218158cceea53ed7379476f03114197df49e352e97677764c0d050c24155edfc4d1089bafcd0666161acc6f2b53bad7648f9564a9cdc969b22d769aa037c00b4b27aae14db8d6085c48ec89f16ef310c532d483aa1754e0e4b1d3f50cc91ef92bb8af19f71eeead2c2a1b32f5f8554204d22d23c1e861b8d3b72b38391abbd4c9a468c75f9f55425b1ccdc7d75fcd8f0730aa27d634fb3347a5a79bd73bbc4d8f0c3b2ad4900a618b54a9fe84738413ae34ccadd19059d68c869976f3b1342b6d53197b798f65977ad905386fca03705d04e6e826b343229a267164b2068d851c69f560abbdbb426aa9ebfd2e79d1e5ea05b8b78869b7722b5245a91021cec3673336befd9074f10aa72cb164cc46be41573d5f482e0495a8287951c9e85dde7e9494044ad93f6981796257b8b6f4095dbd21d9dd853506dd43bc225116cdb4b019d74a49ccd953656d12162103019cdc4ab77f2248b477c0584848da3e7f70c61eb4e66e01f49b97ec92190ca44cf1f45753ddb1d135f00074b70ca728b939379102b396420ce758f0bbae79b2809293d9161621b3a3ead8574652a49f857aedc874aafa48269d666f46efd63c17100812b9a79a0500309e82d3bbe467996dda0aac3afc8d391c6cd3a55d7cf2c7daad4da2072c6800b7d1fc9273921bb6ca97a400000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x9e78d2", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x19d4af3d4de075052a7e46d1e79cdac48c06b20a637af57edfaa39add1b7ebbf", + "transactionPosition": 57 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cbe59", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x5493bc3", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002cbe59196cb8811a0458d4515820c733511a1ee67c4b6354648a0aad557a7d6103db91ba7ec3605d0e0d0a7dd727582070588192c2cfcb22bd6934b66eb4b782332859eca0353d7a6a6de5ae787e23ff5907808da266f0d295889a05be62ca726ac93cae2bf1976516514387a04f6c161b95f727f97fe04494e7eed2533dfa309ff933835dfcf8a37564e3ebf5e4f2a7c0048aa707c303814028b15d44a34184d6e88f422defb35d333642a4592eee09398954097c2bb5290d2d989947f61ffcfbb57ff4efd12c5f0bdbe44e97487e45aeca92cdfd5678303f0b20617dc9fb65ecc734986207f6e60aeb1426702cebcba9b1e3e99b1f05d922b877818dcea601b115ee1794c04f76b666de9bf218f78f3bcb44b1c2f38368f86d53ce36b72bf6517d1a5f289da6799cbc2619403ea09b6f2fe13d598586938debb8a2e28762e65540b2b5401175c6e0533f20065b308444fcdd2462733270fba4adff7c4f1e9ac2c311b0c4e2fc6304f26ad4f1700a849eecaa0778bc7133031c1de707450c8fb1dc14f13789ed53844ca17939d2bc948ec550d0c18282b578e1228c7fa698c47611c28933cc0ae919ea14bb1aa6f75a01e7c474660eb686047eba0742f543acce9640bb67b163874d4528c7ea10b78fbd2a9180d69cb3e6477e8076fbfc1331194b04165be5e67f5161067e3b462edae40b9696d0d5217a1c20752df202384733c238a2a0b1ed16038f3cc7cbe51848d47d9031302870450538e8dd0022e41fb115ef3a4d9c4581a9229ab5385dce0753fcde17e8ed5bd77e3d3f865bc9f2da86ddff535786f72c93627459d5e4953019b9e71a0a13ff2fb7745b9a770c2ff2147edc8bb388730ff2c7b60645eb02a3dd96c6987d5cfe6f2fc254916d37c4589807faccdeed6595c5ed36d3bee947f5600f74b7737eaf6da28e3ba27ef9bd8b2075819620049af7b0034a55355a032fd807838dde66f7e9fee2aafa916f8c481bc8fca7fd868e1b39433a55bb96819eade540d63a2541f6de1ec9bca1c9f5545ab9b87481738c6868c3173daa50e42d6ca2c70172e512f1a7716ad8fcaa273d14d22466de3b18d9ac596b99fbfe1b50dc78a43042057415f19d79a9fb0243639303eeaccd138b5e8ffd0eaa1afa6418b7acab26e09458079a866b5cb655e0e2c9193a3be567ebdb8be7aa7b7ec4ca5726490798ff39a69bc1d15c8b4db2258173bb90c15ccdd3a38f0761a67a2893b7a176478345af99a4f151d89bf47addc9c19dc0a1fc7b8cfc2777cf6f98690feb3024ee5e393f63430d96d88efd01077fb4793b1a1369c529f05ccb8631a86eda1496c700095d52127504428f372d5dfd5b0c62aeed47698c64685eabd5ca056c3cd59f1c352de9b6aaa4ad2e3cfc3ea8ca119db8b24685432c4b2e6904c8b2d9ebc40585f8e37d77d585bc45b24448685f85b9061254e159b4d19ea81a33a3ee180e1ead0492e74fc2f4223ab2fda859b7c5e5abeb763194281c9b80ec89d0e1ef85bb4cbeb34dd238cc85f4f4b916ff8a3b47a9a18925f84adbf8b425970f6b2fa58789473fa2092f619365699266bdf2c84d80fa2f580f2135ea744abe1d6522a98b018b0c48e390f635a4e53c8df2c17b981d8e9b9ecf20681cbe9b727ec422a107486effcc2eb1255b4b16dd9ffda0d317834a3dcaba0647ffff274bbafb78225ed55d8a91e5fd5593425ec96db174708fae8ef62c34bc529fbe0ce13b52fb99d09288a63914fa099634fcc6af402b29dec3579afaa5136ae438ad32576e0a401d9e9da4dd79dd76992b02dc924d7b83c8b1f01ca4409fe937525b7772962430e8169a122afe5535e4c05af5001c96c548ddcdfbf2e313846f4422efc1b7d6399f022926f0a45475d6a1fc2e8dc9e1da4ceeb78ad725f324a5a495e4af104b244d3aecad990a3bf0538bc13afd284af720833bf6e0be96192104f181450c28bb8da27e81dda480c36dc01f13a33989f2cbeb16b295fe2b2fcc6f964fec34622e66a1ce4cd850d06e17ba099287e812603fdc936768fc2f0d13256fb56cd98b546906dd67c8f9c3724429cd8df899588c71a08404ad8e7443b3d19d3614fc7be29a6c7ed83efbf70d3d642c4673ef2622f45fa6ca7cfba8d0c1c49b40caf850a218158cceea53ed7379476f03114197df49e352e97677764c0d050c24155edfc4d1089bafcd0666161acc6f2b53bad7648f9564a9cdc969b22d769aa037c00b4b27aae14db8d6085c48ec89f16ef310c532d483aa1754e0e4b1d3f50cc91ef92bb8af19f71eeead2c2a1b32f5f8554204d22d23c1e861b8d3b72b38391abbd4c9a468c75f9f55425b1ccdc7d75fcd8f0730aa27d634fb3347a5a79bd73bbc4d8f0c3b2ad4900a618b54a9fe84738413ae34ccadd19059d68c869976f3b1342b6d53197b798f65977ad905386fca03705d04e6e826b343229a267164b2068d851c69f560abbdbb426aa9ebfd2e79d1e5ea05b8b78869b7722b5245a91021cec3673336befd9074f10aa72cb164cc46be41573d5f482e0495a8287951c9e85dde7e9494044ad93f6981796257b8b6f4095dbd21d9dd853506dd43bc225116cdb4b019d74a49ccd953656d12162103019cdc4ab77f2248b477c0584848da3e7f70c61eb4e66e01f49b97ec92190ca44cf1f45753ddb1d135f00074b70ca728b939379102b396420ce758f0bbae79b2809293d9161621b3a3ead8574652a49f857aedc874aafa48269d666f46efd63c17100812b9a79a0500309e82d3bbe467996dda0aac3afc8d391c6cd3a55d7cf2c7daad4da2072c6800b7d1fc9273921bb6ca97a4d82a5829000182e20381e80220c655d56cf0ef0742b187ef633a957ffbd32c3defade8f2594ec34910ae8ef12cd82a5828000181e20392202097b123d3710a5dd973f857a5dc4d0514f0a97e1d975eb01e42aaa1cd87b10e3f000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2fbb841", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x19d4af3d4de075052a7e46d1e79cdac48c06b20a637af57edfaa39add1b7ebbf", + "transactionPosition": 57 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001a0f6b", + "to": "0xff000000000000000000000000000000001a0aaa", + "gas": "0x1c23858", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285098182004081820d58c095c1f3fcb3c1579274a763a69efe68df562832d6d07902f5bd0d92df6c5f3633242ab04b3bf33cd6c1dc4e53e6348e57b64a9c8af60ecc295357de84447d6eec698cf27d62e260272bb587d433ca022f29db4399a700813df8341aafefde3eac07783ba253600998029028668b87cd91de868c4a1e0485ecdce958493fb7b03de06060e93b360705af0f49e300d06c90acdf3e6e8d6de2933463909e42a9f1b6a96a465cab34d14c762e113b2ab20797a9101fa6321983e962c74fdab9fe9eda1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" + }, + "result": { + "gasUsed": "0x17514ec", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xe85fd747a88049a644b187321afe6fc108862c99c3e42fb076fe35e266905bcf", + "transactionPosition": 58 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001c3bf7", + "to": "0xff000000000000000000000000000000001c3c0e", + "gas": "0x6e1b2cc", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000027a8518258382034082044082054081820d590240a06d5c12f5ddfcf4805175f47f5711e5e0eda838d6ccc4423f9426fa01451a3037af6100b3ea8913e16caffeac1f8308805c1898f39f306d98d01c0cde75312cd842c524eabd33bd58aa15b50f9d0b1f2e068c36ce8fb4db6cd66e4f11ebb93c01d973c053d13e1b070b9ffe554b0ff62e602e7940bd9904618ab4bdac3dadcccd10183165128f991bc97d20924c719e8b483140672040df267d9c5ec364f04c5df0b2134c18673cb86feeae16bad31fc27d68282f9a2158b0d91718020cf98383682293553b37a85872eaff3c7b34cd107853c65f24bcbbee7c0962080abd615c7568f4f84a795e9c43e21bfd10d3008efcab45e9f96671fa38198fbd4d4f9e50e9285ab59092b4c9f7f34936dab4996949b05ecc76a489b2e2cc02943f12fb143cbbfada5ddb42821e823f5c8acb9686de0e3ef85fc41d39719dbe8702ae9a3662a834f68c200c79f6d9e7c7032124b438ab8660ac3de175f80c0c7fefd992e83534f7b1f7e7c3afb4974cebde8238ffd9f3a738047ee515d483b2f91fd8fc8630777082c23784590b3d8a817a963f10a39e83a5740f7b6954a8930dd713c6fa332ba0da90a8e2b3f71e645f6876ac8a2b980be79ec5fc0d39fec3ac0a6cbcb99e661b4d62d5a46d6feebe18280aeb0e2ff21001b3bdcf4faaa677f16dbd9a03d0dd3fbec38d38403f21fe7795a4612bfec76bb2c945456637b84d2929e0d962a79a161de51d0ff523b91c342c37e5b170338c7678717aecc762d5935ffd168257763650220d5f38f6a269d2c39160a243cd163f8446607bbba2f2d5e61aa91a0037e5de58201bc00903e90ca71cf8a383db0b4d3a038e6c65f02639a026af83a66ff71862a5000000000000" + }, + "result": { + "gasUsed": "0x5b37ee5", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x468720813c31079df1c1d52f6bdc98c5b55dd3c3cf0c0f5377d692e2030c0ed8", + "transactionPosition": 59 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001c3bf7", + "to": "0xff000000000000000000000000000000001c3c0e", + "gas": "0x548fbe8", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f38518258182064081820d58c0b4a54e9a1ab64f94e7977377bea362e540a83376e284bf86dc426fdde3652582267f7508e553394e0317f636155ad659b04b297544b50bc0ddb85e23b7657fb6851233d15ed8c422341192b767e9627396f6b4c1380db51647bda0570fc0c8f51016b9cf8092e073beb73a451a93e08c309762bdd09ab92af5c738661bfdd8e86eb24c5ddbe399bf534221d26bd3ac0aa247b059202ec208dc4b57488ed12e0144b3ab60c54912333580b0b5e2c20cf0f14908dfb11b7514cfd0624a6ccb4c591a0037e5de58201bc00903e90ca71cf8a383db0b4d3a038e6c65f02639a026af83a66ff71862a500000000000000000000000000" + }, + "result": { + "gasUsed": "0x4840f87", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x6ae23d16858a9de909457b27a4a7272b0b7bee42f8d5f12c10f0cdc55118b459", + "transactionPosition": 60 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b3338", + "to": "0xff000000000000000000000000000000002b3363", + "gas": "0x4ae7ef5", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000072818187081a00011b51d82a5829000182e20381e80220f29dcf0ce726c36a3a9dec02c7975bc7d4fbe9058f088a5f66d827231404b12d1a0037de15811a045af8041a004f7082d82a5828000181e20392202091de727d20058c198e55b1a08967352ff72168b2b70f0c597e7e907b3778df3a0000000000000000000000000000" + }, + "result": { + "gasUsed": "0x3613dfc", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x558e1f235e7b5c9ee2373bb7c39b2c236f584979cb6d3b0898e1e283e3c05e07", + "transactionPosition": 61 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b3363", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x4a318a5", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x558e1f235e7b5c9ee2373bb7c39b2c236f584979cb6d3b0898e1e283e3c05e07", + "transactionPosition": 61 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b3363", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x492535d", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x558e1f235e7b5c9ee2373bb7c39b2c236f584979cb6d3b0898e1e283e3c05e07", + "transactionPosition": 61 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b3363", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x4803dec", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f7082811a045af8040000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x489183", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e20392202091de727d20058c198e55b1a08967352ff72168b2b70f0c597e7e907b3778df3a000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x558e1f235e7b5c9ee2373bb7c39b2c236f584979cb6d3b0898e1e283e3c05e07", + "transactionPosition": 61 + }, + { + "type": "call", + "subtraces": 4, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b199b", + "to": "0xff000000000000000000000000000000002b19a2", + "gas": "0x4ed0b83", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000022881858708195f5bd82a5829000182e20381e80220f9acb798b6fe4fb2b334af3d3e0ee522004ea693556ed6e7ead3d68306d75b151a0037e089811a0457ca271a004f598cd82a5828000181e2039220202e5970c0e581497fbb90cc3f526c0d9d553c5fbcdb2aaadcb64149ad5f22b0098708195f37d82a5829000182e20381e802208fe0b57c9c0dff7538fdeedef16dca53661ce854dc7a06a296a6adc3491ce7361a0037ddee811a0457c65a1a004f598dd82a5828000181e203922020607d2d181747f8a1df3b139e06f52588adf196097d38f13f8160aed6d437e1188708195f5cd82a5829000182e20381e80220c31fbd6bb96206aaf825a12337ddcac48808b5b835ef8d2b1c6b82255f1b0e081a0037e0a6811a0457f0331a004f5983d82a5828000181e20392202036078ad115da0b077798bbffb9abdf4756c5fb3eba49d917f608f4e93791b13b8708195f38d82a5829000182e20381e802206a13d744c58c3c3194185fc76b711ddd793451a6bc1c1b0d2593391fa4b9aa271a0037ddf7811a045921771a004f5980d82a5828000181e20392202085574c49ce6d1f8149b4631ce8d502e8764c0e1a949f05287f34553420e6ab228708195f5dd82a5829000182e20381e8022037ea5178bc9933cd3e272574beb4420d23ba7fd1141d27ef728aa2a03cb78d1d1a0037e0aa811a0457e4e41a004f5987d82a5828000181e203922020f4abd76e214c8125bd6b5470b3d111cc049db132b3e80cf0d9e64beb13cd4129000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x307c5da", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xef58a04d45dda0091572a6ae13c1c043e2397ab851d94fb07f108d5bc34beac5", + "transactionPosition": 62 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b19a2", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x4e01c98", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xef58a04d45dda0091572a6ae13c1c043e2397ab851d94fb07f108d5bc34beac5", + "transactionPosition": 62 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b19a2", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x4cf5750", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xef58a04d45dda0091572a6ae13c1c043e2397ab851d94fb07f108d5bc34beac5", + "transactionPosition": 62 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b19a2", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x4bce48f", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043818583081a004f598c811a0457ca2783081a004f598d811a0457c65a83081a004f5983811a0457f03383081a004f5980811a0459217783081a004f5987811a0457e4e40000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xdad3b5", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000de8185d82a5828000181e2039220202e5970c0e581497fbb90cc3f526c0d9d553c5fbcdb2aaadcb64149ad5f22b009d82a5828000181e203922020607d2d181747f8a1df3b139e06f52588adf196097d38f13f8160aed6d437e118d82a5828000181e20392202036078ad115da0b077798bbffb9abdf4756c5fb3eba49d917f608f4e93791b13bd82a5828000181e20392202085574c49ce6d1f8149b4631ce8d502e8764c0e1a949f05287f34553420e6ab22d82a5828000181e203922020f4abd76e214c8125bd6b5470b3d111cc049db132b3e80cf0d9e64beb13cd41290000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xef58a04d45dda0091572a6ae13c1c043e2397ab851d94fb07f108d5bc34beac5", + "transactionPosition": 62 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 3 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b19a2", + "to": "0xff00000000000000000000000000000000000063", + "gas": "0x1053f8b", + "value": "0x48fa86c15fa600", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x1770", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xef58a04d45dda0091572a6ae13c1c043e2397ab851d94fb07f108d5bc34beac5", + "transactionPosition": 62 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001614ac", + "to": "0xff00000000000000000000000000000000018a9d", + "gas": "0x2950360", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285028182034081820d58c0a24c0e37761bfe50b11dd0fd147cc58de9c706966e58ebf1c59bc68d826c3aa1c8ce21e214b65146eebb6599c7e9b13e9771b9703259e98da1c6be2581c7a3d32de9391b77cba94683e4a488a85b7677f7c4b24ee043e5b74328c64b2ab3548e168389d0eae2771b996bd858db8331aea66eacc1dab69bd7488c341a69d1a77c19373ab2cd2e0a213a4dd87e3a65b47ab6ebe365a6b834dd6d0f508e3cf61f4d534aafcc397017d2858c44e26b493131f8db7feb49cdc4df3f916b2681d5a8701a0037e5f6582080334418ee2e59dadfdb8d33bf996c9c35f36dbe740e214e308a34ebd796ddc90000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2288efe", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x9821a2e7145a9a0635ef1ef3b683392817d43c61caafbfbff0ec5422c3ada2bd", + "transactionPosition": 63 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b199b", + "to": "0xff000000000000000000000000000000002b19a2", + "gas": "0x43cf604", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782195f2a590780af0a4181a3dd50f9927739353d495ca29562a915a1ac79b0a82ac9fc0a2f6cf9a54d632c8c9f0198f2837f66507ce46eadce1f4c166c6256647d8973789a927937e5e74d9a27305ce146b549af4be391a9ae468e2a579b2864a66e7f641ffa38019d7736b70736af2d3747d2b5baef4a5d594ff32fec3d286dc115f7af556287d19df302659af6dbc84444e43eb9afe1a42de2f425ff8d15f75809e36e3d559197df381cbaf4cab36ac23c51ae9fc19c0e9dd92ce497c3757784cdde3d11c766a8a3142e9a8ddf1c5c03f6cb797b60a407d6b70395c40be27bca492c50f56b990468a1a436d4d84109afed2b7a8ace86b7c4376ac7e99bf30ef9360b4f84cfe243c8808e68fdc48d6a4780c225d78fe033eadfd2194bff4b72dd07e0c763ce8d13687ceddb0fe5ddcbf00a8d3ef2795a04928c1cd2c8ab9883d8b027d6b9d2e7130732f84d2e7677e8a66fc09bf7677fa8e5a83b4318d7991fa6e9c7e4ed85d92f4d0aa0421ca497c5248e7740b133558b3f348254b784271ea1cba40c570c6ba6dffaf4a6aa9c7f7f0c32d3175cd4b3ecacc8ec238a0b2dcd12decb7d6915f04cd722fe3c39385a51e4fd5a62663027a8e0e9d833d18e95f375aaecac522193cd514123eb9e351fc001127196a777d6cf7a47e490d38bcc435539a21bb781770ebef5972953b9793169212f89ebf0e717d9e37985df6313884a24442391e52cec8e668ffa90cc2f87e4a0ed3d4358d3b8d1ab4702b58df778b1710de8d416f6249f10524144721304e415dcb2b41599553e111f80262229c3e3ef6d2cd9c0a0a34efc99920676c84381dcc23180cc9094bbd90c29caa08077a6dca013e081c38db3db3d69dc436fe177d940ae47f566a2326d7b901bf823aae9ada3d119291b1d75eb7e327b19b4bec1217b732fad8c4c0a68bb8238fc3b9ee2ebc78d295773030cb0c8526ed6426f0765b5b1f1e89d0d80dc399f99ad18b4fb29e1236babcf994e75f7f60a68cd364bada4a5765af2b50f4b3a4695b3fdae7c48133f80fa6148b12478a1b46810d31258b50e3a694d6817548a39d664ba0d5fdda0d32f458b91119ff404818d4163345ac7fcb4dbff87b2c20d407c829c200236f9453a43a8bc9cde4da3d3a6763d306e86f97cf996b875e446091b221926e6a3574a64e8eed09a03e8450707c097c5059b357df5d2625446ebb495637f2137565d97a6e919006a576f55c4eccae9d0919dcc15984d908f970d6988fdd9df8c505c0f1c078069a659c07c420063bd90c71d4b9b4d16908921063c78e8b88fcd606b8505e4cb4c0a23983090df8fa3b14176c08c293c9404a2f3844a16eb4c3d2542398cf90db898fdf234259a06f52e445e2ef8755db263a09c97abb53b6c9044fb2214fbbeeaff34c0888b8ae6977a082b521a106aacaab8acd1faa0422a83a4fd799d3f3bd384bc017b5c2e2d00cd16ebdc2b41dcf1f5f41886ae1455fe0cb8769066a4f114205ed50579936c6ad6ee817a47080865f04c33bf029acbedeedf5474f90e9e556cfebb52bb44b745ac3d1119980a2998282e3384b4b4b72e44a2ce7715853f9f8b4289d83c5a524d0fde04ae762a1e6b9a25bf1a6ea57eded7737afca6b61e83639136b89ce030c6c54cedb8392db55895af9ad866bde97db08fb12f9f235e296c86fa007954cce91f615ff01ad1d7aa0ec788362313392bd4d47921872d821571d42689c2d10e9f27e30e065b0f1c03643f419b5ca7aa5157373e6c0b8ae60b5a56daa8932782bcb72bd7da154719fa551a84af6e3f7b5e30f263944c6c1f47fa510d037b90e01b1095df28baf2ef9885b7af50aaab01ccd913b9f1dccfec40b7fe6c4aefd442654aaa5442abd774d41d55583656a53d5bc9f3ff08d87478894a9ddecd39efa5f2119bfb25415970408fcf3265a8faf706bf6841953e44371630048ab9b604354fa279314cd9dd19af0822942179f32bcd9f9d1b19e4ec3ad7308372d8b5bf03908178e5d4c5e87a3eb40c0120fccad46d4d0f91c484416e0132a4cc9dde03bd5d4c492aaa06967014c5b414c8fedb95c4553f69643e6bdcf3076a67a85b8c32ae6a0e72ce8d2737a8724688121662677175c2ac618a2c481cd4043ce6c0fcd6a1d9bb39e1ac0caf63d27b1a01366de0f5f93d007f480efc95b00263a391ba807aed88a3e421ab2d62b9e604456026434da79a0949a7b12f9c791ddf565ea9f107b4c1b8050d563ba5a633528f068dc30cf7a440cfe418de1f54c571ac670cc2a2fb17ee44e9d57f9c7cdadf0b7772bba6edb8a6b260631a01532da47bf3fc27fd5b137a4a449e0a25a4adb6b60eadd2ec7abec7e32f970fe50696932a324eb8a81297a23f6e27d082d3e7bb299625f591c887bcef5587fdfaad00d5e1faf2c1c17dd48b0504993f224285f3c40e8965bc39422455ff1e248a1ba8859079c361dd332b2246b733cf51cb21b38f704127a36cf08f92b9200a3f951b90979e061b5b3b3c7163fbb03eab9ec644a1f14b05c3e4d90ec541006d90c315a69a4bcbebba3bfb5a1be8814bf3006cdbfc7436673e967e1d2d0be00f12c2ec5b7fc3d05ae6d23cd318c77f9ba86f31dafd6c886a3bd0e05dbbaf6dd052c29e10c13355768314c14c1077f3e8ada461e49d3bb8e0d9270b25f1e35bc638e1c732188ff5a7e082cfd91183afdc79ecc78878e1e0122abf3b767051b93600000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x841db2", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x352d011f8bc53595968e076db6f8bd657486a8fea38439ae699980096c2dc7e9", + "transactionPosition": 64 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b19a2", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x3e9b138", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002b19a2195f2a811a0458bf95582019d87ad3ccfc90bcec5bdd6e8270ed89d169ef967b5f727bae65de6ef176ae9b582045d1743b2baf96952374e2de1c0f2ef15e7cf0e678ebb9d158d2572d304a8530590780af0a4181a3dd50f9927739353d495ca29562a915a1ac79b0a82ac9fc0a2f6cf9a54d632c8c9f0198f2837f66507ce46eadce1f4c166c6256647d8973789a927937e5e74d9a27305ce146b549af4be391a9ae468e2a579b2864a66e7f641ffa38019d7736b70736af2d3747d2b5baef4a5d594ff32fec3d286dc115f7af556287d19df302659af6dbc84444e43eb9afe1a42de2f425ff8d15f75809e36e3d559197df381cbaf4cab36ac23c51ae9fc19c0e9dd92ce497c3757784cdde3d11c766a8a3142e9a8ddf1c5c03f6cb797b60a407d6b70395c40be27bca492c50f56b990468a1a436d4d84109afed2b7a8ace86b7c4376ac7e99bf30ef9360b4f84cfe243c8808e68fdc48d6a4780c225d78fe033eadfd2194bff4b72dd07e0c763ce8d13687ceddb0fe5ddcbf00a8d3ef2795a04928c1cd2c8ab9883d8b027d6b9d2e7130732f84d2e7677e8a66fc09bf7677fa8e5a83b4318d7991fa6e9c7e4ed85d92f4d0aa0421ca497c5248e7740b133558b3f348254b784271ea1cba40c570c6ba6dffaf4a6aa9c7f7f0c32d3175cd4b3ecacc8ec238a0b2dcd12decb7d6915f04cd722fe3c39385a51e4fd5a62663027a8e0e9d833d18e95f375aaecac522193cd514123eb9e351fc001127196a777d6cf7a47e490d38bcc435539a21bb781770ebef5972953b9793169212f89ebf0e717d9e37985df6313884a24442391e52cec8e668ffa90cc2f87e4a0ed3d4358d3b8d1ab4702b58df778b1710de8d416f6249f10524144721304e415dcb2b41599553e111f80262229c3e3ef6d2cd9c0a0a34efc99920676c84381dcc23180cc9094bbd90c29caa08077a6dca013e081c38db3db3d69dc436fe177d940ae47f566a2326d7b901bf823aae9ada3d119291b1d75eb7e327b19b4bec1217b732fad8c4c0a68bb8238fc3b9ee2ebc78d295773030cb0c8526ed6426f0765b5b1f1e89d0d80dc399f99ad18b4fb29e1236babcf994e75f7f60a68cd364bada4a5765af2b50f4b3a4695b3fdae7c48133f80fa6148b12478a1b46810d31258b50e3a694d6817548a39d664ba0d5fdda0d32f458b91119ff404818d4163345ac7fcb4dbff87b2c20d407c829c200236f9453a43a8bc9cde4da3d3a6763d306e86f97cf996b875e446091b221926e6a3574a64e8eed09a03e8450707c097c5059b357df5d2625446ebb495637f2137565d97a6e919006a576f55c4eccae9d0919dcc15984d908f970d6988fdd9df8c505c0f1c078069a659c07c420063bd90c71d4b9b4d16908921063c78e8b88fcd606b8505e4cb4c0a23983090df8fa3b14176c08c293c9404a2f3844a16eb4c3d2542398cf90db898fdf234259a06f52e445e2ef8755db263a09c97abb53b6c9044fb2214fbbeeaff34c0888b8ae6977a082b521a106aacaab8acd1faa0422a83a4fd799d3f3bd384bc017b5c2e2d00cd16ebdc2b41dcf1f5f41886ae1455fe0cb8769066a4f114205ed50579936c6ad6ee817a47080865f04c33bf029acbedeedf5474f90e9e556cfebb52bb44b745ac3d1119980a2998282e3384b4b4b72e44a2ce7715853f9f8b4289d83c5a524d0fde04ae762a1e6b9a25bf1a6ea57eded7737afca6b61e83639136b89ce030c6c54cedb8392db55895af9ad866bde97db08fb12f9f235e296c86fa007954cce91f615ff01ad1d7aa0ec788362313392bd4d47921872d821571d42689c2d10e9f27e30e065b0f1c03643f419b5ca7aa5157373e6c0b8ae60b5a56daa8932782bcb72bd7da154719fa551a84af6e3f7b5e30f263944c6c1f47fa510d037b90e01b1095df28baf2ef9885b7af50aaab01ccd913b9f1dccfec40b7fe6c4aefd442654aaa5442abd774d41d55583656a53d5bc9f3ff08d87478894a9ddecd39efa5f2119bfb25415970408fcf3265a8faf706bf6841953e44371630048ab9b604354fa279314cd9dd19af0822942179f32bcd9f9d1b19e4ec3ad7308372d8b5bf03908178e5d4c5e87a3eb40c0120fccad46d4d0f91c484416e0132a4cc9dde03bd5d4c492aaa06967014c5b414c8fedb95c4553f69643e6bdcf3076a67a85b8c32ae6a0e72ce8d2737a8724688121662677175c2ac618a2c481cd4043ce6c0fcd6a1d9bb39e1ac0caf63d27b1a01366de0f5f93d007f480efc95b00263a391ba807aed88a3e421ab2d62b9e604456026434da79a0949a7b12f9c791ddf565ea9f107b4c1b8050d563ba5a633528f068dc30cf7a440cfe418de1f54c571ac670cc2a2fb17ee44e9d57f9c7cdadf0b7772bba6edb8a6b260631a01532da47bf3fc27fd5b137a4a449e0a25a4adb6b60eadd2ec7abec7e32f970fe50696932a324eb8a81297a23f6e27d082d3e7bb299625f591c887bcef5587fdfaad00d5e1faf2c1c17dd48b0504993f224285f3c40e8965bc39422455ff1e248a1ba8859079c361dd332b2246b733cf51cb21b38f704127a36cf08f92b9200a3f951b90979e061b5b3b3c7163fbb03eab9ec644a1f14b05c3e4d90ec541006d90c315a69a4bcbebba3bfb5a1be8814bf3006cdbfc7436673e967e1d2d0be00f12c2ec5b7fc3d05ae6d23cd318c77f9ba86f31dafd6c886a3bd0e05dbbaf6dd052c29e10c13355768314c14c1077f3e8ada461e49d3bb8e0d9270b25f1e35bc638e1c732188ff5a7e082cfd91183afdc79ecc78878e1e0122abf3b767051b936d82a5829000182e20381e802205236012662fbb977c807728310b40aebc827a02db9ee6a6eb618093047f3f416d82a5828000181e20392202003feb391e43dda2b8d156216efff3abad9143e71882e38ff2f78ed23db51e537000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2fdd661", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x352d011f8bc53595968e076db6f8bd657486a8fea38439ae699980096c2dc7e9", + "transactionPosition": 64 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002d1d1d", + "to": "0xff000000000000000000000000000000002b1e7f", + "gas": "0x3735056", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000708181870819599dd82a5829000182e20381e80220d2d632c58c38d9ec51c905873d86022179e01c1508c4cc851f5f6a1ab3e4c4241a0037e0fc811a045b33ab1a004f64ead82a5828000181e2039220202f50056598e93a1acef58e7f51d3ea8bb0c7a50758cda43c77f43cd2eb09140100000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2660a8d", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x905d04dc590112e6e1c646b036395ef60c40a0dd3d1692e5d7aa645b72206993", + "transactionPosition": 65 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b1e7f", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x367ea2f", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x905d04dc590112e6e1c646b036395ef60c40a0dd3d1692e5d7aa645b72206993", + "transactionPosition": 65 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b1e7f", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x35724e7", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x905d04dc590112e6e1c646b036395ef60c40a0dd3d1692e5d7aa645b72206993", + "transactionPosition": 65 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b1e7f", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x3450f76", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f64ea811a045b33ab0000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x478f65", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e2039220202f50056598e93a1acef58e7f51d3ea8bb0c7a50758cda43c77f43cd2eb091401000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x905d04dc590112e6e1c646b036395ef60c40a0dd3d1692e5d7aa645b72206993", + "transactionPosition": 65 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001db1ef", + "to": "0xff000000000000000000000000000000001db1f8", + "gas": "0x4ff0eb8", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a0001933e59078085a6a05ba075038c36680da91e35dc5e0342a4b0321542423416ef2cd460c0c4ff58ffa9ef1fa9cc25b2552a34c615dba6b9e7239402bf8e7c1d6c5c4310df78676f565d7a5cc7d86d9b5167dd1ab39f5ebae6bfdd2ce39f0121e4da37372620167a8403f8b0559db674be819fd9fd0fbf56c3d2380e69d7b24ac4ec5f15a984f2a347f7ba4369e2b4138a929933721085746f24dde157e3505c368b5f2da8f0f771be99312f7036e1104a8be9110ea577633f13ed70b3af39063c3421cbd673b3794651781e4b02612ac51ad3d045241c3f529ad25440301aea3e523b862ae800ae314e9a7d3c56c10df3febe2c2745a8431b0e400b9e78a151d50861417006dc356daa9dad6de15e4f62c1f3755e326fa69c95ef5bb94447912b0420eaf16b1034069ea566340dcaaad6b3c0e3230f0e24e829deba4880f6b14b069bc1fad638fda5443c98c7a23d733f50316b26ee887cda3dc12b890ebbfe8cd06f0a5e932937e994d3632086e26068050e0eb5e38788eb9917f96d995f9378d0705a71a682a4ea8657a5dc5b301e650f0ec27a5061b72f1b0378c5ccad2af6f73c802f87c56497ca82134b0b33ff48c211664128b2568daed030ab8a9395c7aa3bcd9e4f45570cfadcead43c36274ec16ceaf030b79cc110bb4526ebf51872b2420c025b0dd6eea34b9433bba6343fc6423ef42f6ff8ffd66e1f221d3385138a03155dad6d88e341d059a211606a7b23626046a5b11dcb4b1a36f783f863fe0d98a152c6a776498bb1ce5c9d36782f7ef973439585c50410ae820cdde1e5e78b7ed8f503b499e35f83958098c0d48896c0dc5e8fefe05a8cf42a28bec48f80dd2ac9de1fc09b409bb326b90a84b935c3867530b08df919bee3190d349e9416a8da77a23769d41f94990c342a042024389c6d6cd76994836fa3460ced9122476ec30848370c21e632ceef9ec9c06809562c90d008591f3005d92638efd1c3a4f6f8d86df6e0d39b30cf8f6ec91afc479db4e73016818d72c72e7ea7269eb2baa704b384e39e852103d92696cb10223e1262b3bcfb2f555ed19fbe9adc4cb16e50964be2f9aebcc0cc146466f72ec0c226e4d4e0d9846b99c646bfd5113884ae519ce08dfd6b08eb22a1af25b6d1815c7758bea333b9a410db11c75981e3c8fd36c663ee2fe62da024f01d684333317f1c8c075af0baff1dd9e1e647a295679900455cb16601e70b453351eda37402cfd61566ff38ae99f209e94a03976744a02b3a65f7b09d39bea0437cabc80d3971d0846d565d8bb1534add4e34f906b971460c23fd49f7ceb43db3647e8b4fc88417462477782a5dad0e67a4784c54490c5b21f971d192cb642a66c41d3c68a10b3655552b007fda2576a8cbb01ee3ff85021d26ddfd652a04942a586794ea3ba963ac24509ba0e8ee686f62f67c7d15d0efe99f86500f7f1c109939619a594b7ac7465179d1aebe00ea6a4fcb81ff9d3df670995d2202214db0a3d5eb371dc45fe23c3e82e7ec22f0d61b0d79714dcec70249fb447cbc96e50c387210bae5a4307d24dde932906bc825c16499f98c1837d6cf3561dd5fdb49618f6cfb5a1ca730566923a7836b769548ef077db369969121768624f1a7504590fe55a2dae2053b7b3f3f5301934abe9e804eddd301547c8d3aaa947caf2d21053d659786fbd959e2658777b9aa2808f3319a1189bad15b5f735bb6bc8e068acddb64e9131733ba193b38d9039f5646024ee3eeb5b952191dde3fd98019471737f6e6dc983d57a9fdee96aaf60598335fab83da3328b7f208d0731458e59377832573e74e6de7dc2337eb381fa37e4a08c79a65c754655d6af439d8ad54b347b98323dfe758e27178a40d63208fc1fcd3df61b2d7160757947d46c696a8843f7ce187c6656ac0008407a7b5f634405ea440bc3447ec91910599f4edd8def67045b4200778e4c9757c8e868bc08366fffedc3ce00dbff563b173552ae31d197a949f3d18bb143d460a0e02d3b4aaad2779c0820b2853477964deff42a9067033402aafefbd28dcae30cadf7e68a92b5499c47faf0a6cb25d3225dbbe1debb6a7f8be1692e1ed61ca929fa7b6d88a13905e6992dbf0e9a2102c50b8e346c9fa52e67516a226b4a6ed0a5175c2a4432c22882acb727a284191fb0207329a873381bdbfae72dc2a2f1e6be1c0a8bf77e090d056a7689b5b6763f128f8b76639404cc14d396d6604d2a8c5145ccb1bb72a3d899550b93b132cfff03f3b941c92677f1bc1d0d0bc9819ca0467643eaacd6eb7923124b15cd5cbefd24647152e11e3865c1e17bb1a35f7cd7a57d50998cd91f03efc0f5dc9b5658b8cc739f46745801fd9dedf3829a7ce44ce25ef3a0cb8b49b8c18c89247f4d5095c877d71302e66ccc2ac9debf3c6fb511bad40eae508965dfdf66ed8d45e218f8eb3fc13e98644b50ddd91dbc11d6944ec32801ce9761e585a69930468de3b262956f72e250eb0bb4711b0387509df05732feced968a04b24fe6805ecf93ea6d85d178f4af97ccc05811f56288f28972b3fb873a1b626056be4f4aabe5b3b8bcbfdcdc277e0854c1c3eace993ef36304bb9453cd91e76b60bfbca1bf1613426cdddabd05e689ba64cdbc8c4ffaf177e1a9731b38dca1fb28934b8140d54f6e0a636cdee02e18c31b79871bb61d4a1d171798460859b76e358af75eac0ea8c55d3dd93b584a0000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x7adabe", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x6393b0c1a03fcddab9d197626ee380c8dce03ac552781061a25ce03ed4a4b293", + "transactionPosition": 66 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001db1f8", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x4b51aed", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008338808821a001db1f81a0001933e811a045b2300582097527d534b9bb0b5c38ed86fd4baf2c362b69fc2cb99f053439fd0d75ef7fa5e5820064b2b8946c028299181336eb551f58ccaa48f3f51029555218e5b6eb443910b59078085a6a05ba075038c36680da91e35dc5e0342a4b0321542423416ef2cd460c0c4ff58ffa9ef1fa9cc25b2552a34c615dba6b9e7239402bf8e7c1d6c5c4310df78676f565d7a5cc7d86d9b5167dd1ab39f5ebae6bfdd2ce39f0121e4da37372620167a8403f8b0559db674be819fd9fd0fbf56c3d2380e69d7b24ac4ec5f15a984f2a347f7ba4369e2b4138a929933721085746f24dde157e3505c368b5f2da8f0f771be99312f7036e1104a8be9110ea577633f13ed70b3af39063c3421cbd673b3794651781e4b02612ac51ad3d045241c3f529ad25440301aea3e523b862ae800ae314e9a7d3c56c10df3febe2c2745a8431b0e400b9e78a151d50861417006dc356daa9dad6de15e4f62c1f3755e326fa69c95ef5bb94447912b0420eaf16b1034069ea566340dcaaad6b3c0e3230f0e24e829deba4880f6b14b069bc1fad638fda5443c98c7a23d733f50316b26ee887cda3dc12b890ebbfe8cd06f0a5e932937e994d3632086e26068050e0eb5e38788eb9917f96d995f9378d0705a71a682a4ea8657a5dc5b301e650f0ec27a5061b72f1b0378c5ccad2af6f73c802f87c56497ca82134b0b33ff48c211664128b2568daed030ab8a9395c7aa3bcd9e4f45570cfadcead43c36274ec16ceaf030b79cc110bb4526ebf51872b2420c025b0dd6eea34b9433bba6343fc6423ef42f6ff8ffd66e1f221d3385138a03155dad6d88e341d059a211606a7b23626046a5b11dcb4b1a36f783f863fe0d98a152c6a776498bb1ce5c9d36782f7ef973439585c50410ae820cdde1e5e78b7ed8f503b499e35f83958098c0d48896c0dc5e8fefe05a8cf42a28bec48f80dd2ac9de1fc09b409bb326b90a84b935c3867530b08df919bee3190d349e9416a8da77a23769d41f94990c342a042024389c6d6cd76994836fa3460ced9122476ec30848370c21e632ceef9ec9c06809562c90d008591f3005d92638efd1c3a4f6f8d86df6e0d39b30cf8f6ec91afc479db4e73016818d72c72e7ea7269eb2baa704b384e39e852103d92696cb10223e1262b3bcfb2f555ed19fbe9adc4cb16e50964be2f9aebcc0cc146466f72ec0c226e4d4e0d9846b99c646bfd5113884ae519ce08dfd6b08eb22a1af25b6d1815c7758bea333b9a410db11c75981e3c8fd36c663ee2fe62da024f01d684333317f1c8c075af0baff1dd9e1e647a295679900455cb16601e70b453351eda37402cfd61566ff38ae99f209e94a03976744a02b3a65f7b09d39bea0437cabc80d3971d0846d565d8bb1534add4e34f906b971460c23fd49f7ceb43db3647e8b4fc88417462477782a5dad0e67a4784c54490c5b21f971d192cb642a66c41d3c68a10b3655552b007fda2576a8cbb01ee3ff85021d26ddfd652a04942a586794ea3ba963ac24509ba0e8ee686f62f67c7d15d0efe99f86500f7f1c109939619a594b7ac7465179d1aebe00ea6a4fcb81ff9d3df670995d2202214db0a3d5eb371dc45fe23c3e82e7ec22f0d61b0d79714dcec70249fb447cbc96e50c387210bae5a4307d24dde932906bc825c16499f98c1837d6cf3561dd5fdb49618f6cfb5a1ca730566923a7836b769548ef077db369969121768624f1a7504590fe55a2dae2053b7b3f3f5301934abe9e804eddd301547c8d3aaa947caf2d21053d659786fbd959e2658777b9aa2808f3319a1189bad15b5f735bb6bc8e068acddb64e9131733ba193b38d9039f5646024ee3eeb5b952191dde3fd98019471737f6e6dc983d57a9fdee96aaf60598335fab83da3328b7f208d0731458e59377832573e74e6de7dc2337eb381fa37e4a08c79a65c754655d6af439d8ad54b347b98323dfe758e27178a40d63208fc1fcd3df61b2d7160757947d46c696a8843f7ce187c6656ac0008407a7b5f634405ea440bc3447ec91910599f4edd8def67045b4200778e4c9757c8e868bc08366fffedc3ce00dbff563b173552ae31d197a949f3d18bb143d460a0e02d3b4aaad2779c0820b2853477964deff42a9067033402aafefbd28dcae30cadf7e68a92b5499c47faf0a6cb25d3225dbbe1debb6a7f8be1692e1ed61ca929fa7b6d88a13905e6992dbf0e9a2102c50b8e346c9fa52e67516a226b4a6ed0a5175c2a4432c22882acb727a284191fb0207329a873381bdbfae72dc2a2f1e6be1c0a8bf77e090d056a7689b5b6763f128f8b76639404cc14d396d6604d2a8c5145ccb1bb72a3d899550b93b132cfff03f3b941c92677f1bc1d0d0bc9819ca0467643eaacd6eb7923124b15cd5cbefd24647152e11e3865c1e17bb1a35f7cd7a57d50998cd91f03efc0f5dc9b5658b8cc739f46745801fd9dedf3829a7ce44ce25ef3a0cb8b49b8c18c89247f4d5095c877d71302e66ccc2ac9debf3c6fb511bad40eae508965dfdf66ed8d45e218f8eb3fc13e98644b50ddd91dbc11d6944ec32801ce9761e585a69930468de3b262956f72e250eb0bb4711b0387509df05732feced968a04b24fe6805ecf93ea6d85d178f4af97ccc05811f56288f28972b3fb873a1b626056be4f4aabe5b3b8bcbfdcdc277e0854c1c3eace993ef36304bb9453cd91e76b60bfbca1bf1613426cdddabd05e689ba64cdbc8c4ffaf177e1a9731b38dca1fb28934b8140d54f6e0a636cdee02e18c31b79871bb61d4a1d171798460859b76e358af75eac0ea8c55d3dd93b584ad82a5829000182e20381e802201c82068b925940ebe16ec679b86840d59d53a5afbc0ac8a51cf35e448f994a68d82a5828000181e203922020f96346a454a264d426a574e670033337c65e38a464a9fe95b5347d845167f73200000000000000000000000000" + }, + "result": { + "gasUsed": "0x3016322", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x6393b0c1a03fcddab9d197626ee380c8dce03ac552781061a25ce03ed4a4b293", + "transactionPosition": 66 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001db1ef", + "to": "0xff000000000000000000000000000000001db1f8", + "gas": "0x4c53635", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a0001936a590780979cc13480c6881871acf163c67f7a1a2bc1f03cc354f55dcf9dde15e48cc8a68e98e105f7e7616ad71c627e3d8f6e9b8943c90960eefd4a81a64f2fbdff01e1898df27da876dbda7c1e563a46fbfd254c52605b2add4b45236708ffa56c2fbc07908bb0d02575f826cdcb31c359c02aabad55583196cb94a22d4f17192f8b154a0e1a1f82153db370e15254e9f2976fadeb82b9f9dabed2109febad359bf7f04ea2468d908f72fd558a3e6f38c71e7e85bc1ba5111b542f05ccedd6e05b5599b348dcc5e5c24397b8bca23768f81ce95793fc3bc58d35ea8a77edcdf8dd3bb8bb527033e09f1f013bf3a4c14e050d4892ce22664802477bb67714e5ab3554f6f533905dcc74a467a55daad996dc9e7ba973238b0bc61cc5fc0dcf6f8867746b07a3026ffd5871c015495177899006daebe855e8b582b4141f55a94b06c07535589c5b736aef5faee110569bdebc43f5a97fb56e9378ada1f40298d28f98b3601e22b8029a62d1787652dada0e8b2fd1be40f8371bf9f2622ac367f91e3a4ff6a5d2b2c648eb2265eafc4e1c7a970ca09cfe7b3155bf10b42596bd5ff02f7d4e89010e9c4ac3a27aef7e8a543ca01310a89951f79a2f2429618dae9ae72baf2ebd4252816841e095b84d0d7abc399ef3d9435803749fa2bdf5ba8da6091e0e6c03b171affa5d8a3994ba6e269e2626318fa75808e6962ec2ce525b6ba5c3a395c90698aba9dd1f775527109dda8f3923a1b29dc929d02c9944f123f6b5a406b981ac7d94fdae623b963dabb44965a2ec39dbc3c350e060859f09b33c33b7183ca3507d467b152b9adefffb424b0411309bdb55882d0ed0284bd5891e81a2cf43243ea28da1815900beae3b1241952ff0989730da43746acef8bd5f5180291fdd707b206b7c5e3db17434cd1cf0c9951b147d9de68552128857e9867410bf03a50399230d371fcad03c528ddd8820305db233fb5f807f7268621f43b7bee3d44435ceb00b959a9298343ab73af240d2c6adc91fd617ee83aab7d65cf53248f30699745db31f4727a5a8f4137efc258d63621fd8326c90c26195e85686bea4c9eb8694df56f88732df7e7112b27259326c8d151410b922a23103b10ee6d81d325be256a6760c5b700fc4c21a99ae270f1fb4b6d0cc60948801c9d408bc6223eafcbe52dce0cc9f1ed2f2f2f52b3ba691418fda181a27417fbf753e14a8680b3ff4000579547b6c37c0d72e6d2e8b48280f96da26b838373d5a242342a9a46daefd8341852f2f53f5dfac1d8d40a3402ab891a0a8da280978d7803d88f4c52ab376d6db65c3cf315ee5f185e0f8b30d72f7da427654ead6a1790ff750bb41113313a32d9e81be3849240ec12417e7f16eca03a9a381b90352649b5ccfbd8a1ccaedaf858ef2cb1b31f85bce89d86e471984b30e029c13859ecc88301ade00414c5140b0a1ba262677a9d1f082287db7bdf4ab1f5d6ff313dbdbe62ac8faa283471019bcdb8670033166fc082d37812abe3a30284a67f7bf95c2ed53b1b521baa8d1069578cf5c01c8d09351fb27fe25a8d7b542582846c6b4c8c7f3decd48c0e21d6c00f6106e27a64e8b8a0cb6acf08c1d3fc566229e9317b81c372c53e0b4d61fb477b6c786b0bc58ee7a794b988fe8de466d93665535e19b98b909537d133aa4f4a995a89631da6007e4f698a0a2e529a0ece6534782c158195fa292657956c07d76c1668deb0ed453603c35a3bda1da85b5e29d766a17d87f1f995367f3801c066428d52d2c18a9dbb3934b69aa3c5cd6b20987df92d5cd1bbac10c805e33a9c6ec48162385eb4d2d9cd56dee21dc48ae2bb998b343be328590e079d8bf64b367c8169543df713d89ab398b9856c4c7437abd4ecb799cc0f5e56c7a165d1e8dababa0b8ee63474c0d31fa50e59274bdd73db1d0a6ba2fb590a523234fe5026bd276526293fe12ef6ead189f7d967a858a3243befb772036d5763730b6fd1d7d2515630653a23d63558ec0ef68428987bb3c6beb925703ca08c8333cc5aa742d0afc2a7fe498d6cc5f22f13d993a0b5023e6712c547c825a7b0adf77e9a88d93fc539e4f708e5c0fb9f6f44925ba5e2e97ebba350c59965d74652b17c3707440c8c7bb0604f4a0623a34425f37f8e03f50a8bfba1054ad50942a3f4e9c76486ca9a92219cbea8de506ca908a751ad9c51c1e9296f809b2b02f2f66a5659f5d8bd30426c484507723380cc1ab250482eda674e329f191abc9caa647f6ba3d54449e9969fc2a42f340b5440660da775488e5364d49de6a8a4fa8c81b836abc90ba13a01dedc03e28113d7f5d655b7071e665b9f13772632a5cc8df38593b76b6afe348878d07ef2cfb78c1210c26fbe21ea60564376f1b1770a732dd01a39af2f9ebd3b802686dc271b6046bcf3b1c0c8468506cfc654bb04aebe9d67d8814d0eeb9b1aab31c1f67ca73528ca48743992ac0ed70b4536b46ca73bfeb84e6678f5c730a05e6b02be30a8620830554179018980dff44d9bed0578b162191608205f506d2f427edd72d38e0a3b750aab0a7dc896a5cf8339f685ab8573e94728ced5a0a738844c3d8d64750c547383f99a227dd13138db4712808f367392979cd6227a3ce5930eeb8b2ec388b6fb7c0a2fd9da86ddbc94f7f71e87694781c69f428158a923c3c7b5199f7f9dd6d71eb50b8035b53e8bfeaa2ca0c7aaea9930c2df83b0000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x7ac08c", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x49ef42aace56fbd0d5779635f363a2b37eb171aa85aefcf5218da7d495abf901", + "transactionPosition": 67 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001db1f8", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x47b5c9c", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008338808821a001db1f81a0001936a811a045b2a7758203e20b2105a6aae7da18e80f56751382eb6c4750b41c516ded263e3cd722d81f65820096c96c4cac9b18cb607419f842911557776091216b2c3a38014e9484ff0bb3a590780979cc13480c6881871acf163c67f7a1a2bc1f03cc354f55dcf9dde15e48cc8a68e98e105f7e7616ad71c627e3d8f6e9b8943c90960eefd4a81a64f2fbdff01e1898df27da876dbda7c1e563a46fbfd254c52605b2add4b45236708ffa56c2fbc07908bb0d02575f826cdcb31c359c02aabad55583196cb94a22d4f17192f8b154a0e1a1f82153db370e15254e9f2976fadeb82b9f9dabed2109febad359bf7f04ea2468d908f72fd558a3e6f38c71e7e85bc1ba5111b542f05ccedd6e05b5599b348dcc5e5c24397b8bca23768f81ce95793fc3bc58d35ea8a77edcdf8dd3bb8bb527033e09f1f013bf3a4c14e050d4892ce22664802477bb67714e5ab3554f6f533905dcc74a467a55daad996dc9e7ba973238b0bc61cc5fc0dcf6f8867746b07a3026ffd5871c015495177899006daebe855e8b582b4141f55a94b06c07535589c5b736aef5faee110569bdebc43f5a97fb56e9378ada1f40298d28f98b3601e22b8029a62d1787652dada0e8b2fd1be40f8371bf9f2622ac367f91e3a4ff6a5d2b2c648eb2265eafc4e1c7a970ca09cfe7b3155bf10b42596bd5ff02f7d4e89010e9c4ac3a27aef7e8a543ca01310a89951f79a2f2429618dae9ae72baf2ebd4252816841e095b84d0d7abc399ef3d9435803749fa2bdf5ba8da6091e0e6c03b171affa5d8a3994ba6e269e2626318fa75808e6962ec2ce525b6ba5c3a395c90698aba9dd1f775527109dda8f3923a1b29dc929d02c9944f123f6b5a406b981ac7d94fdae623b963dabb44965a2ec39dbc3c350e060859f09b33c33b7183ca3507d467b152b9adefffb424b0411309bdb55882d0ed0284bd5891e81a2cf43243ea28da1815900beae3b1241952ff0989730da43746acef8bd5f5180291fdd707b206b7c5e3db17434cd1cf0c9951b147d9de68552128857e9867410bf03a50399230d371fcad03c528ddd8820305db233fb5f807f7268621f43b7bee3d44435ceb00b959a9298343ab73af240d2c6adc91fd617ee83aab7d65cf53248f30699745db31f4727a5a8f4137efc258d63621fd8326c90c26195e85686bea4c9eb8694df56f88732df7e7112b27259326c8d151410b922a23103b10ee6d81d325be256a6760c5b700fc4c21a99ae270f1fb4b6d0cc60948801c9d408bc6223eafcbe52dce0cc9f1ed2f2f2f52b3ba691418fda181a27417fbf753e14a8680b3ff4000579547b6c37c0d72e6d2e8b48280f96da26b838373d5a242342a9a46daefd8341852f2f53f5dfac1d8d40a3402ab891a0a8da280978d7803d88f4c52ab376d6db65c3cf315ee5f185e0f8b30d72f7da427654ead6a1790ff750bb41113313a32d9e81be3849240ec12417e7f16eca03a9a381b90352649b5ccfbd8a1ccaedaf858ef2cb1b31f85bce89d86e471984b30e029c13859ecc88301ade00414c5140b0a1ba262677a9d1f082287db7bdf4ab1f5d6ff313dbdbe62ac8faa283471019bcdb8670033166fc082d37812abe3a30284a67f7bf95c2ed53b1b521baa8d1069578cf5c01c8d09351fb27fe25a8d7b542582846c6b4c8c7f3decd48c0e21d6c00f6106e27a64e8b8a0cb6acf08c1d3fc566229e9317b81c372c53e0b4d61fb477b6c786b0bc58ee7a794b988fe8de466d93665535e19b98b909537d133aa4f4a995a89631da6007e4f698a0a2e529a0ece6534782c158195fa292657956c07d76c1668deb0ed453603c35a3bda1da85b5e29d766a17d87f1f995367f3801c066428d52d2c18a9dbb3934b69aa3c5cd6b20987df92d5cd1bbac10c805e33a9c6ec48162385eb4d2d9cd56dee21dc48ae2bb998b343be328590e079d8bf64b367c8169543df713d89ab398b9856c4c7437abd4ecb799cc0f5e56c7a165d1e8dababa0b8ee63474c0d31fa50e59274bdd73db1d0a6ba2fb590a523234fe5026bd276526293fe12ef6ead189f7d967a858a3243befb772036d5763730b6fd1d7d2515630653a23d63558ec0ef68428987bb3c6beb925703ca08c8333cc5aa742d0afc2a7fe498d6cc5f22f13d993a0b5023e6712c547c825a7b0adf77e9a88d93fc539e4f708e5c0fb9f6f44925ba5e2e97ebba350c59965d74652b17c3707440c8c7bb0604f4a0623a34425f37f8e03f50a8bfba1054ad50942a3f4e9c76486ca9a92219cbea8de506ca908a751ad9c51c1e9296f809b2b02f2f66a5659f5d8bd30426c484507723380cc1ab250482eda674e329f191abc9caa647f6ba3d54449e9969fc2a42f340b5440660da775488e5364d49de6a8a4fa8c81b836abc90ba13a01dedc03e28113d7f5d655b7071e665b9f13772632a5cc8df38593b76b6afe348878d07ef2cfb78c1210c26fbe21ea60564376f1b1770a732dd01a39af2f9ebd3b802686dc271b6046bcf3b1c0c8468506cfc654bb04aebe9d67d8814d0eeb9b1aab31c1f67ca73528ca48743992ac0ed70b4536b46ca73bfeb84e6678f5c730a05e6b02be30a8620830554179018980dff44d9bed0578b162191608205f506d2f427edd72d38e0a3b750aab0a7dc896a5cf8339f685ab8573e94728ced5a0a738844c3d8d64750c547383f99a227dd13138db4712808f367392979cd6227a3ce5930eeb8b2ec388b6fb7c0a2fd9da86ddbc94f7f71e87694781c69f428158a923c3c7b5199f7f9dd6d71eb50b8035b53e8bfeaa2ca0c7aaea9930c2df83bd82a5829000182e20381e802209731f18aa4fce0cef187b30f84c05ce42eb42b7321ead72441898d5abdeea412d82a5828000181e20392202039f254d4d3235a7d50f05f37d5d73cdc32d12a9c989840c1108a54fb9a15523500000000000000000000000000" + }, + "result": { + "gasUsed": "0x3778f53", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x49ef42aace56fbd0d5779635f363a2b37eb171aa85aefcf5218da7d495abf901", + "transactionPosition": 67 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cf0e5", + "to": "0xff000000000000000000000000000000002cf0ea", + "gas": "0x338cc13", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708192d05d82a5829000182e20381e80220351469a2b481b840bd9743a6fcf7513753a3937e572c7cb25433031de7ec15651a0037e103811a045669031a004808a5d82a5828000181e20392202046998118b3148e63f207a911c0224a4eac12b07b6743415b6bff7524aac8023500000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2354b00", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x0f12d60f9268de78ac12e64be7f99d6dd88c7d865d8ce91f32c11652baf62c16", + "transactionPosition": 68 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cf0ea", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x32d65ec", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x0f12d60f9268de78ac12e64be7f99d6dd88c7d865d8ce91f32c11652baf62c16", + "transactionPosition": 68 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cf0ea", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x31ca0a4", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x0f12d60f9268de78ac12e64be7f99d6dd88c7d865d8ce91f32c11652baf62c16", + "transactionPosition": 68 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cf0ea", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x30a8b33", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004808a5811a045669030000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x488993", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e20392202046998118b3148e63f207a911c0224a4eac12b07b6743415b6bff7524aac80235000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x0f12d60f9268de78ac12e64be7f99d6dd88c7d865d8ce91f32c11652baf62c16", + "transactionPosition": 68 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce379", + "to": "0xff000000000000000000000000000000002ce3c0", + "gas": "0x305c21c", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708194375d82a5829000182e20381e8022019ea299b91070fce9a4ed47462469afe6593ac2a70357ce4d26758a7334278591a0037e112811a04548e851a00480c92d82a5828000181e203922020690da56aaad92166ebff80ea0daf666c3c7e1a59eb690b95367f47097cd4363c00000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x20c763a", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x949c7c1e151376037350ef882355c9b80173632c806d5ca6164148b7f51ea7cf", + "transactionPosition": 69 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3c0", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x2fa5bf5", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x949c7c1e151376037350ef882355c9b80173632c806d5ca6164148b7f51ea7cf", + "transactionPosition": 69 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3c0", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x2e996ad", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x949c7c1e151376037350ef882355c9b80173632c806d5ca6164148b7f51ea7cf", + "transactionPosition": 69 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3c0", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x2d7813c", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a00480c92811a04548e850000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x488993", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020690da56aaad92166ebff80ea0daf666c3c7e1a59eb690b95367f47097cd4363c000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x949c7c1e151376037350ef882355c9b80173632c806d5ca6164148b7f51ea7cf", + "transactionPosition": 69 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b3543", + "to": "0xff0000000000000000000000000000000021b478", + "gas": "0x1995446", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285068182004081820d58c08a3c01bd1c9be515f6217c646d9fec13e541dc1445178d0fee2bc33183b23dc4dc5b7d7d06493bb131a44566a5e1eecf8fb68474b5448d132996e4cc35db93b6e13b32f59f2d6a02eccc2f128271e171c09ce20cedfa3b7a57f6905d553af8e403305f072dadfa782b2e673feb8bebe6e75d8875a0af330a3ba0b404463b46ada804bde0677c9e44659a1973e82a243e88d1f18d823d6849eff98fab0e9c585246f8fc4a897916d823c91780159e530fd732352ad88e2474f2cf815c388dc3111a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" + }, + "result": { + "gasUsed": "0x1545d77", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x7d27a0315e51d96e06bc4699d5b5efcebf60f838a9db79f1c1ca6183ae3b86c5", + "transactionPosition": 70 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cbdce", + "to": "0xff000000000000000000000000000000002cbe59", + "gas": "0x4f47d1a", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782196c66590780906c8270f7a49125a567510c97404d726700444a974c52abaa369eacad17de494b817f70ae9f60f7e4b4fd7bb7325a17b8124630857a51652e94ee9a590f76dd479998cfaabbb74fc4dbdebe8be928083719ba2bc514dbf3723335072095ac5110206d71e43f6acb4c8d4a5f5c4489c2adfbacf8b54f44eeb958ccee349a7bd587c8ad64af6cbe885df7a04af2a4e1cda51388e3d459cb9efcc9905ffc53d4de4a7aed05a0537de2ce8594c672b5c3876bba195e095302b88d73f91b4d1716fab0eca1e729706fee665d539960ef9a4719ec4edc6bc0a4e6b52bb0e76ad484d5eeb2c1650ef9bc821d67524aa82f3bf4825548eb5d9b8a7f1638c747f219649c75241dbc9d1b47edf6baa29e7d85e667f9c96703e9ff09e3e5ee2be72a10a3e30f2e506c74bff2b581521fceb697c2a7b83db32f3c665e213c0eba5a8505d2415fb60ddebf509bbfd1a1dec3df01d480b23bdc16c8268bc0462d477101742b11bb5f31dfc037608610ad3489b78bd776e3e66e7655f5b55f98ef01edbe1c1ad0938b6883764bca24d33e2dae96c467ddb242b153f1bbb41df5d0842d496f1271e4cc9e212ab58369126cd56c30c602708f6b62ce8c1dd20b7eb5de15447c683536faee20399a2f23a619b2069546b279077a5b73c050962447c05997bb9c5831146230ac4f09cbb01eb7dc5abc87e185eb44da96695fd18f6047831d0276710e7aac3e850507ca142bbf4b036b72b872a3c351eff4e017b664d965c4c3a5b3745fb4dbd401f57ec16b0fcff536d8b705757f369e16ae1ea5b59e4f37d45b15d699257bdf5e01ea1be77e3dbb45a5204108e0009e93197e601b7da1d6bc29dab0c8126b99aef1ada965ea0a4c999b335c81095313b3d576be3ae66e4157a86a985285b64eff9380da6f2b4fcb5f6966ade1c97b4c293efc753316ad19d3e542600ef0539c6b1fa0ebed10edd8b6da5f276fc7d07075e1953120543573a4263641e9cf2c1e73df2d803339b29f7d9b14e08811c3f0d631c08f61853a59cffb9f585e2f409b1149602f4861bfafcbeabb4a8f6a0095e5b5cc20c3c7080e93563d4988839181355d52ec6c8e141890a662460b5d8e4b7a53f2abd3922221b848943b6aa79b10421431d8cf5e5973e7b28f218ab7578011d031c9f1ad86fba0f4915aa01e1dc34465c8e39caadf80d8e312e96b4e1ee885bafa1f2d0346461ea8939f08cd15cfa035611642aff82dede8f823d84aa267db9e9a439dddc9eb81ac4855a4746e844aa385b797c674f952b56ecc8a31180a1478fcb46486892bcd7f546b03546a6a2beb93c5412814a1ec8bf9c8cb8c098afbaac58bbfc45e1db9df070188c93ab52ec845d019ec1fc01f4a2e4efeb9930b71905de02d44736a3d07a7b7c34c4f4480dc8f8b974532afb2bb06ea8165a113710d5e429ba435c552fe7aa34183643a0e1b716447f71dc12ac4668f9c67a3f43195f9f4f9ee28451ac2445403c208be6a02cc5ac06012fd1209663aae19460d2afdff338e18f77a3cc12d9439ba513ddfe94c2f15754f39def5eec8b731642c48e08b4edb7c015578f02f69c4d8ba79b746a0caaee744715d94c139a6c9aa489a3ce3a343edac841181136c833f65a9dad3b1dca25f31f729fcb7dbebb47a11eaaf5a0f7904690b5b31a0bdcb88559d34ea252862b0a399dfcc57e283c0ea11412ab19438e439926f6dc9db6086c68ea329984dff59b636137eb83eb0a0c43b4e3d81c5e0e45bf305f0dc720d092086254dfc294477e5e5ff47c793feaa1866c94e251d5511229f1e95c05540b591984e73a1959b4094e62e71affd916daa99eabffc7ce1881e081be632ae2c09d61ad1b69e1b2bf064eae889182e86e0d65ec4667f75e2d277ee17b0cb85aabe7c6b7169011ed82f831758553ae49d4ea6e41b7e578c6e354c33fe528217a0f331ea1ba1350165b3098015cb58d38340811ff27e5e20a1368081fe140bd1ebc77dfdcdb28bc567e28b6584a7c68b68f6dec93e9cc385135038ee735322260f7c90044be8a19bf18d99e09f70607ff2a6a3a4ad29e0a291202eb601186b75e69835d2258e74bdbd04ec8f9c75981eb6f4dc7f09c01763aa3a3de91dd16c0b3f61b97802a4dd6dfccbc1da28fa09a5f8ee64b41ba12580d4a4163f84ec9f81a4edc5fba7fec105aeeb32dcb68d757cbd276c13a5e09af8302a11e5b503e7911d058ae7c1dcaa44b5927c9ab4f02aa796a478b2442b91f21877c46d91b41b07ba9b2b8514bd1adb787a9a3c27efa27fdeb8371e1d27c66ab80da9ec7c2c1f43145e73dd2483e3a93ea363c28131b8aadcbe8c7fd536caa63a79df8cd3a2e432fb449c6df33bc07a4a85328c5f0d7f1c89d5920bb7272f5b320df888a23877838b9bfb9a53217b09c71b5c5e0ccf18684ecfe43093a8586c2ca5567b99b52dde92398b19af9eaa852639e726d14c261610f64bb225b4d598b3e99f27fd427ccff640bf57173cd0ea640c4fd4662d9c14914d93457b202f3c020c4aec749ee965d824ce864b837797fdb365c5c20bfce3d5f3410308901bb2e6bbd2979af999c909d9f6b240a50559868ade0f8d1b22b8cf1fed7e084c000e3229b36b06c12f9eef090281bbc125f78415f6a9382b6646b5f3741ee2c39ca4a070e3fa322f138ae27f56f7bfecff510cd82b170b2d9c20bcf4133ab2cfb82278268f6a998e07cd00000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x9f6264", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x09256a67c71f386327c50728db231ac787c819340a27da9c52bf5338efdc1b8c", + "transactionPosition": 71 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cbe59", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x485fdf9", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002cbe59196c66811a0458d8675820c733511a1ee67c4b6354648a0aad557a7d6103db91ba7ec3605d0e0d0a7dd727582070588192c2cfcb22bd6934b66eb4b782332859eca0353d7a6a6de5ae787e23ff590780906c8270f7a49125a567510c97404d726700444a974c52abaa369eacad17de494b817f70ae9f60f7e4b4fd7bb7325a17b8124630857a51652e94ee9a590f76dd479998cfaabbb74fc4dbdebe8be928083719ba2bc514dbf3723335072095ac5110206d71e43f6acb4c8d4a5f5c4489c2adfbacf8b54f44eeb958ccee349a7bd587c8ad64af6cbe885df7a04af2a4e1cda51388e3d459cb9efcc9905ffc53d4de4a7aed05a0537de2ce8594c672b5c3876bba195e095302b88d73f91b4d1716fab0eca1e729706fee665d539960ef9a4719ec4edc6bc0a4e6b52bb0e76ad484d5eeb2c1650ef9bc821d67524aa82f3bf4825548eb5d9b8a7f1638c747f219649c75241dbc9d1b47edf6baa29e7d85e667f9c96703e9ff09e3e5ee2be72a10a3e30f2e506c74bff2b581521fceb697c2a7b83db32f3c665e213c0eba5a8505d2415fb60ddebf509bbfd1a1dec3df01d480b23bdc16c8268bc0462d477101742b11bb5f31dfc037608610ad3489b78bd776e3e66e7655f5b55f98ef01edbe1c1ad0938b6883764bca24d33e2dae96c467ddb242b153f1bbb41df5d0842d496f1271e4cc9e212ab58369126cd56c30c602708f6b62ce8c1dd20b7eb5de15447c683536faee20399a2f23a619b2069546b279077a5b73c050962447c05997bb9c5831146230ac4f09cbb01eb7dc5abc87e185eb44da96695fd18f6047831d0276710e7aac3e850507ca142bbf4b036b72b872a3c351eff4e017b664d965c4c3a5b3745fb4dbd401f57ec16b0fcff536d8b705757f369e16ae1ea5b59e4f37d45b15d699257bdf5e01ea1be77e3dbb45a5204108e0009e93197e601b7da1d6bc29dab0c8126b99aef1ada965ea0a4c999b335c81095313b3d576be3ae66e4157a86a985285b64eff9380da6f2b4fcb5f6966ade1c97b4c293efc753316ad19d3e542600ef0539c6b1fa0ebed10edd8b6da5f276fc7d07075e1953120543573a4263641e9cf2c1e73df2d803339b29f7d9b14e08811c3f0d631c08f61853a59cffb9f585e2f409b1149602f4861bfafcbeabb4a8f6a0095e5b5cc20c3c7080e93563d4988839181355d52ec6c8e141890a662460b5d8e4b7a53f2abd3922221b848943b6aa79b10421431d8cf5e5973e7b28f218ab7578011d031c9f1ad86fba0f4915aa01e1dc34465c8e39caadf80d8e312e96b4e1ee885bafa1f2d0346461ea8939f08cd15cfa035611642aff82dede8f823d84aa267db9e9a439dddc9eb81ac4855a4746e844aa385b797c674f952b56ecc8a31180a1478fcb46486892bcd7f546b03546a6a2beb93c5412814a1ec8bf9c8cb8c098afbaac58bbfc45e1db9df070188c93ab52ec845d019ec1fc01f4a2e4efeb9930b71905de02d44736a3d07a7b7c34c4f4480dc8f8b974532afb2bb06ea8165a113710d5e429ba435c552fe7aa34183643a0e1b716447f71dc12ac4668f9c67a3f43195f9f4f9ee28451ac2445403c208be6a02cc5ac06012fd1209663aae19460d2afdff338e18f77a3cc12d9439ba513ddfe94c2f15754f39def5eec8b731642c48e08b4edb7c015578f02f69c4d8ba79b746a0caaee744715d94c139a6c9aa489a3ce3a343edac841181136c833f65a9dad3b1dca25f31f729fcb7dbebb47a11eaaf5a0f7904690b5b31a0bdcb88559d34ea252862b0a399dfcc57e283c0ea11412ab19438e439926f6dc9db6086c68ea329984dff59b636137eb83eb0a0c43b4e3d81c5e0e45bf305f0dc720d092086254dfc294477e5e5ff47c793feaa1866c94e251d5511229f1e95c05540b591984e73a1959b4094e62e71affd916daa99eabffc7ce1881e081be632ae2c09d61ad1b69e1b2bf064eae889182e86e0d65ec4667f75e2d277ee17b0cb85aabe7c6b7169011ed82f831758553ae49d4ea6e41b7e578c6e354c33fe528217a0f331ea1ba1350165b3098015cb58d38340811ff27e5e20a1368081fe140bd1ebc77dfdcdb28bc567e28b6584a7c68b68f6dec93e9cc385135038ee735322260f7c90044be8a19bf18d99e09f70607ff2a6a3a4ad29e0a291202eb601186b75e69835d2258e74bdbd04ec8f9c75981eb6f4dc7f09c01763aa3a3de91dd16c0b3f61b97802a4dd6dfccbc1da28fa09a5f8ee64b41ba12580d4a4163f84ec9f81a4edc5fba7fec105aeeb32dcb68d757cbd276c13a5e09af8302a11e5b503e7911d058ae7c1dcaa44b5927c9ab4f02aa796a478b2442b91f21877c46d91b41b07ba9b2b8514bd1adb787a9a3c27efa27fdeb8371e1d27c66ab80da9ec7c2c1f43145e73dd2483e3a93ea363c28131b8aadcbe8c7fd536caa63a79df8cd3a2e432fb449c6df33bc07a4a85328c5f0d7f1c89d5920bb7272f5b320df888a23877838b9bfb9a53217b09c71b5c5e0ccf18684ecfe43093a8586c2ca5567b99b52dde92398b19af9eaa852639e726d14c261610f64bb225b4d598b3e99f27fd427ccff640bf57173cd0ea640c4fd4662d9c14914d93457b202f3c020c4aec749ee965d824ce864b837797fdb365c5c20bfce3d5f3410308901bb2e6bbd2979af999c909d9f6b240a50559868ade0f8d1b22b8cf1fed7e084c000e3229b36b06c12f9eef090281bbc125f78415f6a9382b6646b5f3741ee2c39ca4a070e3fa322f138ae27f56f7bfecff510cd82b170b2d9c20bcf4133ab2cfb82278268f6a998e07cdd82a5829000182e20381e80220650c58df573197bd182a131c412403032df10dd4be914cad3fee6a8f9f50490dd82a5828000181e203922020bbfd25d9bb672844a5597ab0c099ddd592766e9cc6bfbc1aabcbdeb78b5af603000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x378821c", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x09256a67c71f386327c50728db231ac787c819340a27da9c52bf5338efdc1b8c", + "transactionPosition": 71 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cbdce", + "to": "0xff000000000000000000000000000000002cbe59", + "gas": "0x5842604", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782196cb759078088616c745421a5e0810320c2d4127fb35257616beecae3a3857f10eef0cbe560c70555cf106e7df9ff45475bd7e7f097ada94a97f56afacb8367bae0a7c9b9e07bd27f4372024646cb53abdc2f6be656423de773c1a708381a3424184cf315e80ea091cecc1005df11c33d569f0d275d97d25892f931e33d3817f556f964d44bf425022a7d06d67442c2ad9304f5e6b9b42497880067eaf6df1d5b6d405f1a7296142ea97427040300ece471973832fa6b0dc8cfbd4dbb4b747475ac001497f980a801e5d9ed67095e8253cd38643181f78ed6d1fd39f9d5969faa493b5136c40536e7f464cf02d8b67b682e34825f8d9755f05de55886dc90e1058deab0272e02ecfbbca1f12e12e56123cc2c5ea67f8631520d5691b5a78ddfe1af7e40bebd163fe9a61c4f8a9d02a3939bd06bc3665a97fc75ba9bb0febcfff1b0339d6a88df855e5a15c9a6693164bd5d3e9220869867de3399f97ab90ae31232176481decab82cae43a9c82944ce462cc38e68eb7a112acd831286494b5842f2afd857dda77c58d2134652c533d730e6b5ae06f7d3437c4489c590215f5c8371b355ff2d934d166a48a586e02e8e7f48730810d4b591b5cfad7ebdb03553a70af3c23a214b7b06392f902cbbc6766ca703c25e9122e46cf6b698c6b7b615f8cc1cae3a3f1253f5fadca7be1d9548af2ea031ee8aea3cea3be303178c1d01c9b3b2d5e99daf1546921fb56aaeda89be4f70a11343a4c6a8459454783024ab4aeabba105a969b453cb83ad6dd19bbd8bee6dfe04de7bac58fb49abc7ab6f63831a904a0b66b209f3326c4e21854aff6c2be4f393a145290a9574ae7ab0d6c90ab17da9c5e1c027f06ad6c19797e8f411f5499a6c62b1d8cfa70b6f61fd3f6100b93d6b53141ac5598568e9357449ee5c2b1d444ea251a41956b52d35e2b7cd26dc33c3eaa5067ff1da021e9901f02fa9e7a5a5a55b67c336655a31407ba2899156fc7dc35f221422cdf332c3fd52978b644c6e73a2aff102ff518f8d3ecacdbc64cc07a70f4d62fd677c740f3470e41bf0e28288f3bf8c615054fd2a853c0f80fd284b979999db697097029be9b7a5a0ef6c8abc89ac176180a208cd524679f10c6a9403472f9ff960cebec3e33b9912d62f455ef0adb936e5f88e24127e4f4dd46fe7ff9ae1317c217a630a0760bc84ff5fba0b48b047e5eb25975e00572c6b6dbe561ade01ea50f345d95c91c866537d86c0720e7396dff985a191fd554f838289ae148fb45a6db5ebb752f2690ffcb42e75c29aa74b3f566789ad96d7eda31320391c160dbe295d4a8102d4b331d8a1e1290e41bff958c5e0155f333e1846b21d1a14e0871bacecd05b4a16370aacf1319016359ac2d1d9d3911dd41b1936cc9f0d8d0a33196d4805e869e801df13e1046487c5a0fe53944aa55a6737cc6e0a84fce49f514d76c68e9be3c782c64b788d92c6c04f4c158d5c63670ca19e1a85024017d2160f591c8ac226ffdf96733ccb6bda368b266f6b326ff8b01227d1f4fcbe2850a57c9b71ea234817a198d111f8f81b59b12a4bc48614ec5364db7ef0205315c3530e418f5a039e05883b1401c9d9de917e25a1324bdd5f693cb93a8bd86db1d797bd6be5f9a4078c6710690da48a2b5058939427c91b9184bfba9199a5ce398f3e851e0b2299a1138e3ac216f64a9d8981f312c74419106595e549d9dfcbe7bd6f803995f9f36d20685e0c771c6b73e73c602bb0e18c7c1b71a2b18cababbd1a1891bb3b4b927e58bc8a7b5d6c3433640985609b3e3baf4c178f3a469a52a2697a185fa6b57c2a6d6371cf02f18cdba2ae3b7467a3663962e333726df013118dd4e5f91b258df39a3870e252712e87cc44f2e9e3a7b6e4c503088efa5ce363e7b10d2ce33a87a85629482cb5d64c01ec4f97d0b7f704a8fd30590bcefa2b3699a133a39e0a383cf5db3fcaa299b0154eb3dccdecbd83c3daf38216cd7ce9c9b436ef33decba8a32a2f1a64e5539ebe5b5432d5a438a24d2d6e8ca07f33519cc601dfe0551faeb8514b005c79ad7ed2c7a1f8c9cc2804ca4170bd1ab04e00c0d07e5bef9895515ad0485dcc4b00cdba458655f3818dcdf7757ba76fff722b9856c3925ff17e0213075adcccd677fc0794c92930da09419fbe0fc78c98d466c550a6541aa4e5a01689d9634d384d8b7f77e678676555b342d1f54ab3f7719191c4bc98577a0ead9b05b86e16bd1bdc018a98f467472d8b8e54ca29642b2020d11595a282b3cabd7689bc79d0c5822fa6d3b539a962bdd17b3eb3adf7c34ca9eb2c00a7e18d11e5290d11878e2437876e4d306e2fab457fab2c52deccf7219830e2308d87441b33906d7334feab7c6e9259b8136502f237344b954ab861132dc40769b10ac6f4e4933fa08422e2f499a7e588caec50bb4912e85df69e97c6a148a6961d683b631c82a5e96080368d6800c9908f5e36f93d2d4b3ebcbdeff3e492b26faec713df3f5e74536a313effcb12e1b0c3d2c3e290d34ec267b895f23ca7511cdc0be7dfb6648163f8731e9833b73b77697ee24bb2012cf36986470798b1d00fa384d2e0eeb172ea82d705c6b55785c0c1fc64b0f85081dea8f9b9242961e1251f0d3bc474b6db1977efd1c0d217fbaa15682c62fd9d386fe37b710fb7a3e86018b629cf0c15cb32509633f555f7750c4f553798b97b008b8347c3dedc9d4900000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xa41eb9", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xfe2a899422ce49a8aa92413b3b30dfeef07f4601e1f817a9be1e2681e0c6a3c5", + "transactionPosition": 72 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cbe59", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x510df01", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002cbe59196cb7811a0458d1025820bdda037c22b1041a1ab65a83f366c6dda203b1a83f8a11f7e6d76b019411a960582070588192c2cfcb22bd6934b66eb4b782332859eca0353d7a6a6de5ae787e23ff59078088616c745421a5e0810320c2d4127fb35257616beecae3a3857f10eef0cbe560c70555cf106e7df9ff45475bd7e7f097ada94a97f56afacb8367bae0a7c9b9e07bd27f4372024646cb53abdc2f6be656423de773c1a708381a3424184cf315e80ea091cecc1005df11c33d569f0d275d97d25892f931e33d3817f556f964d44bf425022a7d06d67442c2ad9304f5e6b9b42497880067eaf6df1d5b6d405f1a7296142ea97427040300ece471973832fa6b0dc8cfbd4dbb4b747475ac001497f980a801e5d9ed67095e8253cd38643181f78ed6d1fd39f9d5969faa493b5136c40536e7f464cf02d8b67b682e34825f8d9755f05de55886dc90e1058deab0272e02ecfbbca1f12e12e56123cc2c5ea67f8631520d5691b5a78ddfe1af7e40bebd163fe9a61c4f8a9d02a3939bd06bc3665a97fc75ba9bb0febcfff1b0339d6a88df855e5a15c9a6693164bd5d3e9220869867de3399f97ab90ae31232176481decab82cae43a9c82944ce462cc38e68eb7a112acd831286494b5842f2afd857dda77c58d2134652c533d730e6b5ae06f7d3437c4489c590215f5c8371b355ff2d934d166a48a586e02e8e7f48730810d4b591b5cfad7ebdb03553a70af3c23a214b7b06392f902cbbc6766ca703c25e9122e46cf6b698c6b7b615f8cc1cae3a3f1253f5fadca7be1d9548af2ea031ee8aea3cea3be303178c1d01c9b3b2d5e99daf1546921fb56aaeda89be4f70a11343a4c6a8459454783024ab4aeabba105a969b453cb83ad6dd19bbd8bee6dfe04de7bac58fb49abc7ab6f63831a904a0b66b209f3326c4e21854aff6c2be4f393a145290a9574ae7ab0d6c90ab17da9c5e1c027f06ad6c19797e8f411f5499a6c62b1d8cfa70b6f61fd3f6100b93d6b53141ac5598568e9357449ee5c2b1d444ea251a41956b52d35e2b7cd26dc33c3eaa5067ff1da021e9901f02fa9e7a5a5a55b67c336655a31407ba2899156fc7dc35f221422cdf332c3fd52978b644c6e73a2aff102ff518f8d3ecacdbc64cc07a70f4d62fd677c740f3470e41bf0e28288f3bf8c615054fd2a853c0f80fd284b979999db697097029be9b7a5a0ef6c8abc89ac176180a208cd524679f10c6a9403472f9ff960cebec3e33b9912d62f455ef0adb936e5f88e24127e4f4dd46fe7ff9ae1317c217a630a0760bc84ff5fba0b48b047e5eb25975e00572c6b6dbe561ade01ea50f345d95c91c866537d86c0720e7396dff985a191fd554f838289ae148fb45a6db5ebb752f2690ffcb42e75c29aa74b3f566789ad96d7eda31320391c160dbe295d4a8102d4b331d8a1e1290e41bff958c5e0155f333e1846b21d1a14e0871bacecd05b4a16370aacf1319016359ac2d1d9d3911dd41b1936cc9f0d8d0a33196d4805e869e801df13e1046487c5a0fe53944aa55a6737cc6e0a84fce49f514d76c68e9be3c782c64b788d92c6c04f4c158d5c63670ca19e1a85024017d2160f591c8ac226ffdf96733ccb6bda368b266f6b326ff8b01227d1f4fcbe2850a57c9b71ea234817a198d111f8f81b59b12a4bc48614ec5364db7ef0205315c3530e418f5a039e05883b1401c9d9de917e25a1324bdd5f693cb93a8bd86db1d797bd6be5f9a4078c6710690da48a2b5058939427c91b9184bfba9199a5ce398f3e851e0b2299a1138e3ac216f64a9d8981f312c74419106595e549d9dfcbe7bd6f803995f9f36d20685e0c771c6b73e73c602bb0e18c7c1b71a2b18cababbd1a1891bb3b4b927e58bc8a7b5d6c3433640985609b3e3baf4c178f3a469a52a2697a185fa6b57c2a6d6371cf02f18cdba2ae3b7467a3663962e333726df013118dd4e5f91b258df39a3870e252712e87cc44f2e9e3a7b6e4c503088efa5ce363e7b10d2ce33a87a85629482cb5d64c01ec4f97d0b7f704a8fd30590bcefa2b3699a133a39e0a383cf5db3fcaa299b0154eb3dccdecbd83c3daf38216cd7ce9c9b436ef33decba8a32a2f1a64e5539ebe5b5432d5a438a24d2d6e8ca07f33519cc601dfe0551faeb8514b005c79ad7ed2c7a1f8c9cc2804ca4170bd1ab04e00c0d07e5bef9895515ad0485dcc4b00cdba458655f3818dcdf7757ba76fff722b9856c3925ff17e0213075adcccd677fc0794c92930da09419fbe0fc78c98d466c550a6541aa4e5a01689d9634d384d8b7f77e678676555b342d1f54ab3f7719191c4bc98577a0ead9b05b86e16bd1bdc018a98f467472d8b8e54ca29642b2020d11595a282b3cabd7689bc79d0c5822fa6d3b539a962bdd17b3eb3adf7c34ca9eb2c00a7e18d11e5290d11878e2437876e4d306e2fab457fab2c52deccf7219830e2308d87441b33906d7334feab7c6e9259b8136502f237344b954ab861132dc40769b10ac6f4e4933fa08422e2f499a7e588caec50bb4912e85df69e97c6a148a6961d683b631c82a5e96080368d6800c9908f5e36f93d2d4b3ebcbdeff3e492b26faec713df3f5e74536a313effcb12e1b0c3d2c3e290d34ec267b895f23ca7511cdc0be7dfb6648163f8731e9833b73b77697ee24bb2012cf36986470798b1d00fa384d2e0eeb172ea82d705c6b55785c0c1fc64b0f85081dea8f9b9242961e1251f0d3bc474b6db1977efd1c0d217fbaa15682c62fd9d386fe37b710fb7a3e86018b629cf0c15cb32509633f555f7750c4f553798b97b008b8347c3dedc9d49d82a5829000182e20381e80220ad21a8b5617018e5872ad54d2fb83e094fb5178f7ac7530bc2a82c91c9b0e473d82a5828000181e203922020bc3b85c5479f349cc048347aa46fcd4a1364eb6ec3b191d86bfd70ae490cdc32000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x3e69aeb", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xfe2a899422ce49a8aa92413b3b30dfeef07f4601e1f817a9be1e2681e0c6a3c5", + "transactionPosition": 72 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000181d8a", + "to": "0xff00000000000000000000000000000000181e6b", + "gas": "0x1959fb3", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f385181a8182004081820e58c0a829718db09f68797f2d9be15809d2e1387a94bd8b1a51d53013dab9b9611d70aece45b4771bd878d93db7f151e76e9a812d513d34dd30afd572af6adb1763bf9583245961b2e14558086e2eb0da8a1fbf572d042dc1f0d6f0ac2bdab7ed340306aa8db0e432b57aca8579449eab439fc87f84c441133383e504824927027f70f722acfaebc2ebe1ab004f5989794a51b59d9f1ac7749ae9d4cf9b0860b9b1f8b9bd11b0e7f183bf20e9d1ad2278791c6c438c3c768c6e9395786a463bd9ba4f1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000000000" + }, + "result": { + "gasUsed": "0x1517eb4", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x1b93e4163b1bd35cb6342233c84dcda34d3347e5f5ea996f90e63b2d0e71f98e", + "transactionPosition": 73 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002c2abc", + "to": "0xff000000000000000000000000000000002c2ade", + "gas": "0x4dea6fd", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000007878219911e590780960f552b98a30ad3840c6da73d540213f5723c320b231419709ba5b7381feff81e38de449fdf783e55f3f411c1baa728854228f264cb115325348d007cb5922c59e926caf1a0d78fe1b4ec9841883cfd34d7a6b2eaf574097ca32f079d4a862a12245f8aec0237e66ee5b29da0ab67f6d5da06f5311dc40f205aef2c09826d84283d6c951d802f3a4dbba31b38abf15fa07625c908e2c2797578c1431ee2894e7d954bf134439530ae612ebcf87deb1f354bc3bb96bfa4f4ca53c79112772671891803ee5c6dc2bebf2beea9bed50c947b731c5faf9369b3c42fb5cfe9d0236f4f559a05a7a8a584cf26637e6713ad1a80520fb7f0d3e6f3c70866b6371c17ee1ae76dbbcc809a0c2a70a2e6241ed2b04d9f35f5e9d215e346266321856099c81550a8d7b7e6a82bb31f5002c5545e9e853ae79cbe26d5d9fbdca504de06b48dbec1614875ead88b1aacc0a76a6e1123b91092b53b92d6507d2c4b90b950406e3706e22fd16ba83ef4474f93edf8e908ca824aa875175425b757365e0d94af4d8f054278185210db77b78c66d44ead9ad621b2080b8a96d41aece81e05638984440954d538f31a8d90847550cfc56846a51c83ec9630241066785b926985a90ab0d30f815a1a365adadf5560b911a33336e278dcd77eceef98eaaa2acc96c03806004221de8e1e43c097d2ddd72870cf493361094483976e1cf852362c17559004265230b9f6582e572b3f359a2242948c49b4273efce8cf7e2ae233ab96db76f22b2214c2c2b58edc3faa2fc67e589bad0a2eb05e0830be889f439d27a7c1dab781d301ac88790943ade6fd61679b08f70bfdde55c4f98f8d41a9ff2ad730a8221eacb29d4b26ddcf813bffa913bf5ab88e3f73a9a3b6b8b8e433ecb6ba009384e088519a4422bb250aacf4cc81d39be173eb79b52b2991171310067a240b6507a0b344cecd6896062ed7dc28022567c5236f2ad5e97fbd6c37621fbfe727b11536ee5e6ba31030f252ce8a05224bc69617ba5aa7a5a8561d68c97b7532181c77df9804feb4853fdd3efbff953eb9d8e826ea01b27919b33911bbec2f9041b2aa05c84419df21b1f91515ec4467a3abcb30aab03f04018b351ec6684a1fb67e7f67f356ac375c34f2a181d718318f92b97acd7328907279031a4b93eebc88b4fc70b4e831be82deef3e724b3af5608f1f311723a3a3c7dfa2be97407358469e04304456ee864fc2b212eaa3adff6d89c1991ca2fa1e988eb2769178e935a996ac0c1efa44000328ff4667474f79536499392534a06cd4638cfabbddaab5028f202a3c7364fc01f147902c9bdbc2bfd750498927e9a0769330daada7cb605619ad35a6f016f2405e9978b6df94c5226f1c796328027b9be8f87dfd536ffd4a3cba032084902817ef3b5a014a29d81db98cf0f0ffcc696a4408a40097d0e809237cab607f0ec005572c41cc3a7829a2ecbd9df4280d283f23882fbd6d159e208f0447ef60c9c20ed40115ca2631ebac7a8b5920dfeb64c5354fc1a6a3e489b33e9fe5c7dbf8139d06ed75b648aeaa7995b013265228524981c8f09cc1268bb0a626ab89d3d829fefafddd2a7734787f1e691f862ded29aec3478520af69fda96493b04ba75b38ed974f0ed859bf8703f7b9579c2bc9358994a71481b1450f791996ddd13b9faee4684551059290a96485a537d58087d1e857eb09bf9e2896542631756c5bab31ee5c4a2b4bee767d62945ae42aed7edb1d294df7698b64f1b603179a12f1efd28980a98ba42890c2afcafebf1a970f660ab38ec4120192e3087a438c1860274a65e5928726c4e42bfd07a8adf53878b8aef598229d957591aa63b0e9e3e21a1ad7df441c5292a24f4e6f47e6dd12310a9037da22169cdcb1e46fb20f63afc4133ccb82a4ec7e21af6670e565090ab805d9a93b30648b57e472f24deb499426392b6134b2de005119751aadbdb7535e60042f0f8d549522bacdaa1c55572d267294fd3f2c98230004b05d2f2b2315acf1e4e9d46d71e3811b25690382e88dedf7046af4d4439aaedc1e8bdbed282aeed7d88e451e393bcabb3e8c7e6343aceddaf51dceaaa4041231b5008bdee7e20d551bc2003d54518a240afc80bee8c709256971ce9ae1ed882c2f080ffcc7e9cf0b5a15a678e81ed782fe8ab12df8aac05305bac6864eeb2112012703cc57fa1c7c09845cab94dad1eb3562aeeb75dee6b56fb23f915865f0fa577bae17fbba3d8dab489dc750e20d4bade21b94fa009563993932a5f67cadd5904177a457ede06a0bb3e41f64af699d4b2e0f11368d7c9bd7a490a34b334766df92964dec91fd5cc7e1768319c3b761a16ce91b980ec58ae990b2d2828a0e780a8e84967fa7c03cc052d842cdc645b47f3b000286cc8f28fee0542bd0a715c04816572412a78e07a595f646354de4e8959a80713401b9bae9624f6fd9828d1f36c440417ab6c6e4f7d3ce8f165529983ab31f1bf09eb67f7b19d6f192548cdb5f34accfdfd6a69f081cf9ffa9994cb5824ff1f89bd9915ceb08eb935c7d1181aba7da21f540b7351098fcfb708e91d65d7b091cb0b17dd60f241e87cd8e6635b0eba3b015185d25de049b63a7c32ce752989dac83f03a2af6664f6eb072202100a5a6d10a8ea84d2cc10872acafd6e2989c32baf41e3c4f3d5d55ec352f30517ebd74b96a10b80454e52009898eab5a699900000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x96b2d7", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf14f04259ac556d1381965bd9d12c803e7e1ec83656ef48e3d57130963a8fd10", + "transactionPosition": 74 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002c2ade", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x478d47b", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002c2ade19911e811a0459c3f65820be2d62bd4039006ec2601e9b85f158e3facb09f64592cf07d68ade65df8ce7345820a821855a854abe1c7ada3b128358010c99e1da5bea3dd573733e8978edfe3483590780960f552b98a30ad3840c6da73d540213f5723c320b231419709ba5b7381feff81e38de449fdf783e55f3f411c1baa728854228f264cb115325348d007cb5922c59e926caf1a0d78fe1b4ec9841883cfd34d7a6b2eaf574097ca32f079d4a862a12245f8aec0237e66ee5b29da0ab67f6d5da06f5311dc40f205aef2c09826d84283d6c951d802f3a4dbba31b38abf15fa07625c908e2c2797578c1431ee2894e7d954bf134439530ae612ebcf87deb1f354bc3bb96bfa4f4ca53c79112772671891803ee5c6dc2bebf2beea9bed50c947b731c5faf9369b3c42fb5cfe9d0236f4f559a05a7a8a584cf26637e6713ad1a80520fb7f0d3e6f3c70866b6371c17ee1ae76dbbcc809a0c2a70a2e6241ed2b04d9f35f5e9d215e346266321856099c81550a8d7b7e6a82bb31f5002c5545e9e853ae79cbe26d5d9fbdca504de06b48dbec1614875ead88b1aacc0a76a6e1123b91092b53b92d6507d2c4b90b950406e3706e22fd16ba83ef4474f93edf8e908ca824aa875175425b757365e0d94af4d8f054278185210db77b78c66d44ead9ad621b2080b8a96d41aece81e05638984440954d538f31a8d90847550cfc56846a51c83ec9630241066785b926985a90ab0d30f815a1a365adadf5560b911a33336e278dcd77eceef98eaaa2acc96c03806004221de8e1e43c097d2ddd72870cf493361094483976e1cf852362c17559004265230b9f6582e572b3f359a2242948c49b4273efce8cf7e2ae233ab96db76f22b2214c2c2b58edc3faa2fc67e589bad0a2eb05e0830be889f439d27a7c1dab781d301ac88790943ade6fd61679b08f70bfdde55c4f98f8d41a9ff2ad730a8221eacb29d4b26ddcf813bffa913bf5ab88e3f73a9a3b6b8b8e433ecb6ba009384e088519a4422bb250aacf4cc81d39be173eb79b52b2991171310067a240b6507a0b344cecd6896062ed7dc28022567c5236f2ad5e97fbd6c37621fbfe727b11536ee5e6ba31030f252ce8a05224bc69617ba5aa7a5a8561d68c97b7532181c77df9804feb4853fdd3efbff953eb9d8e826ea01b27919b33911bbec2f9041b2aa05c84419df21b1f91515ec4467a3abcb30aab03f04018b351ec6684a1fb67e7f67f356ac375c34f2a181d718318f92b97acd7328907279031a4b93eebc88b4fc70b4e831be82deef3e724b3af5608f1f311723a3a3c7dfa2be97407358469e04304456ee864fc2b212eaa3adff6d89c1991ca2fa1e988eb2769178e935a996ac0c1efa44000328ff4667474f79536499392534a06cd4638cfabbddaab5028f202a3c7364fc01f147902c9bdbc2bfd750498927e9a0769330daada7cb605619ad35a6f016f2405e9978b6df94c5226f1c796328027b9be8f87dfd536ffd4a3cba032084902817ef3b5a014a29d81db98cf0f0ffcc696a4408a40097d0e809237cab607f0ec005572c41cc3a7829a2ecbd9df4280d283f23882fbd6d159e208f0447ef60c9c20ed40115ca2631ebac7a8b5920dfeb64c5354fc1a6a3e489b33e9fe5c7dbf8139d06ed75b648aeaa7995b013265228524981c8f09cc1268bb0a626ab89d3d829fefafddd2a7734787f1e691f862ded29aec3478520af69fda96493b04ba75b38ed974f0ed859bf8703f7b9579c2bc9358994a71481b1450f791996ddd13b9faee4684551059290a96485a537d58087d1e857eb09bf9e2896542631756c5bab31ee5c4a2b4bee767d62945ae42aed7edb1d294df7698b64f1b603179a12f1efd28980a98ba42890c2afcafebf1a970f660ab38ec4120192e3087a438c1860274a65e5928726c4e42bfd07a8adf53878b8aef598229d957591aa63b0e9e3e21a1ad7df441c5292a24f4e6f47e6dd12310a9037da22169cdcb1e46fb20f63afc4133ccb82a4ec7e21af6670e565090ab805d9a93b30648b57e472f24deb499426392b6134b2de005119751aadbdb7535e60042f0f8d549522bacdaa1c55572d267294fd3f2c98230004b05d2f2b2315acf1e4e9d46d71e3811b25690382e88dedf7046af4d4439aaedc1e8bdbed282aeed7d88e451e393bcabb3e8c7e6343aceddaf51dceaaa4041231b5008bdee7e20d551bc2003d54518a240afc80bee8c709256971ce9ae1ed882c2f080ffcc7e9cf0b5a15a678e81ed782fe8ab12df8aac05305bac6864eeb2112012703cc57fa1c7c09845cab94dad1eb3562aeeb75dee6b56fb23f915865f0fa577bae17fbba3d8dab489dc750e20d4bade21b94fa009563993932a5f67cadd5904177a457ede06a0bb3e41f64af699d4b2e0f11368d7c9bd7a490a34b334766df92964dec91fd5cc7e1768319c3b761a16ce91b980ec58ae990b2d2828a0e780a8e84967fa7c03cc052d842cdc645b47f3b000286cc8f28fee0542bd0a715c04816572412a78e07a595f646354de4e8959a80713401b9bae9624f6fd9828d1f36c440417ab6c6e4f7d3ce8f165529983ab31f1bf09eb67f7b19d6f192548cdb5f34accfdfd6a69f081cf9ffa9994cb5824ff1f89bd9915ceb08eb935c7d1181aba7da21f540b7351098fcfb708e91d65d7b091cb0b17dd60f241e87cd8e6635b0eba3b015185d25de049b63a7c32ce752989dac83f03a2af6664f6eb072202100a5a6d10a8ea84d2cc10872acafd6e2989c32baf41e3c4f3d5d55ec352f30517ebd74b96a10b80454e52009898eab5a6999d82a5829000182e20381e80220c128d6724fd7c641a959983b978d9fa693eb45ebfa44d0befd58193ac5f96c55d82a5828000181e2039220201026e6f2fadbf4f6d050d669a0d7171f9b602d906e9c51a6a311c86b5273dd2b000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x30520d4", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf14f04259ac556d1381965bd9d12c803e7e1ec83656ef48e3d57130963a8fd10", + "transactionPosition": 74 + }, + { + "type": "call", + "subtraces": 21, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cffc4", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x530c26f6", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000007128188828bd82a5828000181e203922020638379e47a3916bae849a7d2eb688c4c79f2a16dc32813f0fbc2799237183c251b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d41584367354149677a69414f5733616d6d6f2f2f342b6f4e77425546714c787a57712b5365565143506f79524842494f4c46451a00381e4f1a004fa10f40480016de9bafa1a688405842018c87b3a0c61f501ebe01004d2c3989e74e8d3409e66ea7cf0485433f61ea84b92cab8863c916181ce0de0547b883855699afb28d70d8ed96c4fa49608949373100828bd82a5828000181e203922020a646954f2e700a51f149491da320e5065d7266a505b4013f3fe2b95a410487341b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d415843673541496777613542697561305a34474d65625947757336787a484e5a6e47726c744b456d7535744b3536676e366e551a00381e4f1a004fa10f40480016de9bafa1a6884058420144ad58edfe5d5d30d7b9789535d6c44c6f0014a05b3cb8e1141c1bf9a506d34c68d42e1f2b90ad6b13e231d84358535d36eb329f4e126dc5667eeebe8f87d40701828bd82a5828000181e20392202019553461c49b73c9ac6661d0bcbad1abd0b5747cb3b2c2cdbaaf2f0ee22e4f381b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d41584367354149676d43454d37766c6163766b78657351395a6e785554564433696473695259644c613641636b4d6266514b381a00381e4f1a004fa10f40480016de9bafa1a6884058420111c340ca79b4ec152f975556bdca273716cd512588b630a12ac48109c4719e00406e504a97193fd228a1fa0492381acf41c3929fdc8f5a2d0cec1eef7216c79d01828bd82a5828000181e20392202078ef0796f6fb842665ab9c1896535d700bea5b1f70189272d8bea7a61b36f43b1b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d415843673541496749617553335659766c5578394e7575384c42776d507a55326f6d6e6a4c724e2b534474686439535656776b1a00381e4f1a004fa10f40480016de9bafa1a68840584201634845b6b07346e86e066a38f58506581f2c0edc279607b5d55f0245a2b0713046385efd92bd31fc2553d484d6067a699f1b9997dac16cd7f6467904f029f25501828bd82a5828000181e203922020997081e8865104540ec72c6f436a983e9633c5e5098d7ab01edf1a381c9495201b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d415843673541496742437463616277537077566247476239436d3068686a2b5075756e485471597a52694d41433676563879771a00381e4f1a004fa10f40480016de9bafa1a688405842013f72930bc2558ba9617bd708bd4abf74e197ba4483a97ced4bd8029bc0c5be26696583bf0c963021d7009827f800494e13e8cb5c0e17a44955cfe2739d8ed5c301828bd82a5828000181e20392202058a6d1ab9902fade4bf717e7331aee4b46fc210ba1b972dcf3ea2048a541463e1b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d41584367354149674a4b56744f37483938726c477248634d5738466a5764644d6d532f54356a34334b4c4944563575713037491a00381e501a004fa11040480016de9ad5b2b21640584201df7baeaa70111f1bf9cabbebb63a42ed93369fdbc655810c47539e2109e457984c9dfb31ebbb414bf6bde203b8b16895df94722e92d76f49d075deb1528e4e1d01828bd82a5828000181e2039220204792c6cd5e8d978b7093e5d9a26a72c45fe7c815fad2bced0745522d5b2004261b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d41584367354149677567622f52586644466330763238562b304e4d684c5263723376596f70682f455246325435742f354970731a00381e501a004fa11040480016de9ad5b2b2164058420119c8ffdccfa265a45e1f93b09d158cb6f6d9313a9acb81dbd40ac6006d25003e29d4bcc093e3cc3de10fb0a3f4221c8e98a8785e3c63fc8843786af1f975cd3001828bd82a5828000181e203922020ffcfabfe3a003640cba142966a19f85d1028b10edb5b0d647273c55857af431f1b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d4158436735414967764c754c47416773594e6a3972372f734b67656433356f6d6d31545141614830716d6b6d307a54796774411a00381e501a004fa11040480016de9ad5b2b21640584201b639096d1da0d389c6fd4d4104be639dbb22c591fe7bdebb2907aee734a9b73e3cb9f9262905b81f92997b92e23616cf3f635eef8405e4d794f80f4fa431c2e7010000000000000000000000000000" + }, + "result": { + "gasUsed": "0x3374f7cd", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002d82881a045b57631a045b57641a045b57651a045b57661a045b57671a045b57681a045b57691a045b576a42140100000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff00000000000000000000000000000000108a0a", + "gas": "0x52f45b15", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000014c1cb970000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000064500c4ffb3010000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x133019", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x52e08644", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x52cfba64", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 3 + ], + "action": { + "callType": "staticcall", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000022cdaa", + "gas": "0x52bcb4e2", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e38258418c87b3a0c61f501ebe01004d2c3989e74e8d3409e66ea7cf0485433f61ea84b92cab8863c916181ce0de0547b883855699afb28d70d8ed96c4fa49608949373100589d8bd82a5828000181e203922020638379e47a3916bae849a7d2eb688c4c79f2a16dc32813f0fbc2799237183c251b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d41584367354149677a69414f5733616d6d6f2f2f342b6f4e77425546714c787a57712b5365565143506f79524842494f4c46451a00381e4f1a004fa10f40480016de9bafa1a688400000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2e9807", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 4 + ], + "action": { + "callType": "staticcall", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000022cdaa", + "gas": "0x528ae02a", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e382584144ad58edfe5d5d30d7b9789535d6c44c6f0014a05b3cb8e1141c1bf9a506d34c68d42e1f2b90ad6b13e231d84358535d36eb329f4e126dc5667eeebe8f87d40701589d8bd82a5828000181e203922020a646954f2e700a51f149491da320e5065d7266a505b4013f3fe2b95a410487341b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d415843673541496777613542697561305a34474d65625947757336787a484e5a6e47726c744b456d7535744b3536676e366e551a00381e4f1a004fa10f40480016de9bafa1a688400000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x26f6e7", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 5 + ], + "action": { + "callType": "staticcall", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000022cdaa", + "gas": "0x526100b3", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e382584111c340ca79b4ec152f975556bdca273716cd512588b630a12ac48109c4719e00406e504a97193fd228a1fa0492381acf41c3929fdc8f5a2d0cec1eef7216c79d01589d8bd82a5828000181e20392202019553461c49b73c9ac6661d0bcbad1abd0b5747cb3b2c2cdbaaf2f0ee22e4f381b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d41584367354149676d43454d37766c6163766b78657351395a6e785554564433696473695259644c613641636b4d6266514b381a00381e4f1a004fa10f40480016de9bafa1a688400000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x26f6e7", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 6 + ], + "action": { + "callType": "staticcall", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000022cdaa", + "gas": "0x5237213c", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e3825841634845b6b07346e86e066a38f58506581f2c0edc279607b5d55f0245a2b0713046385efd92bd31fc2553d484d6067a699f1b9997dac16cd7f6467904f029f25501589d8bd82a5828000181e20392202078ef0796f6fb842665ab9c1896535d700bea5b1f70189272d8bea7a61b36f43b1b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d415843673541496749617553335659766c5578394e7575384c42776d507a55326f6d6e6a4c724e2b534474686439535656776b1a00381e4f1a004fa10f40480016de9bafa1a688400000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x26f6e7", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 7 + ], + "action": { + "callType": "staticcall", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000022cdaa", + "gas": "0x520d41c5", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e38258413f72930bc2558ba9617bd708bd4abf74e197ba4483a97ced4bd8029bc0c5be26696583bf0c963021d7009827f800494e13e8cb5c0e17a44955cfe2739d8ed5c301589d8bd82a5828000181e203922020997081e8865104540ec72c6f436a983e9633c5e5098d7ab01edf1a381c9495201b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d415843673541496742437463616277537077566247476239436d3068686a2b5075756e485471597a52694d41433676563879771a00381e4f1a004fa10f40480016de9bafa1a688400000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x26f6e7", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 8 + ], + "action": { + "callType": "staticcall", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000022cdaa", + "gas": "0x51e3624e", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e3825841df7baeaa70111f1bf9cabbebb63a42ed93369fdbc655810c47539e2109e457984c9dfb31ebbb414bf6bde203b8b16895df94722e92d76f49d075deb1528e4e1d01589d8bd82a5828000181e20392202058a6d1ab9902fade4bf717e7331aee4b46fc210ba1b972dcf3ea2048a541463e1b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d41584367354149674a4b56744f37483938726c477248634d5738466a5764644d6d532f54356a34334b4c4944563575713037491a00381e501a004fa11040480016de9ad5b2b216400000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x26f6e7", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 9 + ], + "action": { + "callType": "staticcall", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000022cdaa", + "gas": "0x51b982d6", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e382584119c8ffdccfa265a45e1f93b09d158cb6f6d9313a9acb81dbd40ac6006d25003e29d4bcc093e3cc3de10fb0a3f4221c8e98a8785e3c63fc8843786af1f975cd3001589d8bd82a5828000181e2039220204792c6cd5e8d978b7093e5d9a26a72c45fe7c815fad2bced0745522d5b2004261b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d41584367354149677567622f52586644466330763238562b304e4d684c5263723376596f70682f455246325435742f354970731a00381e501a004fa11040480016de9ad5b2b216400000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x26f6e7", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 10 + ], + "action": { + "callType": "staticcall", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000022cdaa", + "gas": "0x518fa35f", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e3825841b639096d1da0d389c6fd4d4104be639dbb22c591fe7bdebb2907aee734a9b73e3cb9f9262905b81f92997b92e23616cf3f635eef8405e4d794f80f4fa431c2e701589d8bd82a5828000181e203922020ffcfabfe3a003640cba142966a19f85d1028b10edb5b0d647273c55857af431f1b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d4158436735414967764c754c47416773594e6a3972372f734b67656433356f6d6d31545141614830716d6b6d307a54796774411a00381e501a004fa11040480016de9ad5b2b216400000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x26f6e7", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 11 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff00000000000000000000000000000000000007", + "gas": "0x508a6662", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000c26ddbd50000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000064500aa9b8b010000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2edc57", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000104f002b5ff708418d6c8000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [ + 12 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff00000000000000000000000000000000000007", + "gas": "0x4a86bae6", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000d7d4deed00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000026f844500aa9b8b014200064e0003782dace9d9000000000000005902538288861a00108a0ad82a5828000181e203922020638379e47a3916bae849a7d2eb688c4c79f2a16dc32813f0fbc2799237183c251b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e203922020a646954f2e700a51f149491da320e5065d7266a505b4013f3fe2b95a410487341b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e20392202019553461c49b73c9ac6661d0bcbad1abd0b5747cb3b2c2cdbaaf2f0ee22e4f381b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e20392202078ef0796f6fb842665ab9c1896535d700bea5b1f70189272d8bea7a61b36f43b1b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e203922020997081e8865104540ec72c6f436a983e9633c5e5098d7ab01edf1a381c9495201b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e20392202058a6d1ab9902fade4bf717e7331aee4b46fc210ba1b972dcf3ea2048a541463e1b00000008000000001a001782c01a001b77401a00381e50861a00108a0ad82a5828000181e2039220204792c6cd5e8d978b7093e5d9a26a72c45fe7c815fad2bced0745522d5b2004261b00000008000000001a001782c01a001b77401a00381e50861a00108a0ad82a5828000181e203922020ffcfabfe3a003640cba142966a19f85d1028b10edb5b0d647273c55857af431f1b00000008000000001a001782c01a001b77401a00381e50800000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x397cbb2", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000067844f002b5c7eda94a39380000000000000500006a8d4d4487a428de5110000000000520002f0503706f730bd424045568000000000583083820880820080881a034d18b51a034d18b61a034d18b71a034d18b81a034d18b91a034d18ba1a034d18bb1a034d18bc00000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 12, + 0 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000007", + "to": "0xff00000000000000000000000000000000000006", + "gas": "0x475d9536", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000de180de3000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000277821a85223bdf59026e861a0022cdaa06054e0003782dace9d9000000000000005902538288861a00108a0ad82a5828000181e203922020638379e47a3916bae849a7d2eb688c4c79f2a16dc32813f0fbc2799237183c251b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e203922020a646954f2e700a51f149491da320e5065d7266a505b4013f3fe2b95a410487341b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e20392202019553461c49b73c9ac6661d0bcbad1abd0b5747cb3b2c2cdbaaf2f0ee22e4f381b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e20392202078ef0796f6fb842665ab9c1896535d700bea5b1f70189272d8bea7a61b36f43b1b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e203922020997081e8865104540ec72c6f436a983e9633c5e5098d7ab01edf1a381c9495201b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e20392202058a6d1ab9902fade4bf717e7331aee4b46fc210ba1b972dcf3ea2048a541463e1b00000008000000001a001782c01a001b77401a00381e50861a00108a0ad82a5828000181e2039220204792c6cd5e8d978b7093e5d9a26a72c45fe7c815fad2bced0745522d5b2004261b00000008000000001a001782c01a001b77401a00381e50861a00108a0ad82a5828000181e203922020ffcfabfe3a003640cba142966a19f85d1028b10edb5b0d647273c55857af431f1b00000008000000001a001782c01a001b77401a00381e508040000000000000000000" + }, + "result": { + "gasUsed": "0xa1c4027", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000003083820880820080881a034d18b51a034d18b61a034d18b71a034d18b81a034d18b91a034d18ba1a034d18bb1a034d18bc00000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 13 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000022cdaa", + "gas": "0x10787f37", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009582588d8bd82a5828000181e203922020638379e47a3916bae849a7d2eb688c4c79f2a16dc32813f0fbc2799237183c251b0000000800000000f54500aa9b8b0144008a944278346d41584367354149677a69414f5733616d6d6f2f2f342b6f4e77425546714c787a57712b5365565143506f79524842494f4c46451a00381e4f1a004fa10f40480016de9bafa1a688401a045b57630000000000000000000000" + }, + "result": { + "gasUsed": "0x98587", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 14 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000022cdaa", + "gas": "0x106e1d88", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009582588d8bd82a5828000181e203922020a646954f2e700a51f149491da320e5065d7266a505b4013f3fe2b95a410487341b0000000800000000f54500aa9b8b0144008a944278346d415843673541496777613542697561305a34474d65625947757336787a484e5a6e47726c744b456d7535744b3536676e366e551a00381e4f1a004fa10f40480016de9bafa1a688401a045b57640000000000000000000000" + }, + "result": { + "gasUsed": "0x98587", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 15 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000022cdaa", + "gas": "0x1063bbd9", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009582588d8bd82a5828000181e20392202019553461c49b73c9ac6661d0bcbad1abd0b5747cb3b2c2cdbaaf2f0ee22e4f381b0000000800000000f54500aa9b8b0144008a944278346d41584367354149676d43454d37766c6163766b78657351395a6e785554564433696473695259644c613641636b4d6266514b381a00381e4f1a004fa10f40480016de9bafa1a688401a045b57650000000000000000000000" + }, + "result": { + "gasUsed": "0x98587", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 16 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000022cdaa", + "gas": "0x10595a2a", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009582588d8bd82a5828000181e20392202078ef0796f6fb842665ab9c1896535d700bea5b1f70189272d8bea7a61b36f43b1b0000000800000000f54500aa9b8b0144008a944278346d415843673541496749617553335659766c5578394e7575384c42776d507a55326f6d6e6a4c724e2b534474686439535656776b1a00381e4f1a004fa10f40480016de9bafa1a688401a045b57660000000000000000000000" + }, + "result": { + "gasUsed": "0x98587", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 17 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000022cdaa", + "gas": "0x104ef87c", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009582588d8bd82a5828000181e203922020997081e8865104540ec72c6f436a983e9633c5e5098d7ab01edf1a381c9495201b0000000800000000f54500aa9b8b0144008a944278346d415843673541496742437463616277537077566247476239436d3068686a2b5075756e485471597a52694d41433676563879771a00381e4f1a004fa10f40480016de9bafa1a688401a045b57670000000000000000000000" + }, + "result": { + "gasUsed": "0x98587", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 18 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000022cdaa", + "gas": "0x104496cd", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009582588d8bd82a5828000181e20392202058a6d1ab9902fade4bf717e7331aee4b46fc210ba1b972dcf3ea2048a541463e1b0000000800000000f54500aa9b8b0144008a944278346d41584367354149674a4b56744f37483938726c477248634d5738466a5764644d6d532f54356a34334b4c4944563575713037491a00381e501a004fa11040480016de9ad5b2b216401a045b57680000000000000000000000" + }, + "result": { + "gasUsed": "0x98587", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 19 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000022cdaa", + "gas": "0x103a351e", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009582588d8bd82a5828000181e2039220204792c6cd5e8d978b7093e5d9a26a72c45fe7c815fad2bced0745522d5b2004261b0000000800000000f54500aa9b8b0144008a944278346d41584367354149677567622f52586644466330763238562b304e4d684c5263723376596f70682f455246325435742f354970731a00381e501a004fa11040480016de9ad5b2b216401a045b57690000000000000000000000" + }, + "result": { + "gasUsed": "0x98587", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 20 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000022cdaa", + "gas": "0x102fd36f", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009582588d8bd82a5828000181e203922020ffcfabfe3a003640cba142966a19f85d1028b10edb5b0d647273c55857af431f1b0000000800000000f54500aa9b8b0144008a944278346d4158436735414967764c754c47416773594e6a3972372f734b67656433356f6d6d31545141614830716d6b6d307a54796774411a00381e501a004fa11040480016de9ad5b2b216401a045b576a0000000000000000000000" + }, + "result": { + "gasUsed": "0x98587", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", + "transactionPosition": 75 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001c17d8", + "to": "0xff000000000000000000000000000000001c17eb", + "gas": "0x4af5d01", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000072818187081a000122f0d82a5829000182e20381e802203050bd59cec207bc1408562d631cd13b7a00056b34e3dc612288cec5950573111a0037e0c0811a045b35581a00412a9ad82a5828000181e203922020e296585450fb35885860e0c4ac16cc5a7b1013b0f76d736b884763912a3e7e080000000000000000000000000000" + }, + "result": { + "gasUsed": "0x362e335", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x78132bb8f711bfc5da6a26980693e7256b6ba22e5d829b3d2e8614ba26b0a953", + "transactionPosition": 76 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001c17eb", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x4a3f6b1", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x78132bb8f711bfc5da6a26980693e7256b6ba22e5d829b3d2e8614ba26b0a953", + "transactionPosition": 76 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001c17eb", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x4933169", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x78132bb8f711bfc5da6a26980693e7256b6ba22e5d829b3d2e8614ba26b0a953", + "transactionPosition": 76 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001c17eb", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x4811bf8", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a00412a9a811a045b35580000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x478528", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020e296585450fb35885860e0c4ac16cc5a7b1013b0f76d736b884763912a3e7e08000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x78132bb8f711bfc5da6a26980693e7256b6ba22e5d829b3d2e8614ba26b0a953", + "transactionPosition": 76 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002d0815", + "to": "0xff000000000000000000000000000000002d082d", + "gas": "0x512a1bc", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782194e995907808c80a1a7f1aef23e9c5a72f013f261279d9c83df068122083bfc73c928562b99d7e142e8323c3b1ba398efc3fddd17d686367ba915da2e8f05f5605d58f2957927a51de60a07ab0dbd24ac1e8a52b6af11b95df831d14dbac25d750fd4a45dca0837df29befcba63b8178ea8f02340b6d7d6bdb058bfc565ded5260ada6394f7c68c9a4d8494e84eeac2866efd78a5ab8995d7fa578bdc9df4b54ede4212535f7f890a681a729126bc2012fbb57b1cb3b59a93bec4617ee15ad138e9985b14b4955cd39414d99e873f5f4647c36d32362ee8d01c892a0117abed4d76e5f4f8fffbef81f01b535662b42ce3ccf5b415a2b8a671847288a295efc0f210cd27009e8084a633405e7f458cf404909035e07384d4f599bc4d7a2de45c9630b2b12c33072bff14f9e8dc7cbefc0f2573c2227e89711e63ae803944745a28949dc0ac962d4b8faccb5730b1a5e95e59c269dc1fb1a45fe8186d56382cfcc8c2cb70e93f01a16f059d08f5cde17a7fc62454ec77182142a08e69d2b2b4ff48333542077db708bd647e0ac8f4d1db47b183c91f6084957b28fb75778288f8b83975bc630c1ee44dcd9a9b95c3e440aac0bfe88683986dcca45b0e4c16286c3a2aeb9f7e1d43b914cd0b3f629ee426feff417c321ddb9b798070e040a269c9e22b1e1a81e20931d105241068cf3ae4792bda0f15593e4482dfeeae4e2f0386f08eb5cf7f1bbec416fa4378dfd75924a56d04610790aeb39aaaa8c2840f7c52fcb8f89a8b2d7cc4336ecac6d9e40223d45b31f2926dd0e45a1bfb6f0b6cfb5f4d0f0d8f9255aa0021ea31b75191ea0159be650d495e4dbe608db1ecfba2da7de60a321e5f7da691ad4db7db3b5c7885ad51cea72d38ad5c0eb37188c86cbe01c938896c0b6893c8698a5f7f1eb5cea5cdbcd39f718098086ea5be03d25c5d9ab5a30d96b78308b14a7ca1b098b8045c4d90a78206fc18fe6e1cf8c4cca2cda8dcd6b345211e4d8841a90978a7a21c58474c06aa5e568adaaa9fa880ac84956894d2e976d459bed01056a00e69bfccfe9d47f65fdef85720468801603ba01a63865c99acba06990df8d8ada941b6ebedc7484a9a574a18b16cea4683a8bfd6a90e1d845fa4ca568f3e4ea10818a8b551ee7defa34f7d95a71ed3bd40ceb688ec6c00cc207ce8305060b96a5acfb86c38a514bc3223a21e36a2212bf47bef2c2e6b3a93ad463501f46a336efed31b3d9e9998dbb10362e86135838c62a8fbb0489c18f72110dd2fc63428d2dd7e4351b2e075ffc89d9f99a1c2e049c1b5c7ddcebf1972cc4208122a1c981f44bd5c4810a2233922ceadebff26e745d4dba5cd698659399f6eb4b80e14b0f5b2d3604c971c4d6d5a2a3aad696a5660ceb110379fad11e21bcbcf82cc7c9de0ea2afbc0a90c4e22b1562cb1f436a758f2550d8ec0f06ea21e2a230e94e2b1eb5f3dba6341a01dcb08c9c9e19a68e1302b6ec8d9453214ec9340261329fc6389bbe2ee256e72c6ee1440ec2cd7d6e9a0ccaa1676826978e9763fdb9883efd5856573bffda76b38e20a84a48ef7aa8f17163962498dad8493ee48f8f0aca45098fc026df4b3dc20231bb647d894380c97bdedcd652bce638f65cb1b82c672e1aba05a9220b3540cbfed4bc74626309f44fd91d4d666349fd06ac0af5eaa45e37913a9ac44edd81d852c06f2a4fc567dc4c88164be2e004c7c4adc45223a4f6c0e34056c15393ef77c4a6f7f8ebd40774402335828324485c93f37cc01627a3234c1544fbda48a4d48be691d60055a3dbc38890cc0a5c9c89b4487f9865c0279f8898b77f66ee7c0b5f60b7f93cff14cffbd5465f61130bcee8107b17b6f761697f9008048dee7d0d3ae8519d43e3769b6c954fbbde729cd7c2fb86f97a78a947df5709f793c3d532e1dc4f81943b2f4e3502ffa30bd2d0da55c7661d6e779218c9b5708687671a0d2c7aa11962792e3ceb0cd8fd43824d6076c9566cc9fcf53af9c5ca560d6c2f73032ad1bc2a6b7667a4d925604e8b0ae8b55152a14ca4945e04f764f32c499d127884d6aa62fcea45b9c8d4a4f92cef803c55f66794f15cd5408dd57b802f4b95076a42eaf8bc88cae56becbdbb80130d2a52271d56c12876c2468726b439ea6e3558513790133e1dabb8574bb9cb99e70062f8cb206a536d8531deef4622096152e2e11d781b076e340edead07f4d34716bb6a5e6f0f26d3db1ba392c423857d9a2415eb660aae7b35aea3690a916e06ca2f927c5746b850f48239eec274963a123579ff3c33cdd40c83f04c6bfb307827953be12769e909af08c922f5872b8091629d637234884ed042edcca7c763e92163bc5e1ef1dfa28bd61ff429eb805a50e5cd798e723a051b57f08f9d4cebb4c5b5d78c8941e7a506d48fbf39b62c0a9255ed8a21170fecc1749cbb3b3ce64becf159388dc98ef1ce0163e12083ebafe678083245e05fb0fe66eefde1a1af1cb5529efe6897d7982a0a52fe1a6eb7c2d0df9568427e81a5c2dc38229752c83511345b03aaf28e40f40d6437410404158149cc36906067c6228cf679e1edc64b37abab0026c2fbf628c75c140466df6dd059ed0f4ab80b87bd7b107d8d5a1d40238c594468b699a34606c6a45aab2b7e834df48b7b2956fd06ccbffaded621041a9d8f78061daba66f2f943910c69ff71169a5a2464e05895a10ed0c38973d188afbd1500000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x88fc3f", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf9b0bea08c4a70da033330b656816716dd0bb2907a20fbb247e02bd6f5a7506c", + "transactionPosition": 77 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002d082d", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x4ba7b2f", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002d082d194e99811a045b25435820e1d4ff2fffdc2d955643405d87b009d9b4e4416564a87718a503d569c42a9c13582079dec049eddef67a2d8acaafb048cce7ccea8cd69df4b68830bb33041f5914c25907808c80a1a7f1aef23e9c5a72f013f261279d9c83df068122083bfc73c928562b99d7e142e8323c3b1ba398efc3fddd17d686367ba915da2e8f05f5605d58f2957927a51de60a07ab0dbd24ac1e8a52b6af11b95df831d14dbac25d750fd4a45dca0837df29befcba63b8178ea8f02340b6d7d6bdb058bfc565ded5260ada6394f7c68c9a4d8494e84eeac2866efd78a5ab8995d7fa578bdc9df4b54ede4212535f7f890a681a729126bc2012fbb57b1cb3b59a93bec4617ee15ad138e9985b14b4955cd39414d99e873f5f4647c36d32362ee8d01c892a0117abed4d76e5f4f8fffbef81f01b535662b42ce3ccf5b415a2b8a671847288a295efc0f210cd27009e8084a633405e7f458cf404909035e07384d4f599bc4d7a2de45c9630b2b12c33072bff14f9e8dc7cbefc0f2573c2227e89711e63ae803944745a28949dc0ac962d4b8faccb5730b1a5e95e59c269dc1fb1a45fe8186d56382cfcc8c2cb70e93f01a16f059d08f5cde17a7fc62454ec77182142a08e69d2b2b4ff48333542077db708bd647e0ac8f4d1db47b183c91f6084957b28fb75778288f8b83975bc630c1ee44dcd9a9b95c3e440aac0bfe88683986dcca45b0e4c16286c3a2aeb9f7e1d43b914cd0b3f629ee426feff417c321ddb9b798070e040a269c9e22b1e1a81e20931d105241068cf3ae4792bda0f15593e4482dfeeae4e2f0386f08eb5cf7f1bbec416fa4378dfd75924a56d04610790aeb39aaaa8c2840f7c52fcb8f89a8b2d7cc4336ecac6d9e40223d45b31f2926dd0e45a1bfb6f0b6cfb5f4d0f0d8f9255aa0021ea31b75191ea0159be650d495e4dbe608db1ecfba2da7de60a321e5f7da691ad4db7db3b5c7885ad51cea72d38ad5c0eb37188c86cbe01c938896c0b6893c8698a5f7f1eb5cea5cdbcd39f718098086ea5be03d25c5d9ab5a30d96b78308b14a7ca1b098b8045c4d90a78206fc18fe6e1cf8c4cca2cda8dcd6b345211e4d8841a90978a7a21c58474c06aa5e568adaaa9fa880ac84956894d2e976d459bed01056a00e69bfccfe9d47f65fdef85720468801603ba01a63865c99acba06990df8d8ada941b6ebedc7484a9a574a18b16cea4683a8bfd6a90e1d845fa4ca568f3e4ea10818a8b551ee7defa34f7d95a71ed3bd40ceb688ec6c00cc207ce8305060b96a5acfb86c38a514bc3223a21e36a2212bf47bef2c2e6b3a93ad463501f46a336efed31b3d9e9998dbb10362e86135838c62a8fbb0489c18f72110dd2fc63428d2dd7e4351b2e075ffc89d9f99a1c2e049c1b5c7ddcebf1972cc4208122a1c981f44bd5c4810a2233922ceadebff26e745d4dba5cd698659399f6eb4b80e14b0f5b2d3604c971c4d6d5a2a3aad696a5660ceb110379fad11e21bcbcf82cc7c9de0ea2afbc0a90c4e22b1562cb1f436a758f2550d8ec0f06ea21e2a230e94e2b1eb5f3dba6341a01dcb08c9c9e19a68e1302b6ec8d9453214ec9340261329fc6389bbe2ee256e72c6ee1440ec2cd7d6e9a0ccaa1676826978e9763fdb9883efd5856573bffda76b38e20a84a48ef7aa8f17163962498dad8493ee48f8f0aca45098fc026df4b3dc20231bb647d894380c97bdedcd652bce638f65cb1b82c672e1aba05a9220b3540cbfed4bc74626309f44fd91d4d666349fd06ac0af5eaa45e37913a9ac44edd81d852c06f2a4fc567dc4c88164be2e004c7c4adc45223a4f6c0e34056c15393ef77c4a6f7f8ebd40774402335828324485c93f37cc01627a3234c1544fbda48a4d48be691d60055a3dbc38890cc0a5c9c89b4487f9865c0279f8898b77f66ee7c0b5f60b7f93cff14cffbd5465f61130bcee8107b17b6f761697f9008048dee7d0d3ae8519d43e3769b6c954fbbde729cd7c2fb86f97a78a947df5709f793c3d532e1dc4f81943b2f4e3502ffa30bd2d0da55c7661d6e779218c9b5708687671a0d2c7aa11962792e3ceb0cd8fd43824d6076c9566cc9fcf53af9c5ca560d6c2f73032ad1bc2a6b7667a4d925604e8b0ae8b55152a14ca4945e04f764f32c499d127884d6aa62fcea45b9c8d4a4f92cef803c55f66794f15cd5408dd57b802f4b95076a42eaf8bc88cae56becbdbb80130d2a52271d56c12876c2468726b439ea6e3558513790133e1dabb8574bb9cb99e70062f8cb206a536d8531deef4622096152e2e11d781b076e340edead07f4d34716bb6a5e6f0f26d3db1ba392c423857d9a2415eb660aae7b35aea3690a916e06ca2f927c5746b850f48239eec274963a123579ff3c33cdd40c83f04c6bfb307827953be12769e909af08c922f5872b8091629d637234884ed042edcca7c763e92163bc5e1ef1dfa28bd61ff429eb805a50e5cd798e723a051b57f08f9d4cebb4c5b5d78c8941e7a506d48fbf39b62c0a9255ed8a21170fecc1749cbb3b3ce64becf159388dc98ef1ce0163e12083ebafe678083245e05fb0fe66eefde1a1af1cb5529efe6897d7982a0a52fe1a6eb7c2d0df9568427e81a5c2dc38229752c83511345b03aaf28e40f40d6437410404158149cc36906067c6228cf679e1edc64b37abab0026c2fbf628c75c140466df6dd059ed0f4ab80b87bd7b107d8d5a1d40238c594468b699a34606c6a45aab2b7e834df48b7b2956fd06ccbffaded621041a9d8f78061daba66f2f943910c69ff71169a5a2464e05895a10ed0c38973d188afbd15d82a5829000182e20381e8022046a040e4fb486d77bd3aefb25a4eb3670e46e3f935fade483164764113c9092ad82a5828000181e2039220208e04de2905b7795a206f4ca5423c6a65dabfd8304b05b92aeb298af986ff1c01000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x309b9c7", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf9b0bea08c4a70da033330b656816716dd0bb2907a20fbb247e02bd6f5a7506c", + "transactionPosition": 77 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002d0815", + "to": "0xff000000000000000000000000000000002d082d", + "gas": "0x4d995a0", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782194e7659078099831f9576b59ca994613a166b25313bd47dfea452d5fca7152208025b41569c4b746255e1f9b84c29ae1a2fd7b51c41b8ef90add5e5507af6fc9477acf8a4e467d92a6f8b9192187d58a63cfebbbb58db31e89a5cb0cec05afa21126a9f1f4d12a51c2bf2630a8f35de9f5b5917dbbd53fb2a80b940dba66b544572dcc9442f0b504c48d4af9930d653ad9e5de6d407b58f2ea14e8c4d0daa6b1ba3a9ee6aa6336bfe6a6d62b9b76bd8ce9a3cb83b67c98695a0d83762582a3c0a0f9ee2e4f081b5f7573a0879bef50e2833c42862f1c18a880b9caa2a55f0eb415e8394fef0c5e681cb33cb06532f49658ffe675138b16d27817e745e0b0cc3928eaf99644f8eaebb6f1679a0a9fb3814a3342004b5885dc1a14a27b4fa64286dd0a076e3b60276a16e86e2736bd0a808734caede2fb2f09db852720f3e531d3226ef7c42dab93285600995614ce01dbdf40c0a2450ae04149d63878dee9f00c37980523cadd7570c2e9fe5ed4b2b33a16f510e5cdfe2add1e44df129097b71a25f918bc5ed80278fa2c13977ce824c8772f285c8ff2cae6502d59f46965a18169fae74f22801c0360ee99355f2d05e8cae2635e9148d397467af6b2288b27f2104fce3a1de18900fe71808c5d30b050f2a6842794dc547b49ca9c8d9eafd6dc5e40f9999ad0d06dfdf3a3148d6504816215035bf4ef67f767196837fd6332179e426838a1a6015d1ed9eecce775bb1cf805beeb22f96ccbed2588efcee9e0900ca3d1dcda9cd9fb1b74d1421f69e9f64116ca7f847824983039a2cf530ab96016bb625df50b880b01ea628f7bac496a4884a2be94a8bae8d344f0e4ebcb1910cf9d4547df86ba460645e4803f4a9f94b1ab3385552872be6f3a8dfb1993843ff36dd9f84f8bea6dafdf293a110d6617c14d2f9baed553c8a13fc1b711f1eeb45eb24a4ec0407f15431b667d49129837af9ed1dd815feb000650de073f80802572806947ae12feb9068dab35f2b3ba34a2fa9a4086187a81a66b249c8bee5b6ceb4bd8e453db444ef365eeea12e1e2ad3b656a2071a21512e972a99b7578898f82e458a6dcb91f2fe327f52b66d72167780f91bf3087924285f659f13900e441d56720ad2706460b970a36a67ec7d4ae41dbb0c63e490fc93ce81e6462b22842cc6fcdcda160f534531f6142ec8b6c4a46b506e1098e19994af6317be2aaa17f028adc4c43a18a2b77b16b1504c73782e5a9d0cdae8ad687921e1e5a1994c1cf0a46cd6cb78b318e72fdcd162adf463642e7bc48a6c92028af24770f3456e2dec29a3f3dc5b310183734a2c2f736a28807168b68e1985d39c39586eab9ded0df6d5643cd41eaee50789eda6cee7540552c9ec67094db48f7d7053605b266747545e72283d5640548aac9fce739dd4004d4ef869a72480a04609bd8f87054815b3cdfb18929784b1c12d8d6aa81bb417625924df826dcc025a1cce3c7203d906c6f25d7a863b1831bfb61737060a63707c5308f125fde71c50686e8813373aea7451e15e1579508a43b7bf9ac23546bfe1e5d80de563979d455615f690a07a44b2e7f4fa8460f1a1ce0a428495e3746cca143099ea00351ab4e001eeb3a8f2f8dc7ceaa06e9680424438aa2401867f354e33808fee11abc04240d19eeb91fd9551fb828a32cbd7e3e1b924247bd77f37335c957be1af84013c9ed4ad5beaee30bf3838c9bdaa4b4c9c6ef4b838360983426cde4454d1220f8677da23a117844bb82e168c51781222bc620ad9e8341141391b7f1e74ba73c751b47a8c2a44bbf90f2926a19fef298afdf0ed34c7f34e8c3f3339c653a7adb80caf0d61be88fb521e254a313d3118f0d59d9a77b5b29d321c08c739fd7256bc4cf42fa1b3c200bddfeea074843a99e8b14bc42ec667fc1a46356e3e18aea5bb5cbb981e867e2a9f26e4c00e4f922741e132900115257ece0265cdc485a9a6b1e2141fb5048e05e911a34b14eccb6c9cc5ad8af7be6cb6c5d9159832640d0773f5b3cb57cb4f85d9673db29632bc0c5bc7d26db2d396c8d74932fc3a7670440b76ea62b96e69dca52efccfebc9ae594b3a0e26143124d8a84ac427cd974889dd6b74405dd8d2367a7ab0e4aeec9b51b1e96f4819f48deb0afb32d9b0b9bbc4701c9d60da394dcea1d86c98edb8b6a065bc0c19840d2e6f454459f9aeedb422addd265807259f0dfceb1321cb3acd3b40584bf8b3340066580a27ce0c9b289959fce200924097e34d457096a4c5e3c3da0e644d665c071d75b3f7828361f7d801e48527af973586fbd0c07de1925a07388f606844c7c379a347b3439648a6752e95a4e89fd64823c8ac619459b93cf7716c1dcea6ddd460f4c42eb9994ff98a8b00cf9a74b13152e9fb1bfcf0b00d45869bdfd88355ec1d473a8752bf1a2a04640ab3918bc4f0bef6b9187b74c414b0d1ee19ff3a1aeee50b73300d3010bd2698ca444c629509acce503d59248b475602fe48abea5a4b8da9886e71d00f4ba62142c8790b630b357014be80b83aadd3784423b90d54cce1ac94297a694e0da2f284a0af093c1a73557ff126cfd29e035d59c283c826d17da2af6d967936735edd32c4e3dbaddc3b113a2a03c2b7ee953b194f05f2b17e54fa36fb3b30396fb858ef99398e82a3ea62113cac8f1a216017083de9d1c5524b777dfba1051c3bc2d510cfde0fb762cea5f226d8b6e64b00000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x89592a", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x11af98bb4b63e184700045f41d8370daa555dc189c7670a92b59c4fc7c47ff06", + "transactionPosition": 78 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002d082d", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x48112a4", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002d082d194e76811a045b225a58208e503e3d366299b9095156df2ccafbc49fbe238c3592a879c7cbc3fa095d26545820f7f9630e9e1ff4c01611a3dd41958c27ef95804e97da3fafe1e0c27da131a91459078099831f9576b59ca994613a166b25313bd47dfea452d5fca7152208025b41569c4b746255e1f9b84c29ae1a2fd7b51c41b8ef90add5e5507af6fc9477acf8a4e467d92a6f8b9192187d58a63cfebbbb58db31e89a5cb0cec05afa21126a9f1f4d12a51c2bf2630a8f35de9f5b5917dbbd53fb2a80b940dba66b544572dcc9442f0b504c48d4af9930d653ad9e5de6d407b58f2ea14e8c4d0daa6b1ba3a9ee6aa6336bfe6a6d62b9b76bd8ce9a3cb83b67c98695a0d83762582a3c0a0f9ee2e4f081b5f7573a0879bef50e2833c42862f1c18a880b9caa2a55f0eb415e8394fef0c5e681cb33cb06532f49658ffe675138b16d27817e745e0b0cc3928eaf99644f8eaebb6f1679a0a9fb3814a3342004b5885dc1a14a27b4fa64286dd0a076e3b60276a16e86e2736bd0a808734caede2fb2f09db852720f3e531d3226ef7c42dab93285600995614ce01dbdf40c0a2450ae04149d63878dee9f00c37980523cadd7570c2e9fe5ed4b2b33a16f510e5cdfe2add1e44df129097b71a25f918bc5ed80278fa2c13977ce824c8772f285c8ff2cae6502d59f46965a18169fae74f22801c0360ee99355f2d05e8cae2635e9148d397467af6b2288b27f2104fce3a1de18900fe71808c5d30b050f2a6842794dc547b49ca9c8d9eafd6dc5e40f9999ad0d06dfdf3a3148d6504816215035bf4ef67f767196837fd6332179e426838a1a6015d1ed9eecce775bb1cf805beeb22f96ccbed2588efcee9e0900ca3d1dcda9cd9fb1b74d1421f69e9f64116ca7f847824983039a2cf530ab96016bb625df50b880b01ea628f7bac496a4884a2be94a8bae8d344f0e4ebcb1910cf9d4547df86ba460645e4803f4a9f94b1ab3385552872be6f3a8dfb1993843ff36dd9f84f8bea6dafdf293a110d6617c14d2f9baed553c8a13fc1b711f1eeb45eb24a4ec0407f15431b667d49129837af9ed1dd815feb000650de073f80802572806947ae12feb9068dab35f2b3ba34a2fa9a4086187a81a66b249c8bee5b6ceb4bd8e453db444ef365eeea12e1e2ad3b656a2071a21512e972a99b7578898f82e458a6dcb91f2fe327f52b66d72167780f91bf3087924285f659f13900e441d56720ad2706460b970a36a67ec7d4ae41dbb0c63e490fc93ce81e6462b22842cc6fcdcda160f534531f6142ec8b6c4a46b506e1098e19994af6317be2aaa17f028adc4c43a18a2b77b16b1504c73782e5a9d0cdae8ad687921e1e5a1994c1cf0a46cd6cb78b318e72fdcd162adf463642e7bc48a6c92028af24770f3456e2dec29a3f3dc5b310183734a2c2f736a28807168b68e1985d39c39586eab9ded0df6d5643cd41eaee50789eda6cee7540552c9ec67094db48f7d7053605b266747545e72283d5640548aac9fce739dd4004d4ef869a72480a04609bd8f87054815b3cdfb18929784b1c12d8d6aa81bb417625924df826dcc025a1cce3c7203d906c6f25d7a863b1831bfb61737060a63707c5308f125fde71c50686e8813373aea7451e15e1579508a43b7bf9ac23546bfe1e5d80de563979d455615f690a07a44b2e7f4fa8460f1a1ce0a428495e3746cca143099ea00351ab4e001eeb3a8f2f8dc7ceaa06e9680424438aa2401867f354e33808fee11abc04240d19eeb91fd9551fb828a32cbd7e3e1b924247bd77f37335c957be1af84013c9ed4ad5beaee30bf3838c9bdaa4b4c9c6ef4b838360983426cde4454d1220f8677da23a117844bb82e168c51781222bc620ad9e8341141391b7f1e74ba73c751b47a8c2a44bbf90f2926a19fef298afdf0ed34c7f34e8c3f3339c653a7adb80caf0d61be88fb521e254a313d3118f0d59d9a77b5b29d321c08c739fd7256bc4cf42fa1b3c200bddfeea074843a99e8b14bc42ec667fc1a46356e3e18aea5bb5cbb981e867e2a9f26e4c00e4f922741e132900115257ece0265cdc485a9a6b1e2141fb5048e05e911a34b14eccb6c9cc5ad8af7be6cb6c5d9159832640d0773f5b3cb57cb4f85d9673db29632bc0c5bc7d26db2d396c8d74932fc3a7670440b76ea62b96e69dca52efccfebc9ae594b3a0e26143124d8a84ac427cd974889dd6b74405dd8d2367a7ab0e4aeec9b51b1e96f4819f48deb0afb32d9b0b9bbc4701c9d60da394dcea1d86c98edb8b6a065bc0c19840d2e6f454459f9aeedb422addd265807259f0dfceb1321cb3acd3b40584bf8b3340066580a27ce0c9b289959fce200924097e34d457096a4c5e3c3da0e644d665c071d75b3f7828361f7d801e48527af973586fbd0c07de1925a07388f606844c7c379a347b3439648a6752e95a4e89fd64823c8ac619459b93cf7716c1dcea6ddd460f4c42eb9994ff98a8b00cf9a74b13152e9fb1bfcf0b00d45869bdfd88355ec1d473a8752bf1a2a04640ab3918bc4f0bef6b9187b74c414b0d1ee19ff3a1aeee50b73300d3010bd2698ca444c629509acce503d59248b475602fe48abea5a4b8da9886e71d00f4ba62142c8790b630b357014be80b83aadd3784423b90d54cce1ac94297a694e0da2f284a0af093c1a73557ff126cfd29e035d59c283c826d17da2af6d967936735edd32c4e3dbaddc3b113a2a03c2b7ee953b194f05f2b17e54fa36fb3b30396fb858ef99398e82a3ea62113cac8f1a216017083de9d1c5524b777dfba1051c3bc2d510cfde0fb762cea5f226d8b6e64bd82a5829000182e20381e80220f35099649c579a0471c666d7319476b5bdc31aa0d651e214daae463001788769d82a5828000181e2039220200eaa7afc2292c2eb6a2db7208cd738b6b5b7fb69577b134cf1817c6f4d39dc0d000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x37f99a8", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x11af98bb4b63e184700045f41d8370daa555dc189c7670a92b59c4fc7c47ff06", + "transactionPosition": 78 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001ca66f", + "to": "0xff000000000000000000000000000000001ca698", + "gas": "0x336d63a", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000027a8518228382004082014082024081820d590240a08ff3b7b6fac42168d1bb9fe5ec46d2a1f0f7393abe61635e7cd7abb92358fc5b06b05b5598363c685a19a2eb0a789a974237d01d4e9abab7fd26c5f5b961c3c2e7e7a02f811aef4e9017183f01fbf2fcec48c11c66b04ec88c6205326d942c0481a6cd1f2a9ce933999b9f1ca17e7e250dace30151e38d334fd375c80942e881b4b75905866110ff9a8767765908bea19acda6d804c59114151663ef6aed4a26048afbc1b3541673ee9b223dec1adbc7e0317d90c570b2aa0ef83ca19fdc649143af73cce6bc1b5a2710b6da1abc090b08f26d9f14d4db79460f04803bfd718988bc44a81936da65d2631296f56489b8cce5f35c108aaf789d8f51cbccb80974d86b100876b421af58cc973a25363ad0e465c91a650677fd76e3d82d028dd910925f07bb1fe8e81bb8bbea9661f6bab48cd6a72c0fd8a70edb644cb4f1cb5c51be9168b28a2eba6e429f0fd69657fd88e05ef08bb018310481be280d48d435cf22bc452d61a846bae765092fd51625d5cea1a4d2b1fc2064b7b682393d35348c988668c6d985754deaa21b4ec3e7411268453a8f118295888def479a01e0c3a8fa274ce6c75cfe7ddd1f32d033a43080a25e8579a26b7dc65c1de7e88f22234842ce224f4f946717a7ada60282758ee63a6a08fb59f73ee5419aaeceeaac6511e0b58e0e14906e2e9343e52d9e6d05d4149ee4777bbfadb41f1d822dc68c5d5db3e1cc7d1cf8af15a7f2f874bbd867b13c74e229c51a1fe2e34d3e141592e5d16406695de5c9d4044770ccc776e49db6fdd3ac048ecf32d813e0e3fb883dc61a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d69000000000000" + }, + "result": { + "gasUsed": "0x2a5696d", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xab7a2853fd0c3c51361087f07a575acf5ac0930968bef1ac73bf360275a52281", + "transactionPosition": 79 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000014b4b4", + "to": "0xff0000000000000000000000000000000014b4ca", + "gas": "0x27c657d", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001b785182f8282004082014081820d590180a6333476a9dc6aaee94d32d85f613057168b6aca7e43fcd54ee38b03aae16a0917c867e0339d5f492cd987024f4141a0966b843654b2e9e7af330fe6d83fa05630c3aff5417eeb00e5e3aafce6d12ffdcd18b68cea29003838faee385a3626330a8e3c20ec69cdb8e3b5ce8594c9c9eaf68996771fc6b8399edd629b97856d7611475591ba61b86aafba95ebb482ec5498d88485f2cb9d6920fd45dd8a234091a31014aa836496dced595b0f5f89aa7abfa5c5eae63a11e93c0a3b241cf42901b931570b6fe9f665a4e62be9d3b1cfc5c3d915edc6b899b28f9e49ed67157375d74210aa54d116b9af6d1da31095d2eaa8a5d81d48da35aa43436e12c6d1a539c691aaf6a3575dcf80219a0214b5babfb9b8811e55ec3afd1b9fd73f1b82dff707ea2962acc711288fa110af7e7671ec90b1a7f0f40534c7fc9b538a09fd3e89a5e38de44b12b279f952775aa0075d8c9840f0bbb2c40c0a5c4b999329da775933d297d4c97101cb13ed3192edac7596df7ac4533cf1a38b07fa1e18b58b11eb1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d69000000000000000000" + }, + "result": { + "gasUsed": "0x20d2a3e", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x395ddc88f44eadde7eab48be036779baba3f53083167c7517a713f15c96bc804", + "transactionPosition": 80 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000112c55", + "to": "0xff00000000000000000000000000000000112c87", + "gas": "0x184c153", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f385181e8182004081820d58c0b6ea07c16c33360054815486dabadebe23a05ea409bd99d4fbf7ee738771daa5b76f68d46c3a59f42766ef147e7ed9a299dfe9d4990acbcd4aa5c21ba31e98b599d8f904c11c3a312877e1ed3e9d76581bf5b1b6c37872136a084bf5e7be564a150ed4a52079e13f4fa2a95946893e961ed7ff6753f8412494ba67faca84a2d912a51d9388bbdee6338a6b58bf7283ae85f28878222de7ceea33fa13d229b5cb1ba5a74186feabf2752d0329b868f9e92f5cc92bc1f120bd2d1f8eccabb8ef0d1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000000000" + }, + "result": { + "gasUsed": "0x143ecfa", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x865c98e58b41449ae2484a8b15d7a5fa601779e0a55194cc8ccf5276556c532c", + "transactionPosition": 81 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ae495", + "to": "0xff000000000000000000000000000000002b1e7f", + "gas": "0x526070a", + "value": "0xf2ac3304b3a9ee7", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000787821958b3590780b104649bcc2962b63068e99c9027750b8c0624579c9782df07b725bbf8fd863d32bfb7e038c5ee49a53a243b7c4b619f8f01f2528b63d3d30ee42353e63991460d3017d690d5d1d4061308bcc06d6df4b637605a12c71871455de0ec0ca9d4bc0618c739d4a1d2840b40e941da2f979eecb008f4d42936fd507dd2ae990e56a60d0eb73c942acc11a32316852505bc07ab49bfdfbdee30b32738060a00deeb7a72fcecaca2c2fb0c78d2572b23d95a996fbae088c4089a1c7d78a6879e40b1d8a08912c83a221c96cdcf3c27a041039e0c082b49aa120a8612ebbb8b212643e5f919598c6d783c944ae489e397c5dd3ea0afd730a6bd1c439380d86e05420a08145ffff0b1c76da1f920b39ff18cb04a3dc17c98b841c7bbff185d647e3b73f411f897ba7012863e6c3f58d734e3e105ff9e66cf6fa967763ff752823c056262bac4ca4ec4cdea379a6021f4674f16e4897b09752a737058c6539bb958782e5e2a9c87fa86b281e4fcc52acb75d568f6bcaff64d47e2cd235efdf5d1587ba860af4a62d1b04809f8fb934412ae24a9ac4dae0f8b9b6984b08e016c7053aa46fb312790aa0aaf31e7996fbae29c724027a0309818b8abc4baf5c5d96a582f55b79cccea28bcdf5ee03001dfff5cd945dfa476cd9008aea7eea2362719bb57342e15719a80d95056f0db56af34639b334309caddc833513c772ba05b7a365d6e91141112e6570296fce32640717602308ca37497d9efbdf284d2f4e7502ef3072a9e01f4a9a055457ce9db2b66c6e20aac758aaa1d085a1143bafe1ca3bb961379a02718f0fbbc7394f89b6ed096317866c50eb17fbb56ef9e0ef4ba22eed07a2cfa955669b7861ff662fe5698eb2d6e608a209c2286f0a6ab0cea4db0a567747498be3aac058521cb00e1ad7c6b6223f01f755c9ea146ec5c36562116172684cb16ce4479da96c83f6f509002ce583dfaa127db607a494aa3676db46be3e5faf22a990280626e7e39de54b6437302e194aa3ccbda92483130976b902cb621226639664deb1564336ae7f7d60d8c81ede66a154b38c147ce6c8ddfc93e8c20116085603c2d8cbeb6eed491e82d1838809bba6ff51bf56155c1e3e5eb4811bfeb2f68c17e4830a4fdaae17bdc1ff6869aee810941f3a7d3e4b01b47a118bf44f98d0acc3580390a05a4597f3a055498485d7850ef0a30cc412156e872a8ac1525f00c09113370c97ee408b6669998c6bd4513dc1ed023093e93354650c5c9be1c302a3e6f4ea612c6f24b2f1d31344d0f8ba7715ce0d0ca3ec816938fc2b7355c3424a2d2af92c6ed135bb8a0f2e692dfac2a25ca123dac083ad7f379d9316d320da4d4f6df987064e5918b5b55d0e39d97a1e972097f3cc790edf8479aa57d1dc911cb1c584aabef898354e0858b7c869ea0f3b4a827bc681be89ede0af01dcd2856595eaccb0d6ffd4aefaf3c1d406e39faba62ea6d46d24000097db353f506c4054e9309827a32246d6db154689bd3d164e9ad0ee9e662cb4f66e4f191d2adab221a004f49a035ab639d3a7a9044a148b12c4d0809dbe51231938026da42e017e475eca0d6f6f584dae896b73c5c1048ef04d42389728b8ad82479de84600161a731949cf60a2b18093ce01b865c31cb192dcfa58942f15ce2396893408b81b69b02d146412f8c5c57962aef640b774098da0b21442042325efa87ec757e45b40d0b01555296a39172f3f72098c6564a7a40b5526b63208f411e01fe949af04904c39b78775cffca74cd36af636971621ea74c83ad29f2cc55974c5ba7fdfc4e3b743f906e9d739a545f5974bf44e2f695326a2296fc4eba138971507b5c208e78fbaa94a1f0f6332ff6f242679dca0034472a9ac61f993a8f09dae64b860a44aa5e61c527122e4074a24fae53f58b713df614d5a308c91299f13f34ccefb9c4aab4f75baec8d0ac16d8e79115ebb45499a39d5ff53b40eb21496ebbb37c9a691748b80dd24b9d6f869300799059f284c39b97f1f75b773d78793de665ad8af00dddbad2208b6b3add604e1fa2d567f44be4661d4e74d72fb2018dc31a6542e856c9ca57804cbc9b5af7e14ffbc1fc59a65e845ecbdbb8cf1a8797224dfa0c76e8f995f899d541d50dd45cab0f05312ab8ea7a8522a1756d6d2e770d93500f7782670068603ab43ad972b4a55b42e5e0932790eba71343d1da3dfe2490959a3c11c2a1e859134047d18fbf75445d34cbaf139550fe0d9a72f80e13310255cc82b955156746e04272811b74a0b3ca3c2866b59b12dda698d562a619238965ed1200b8b2f41d3e06318067a0c248112a3aaf3aa30a396be7422986e35b41e8d375f76010785ff3eb09489b70e5a2c82984a10812bf9b1c7f4f877df41bab0d7b01e685670c56f3e545329a4a882fb0a7264343d54597346b15df13f25d7a0931a887a7be4465b717ee4552a0192534a10b2d19d086d577d1adb8154787e3af1050370e60599d3636e0d5081e357c5483e2a5eafb2f71c6db5b1213f4a07ffbe2a86eec3960d242fe2106ffbd9865882ae4dfc2d9542cda9a26a3320e72db1fe68c1235c468bffdb20a26791df145dac5a0d2c9527ea841961c45fda95d3d0267955dfef6bd960231c1a8dfd36583533836a1dd94d4b456a97ef41237fd6d8d5b898aa21eed123218ef4b3aedd3e645c9c0166c26e652699bf951f8f6f0af9ae11500000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x7dd123", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xd5c156a48ee21fc31fd16f0102ba7f09ae50f5f040c95fec880ebd46b96c2506", + "transactionPosition": 82 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b1e7f", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x4d9396e", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002b1e7f1958b3811a045b26ab5820f8ccb64bff08817a3cb554d2b97d7ad9fb9428ac6bde0bd9144cfdffec760af75820749b5789aaaaa013a8a90db28d47429b37b0c322abf2f2af8eb0261f3fd9f133590780b104649bcc2962b63068e99c9027750b8c0624579c9782df07b725bbf8fd863d32bfb7e038c5ee49a53a243b7c4b619f8f01f2528b63d3d30ee42353e63991460d3017d690d5d1d4061308bcc06d6df4b637605a12c71871455de0ec0ca9d4bc0618c739d4a1d2840b40e941da2f979eecb008f4d42936fd507dd2ae990e56a60d0eb73c942acc11a32316852505bc07ab49bfdfbdee30b32738060a00deeb7a72fcecaca2c2fb0c78d2572b23d95a996fbae088c4089a1c7d78a6879e40b1d8a08912c83a221c96cdcf3c27a041039e0c082b49aa120a8612ebbb8b212643e5f919598c6d783c944ae489e397c5dd3ea0afd730a6bd1c439380d86e05420a08145ffff0b1c76da1f920b39ff18cb04a3dc17c98b841c7bbff185d647e3b73f411f897ba7012863e6c3f58d734e3e105ff9e66cf6fa967763ff752823c056262bac4ca4ec4cdea379a6021f4674f16e4897b09752a737058c6539bb958782e5e2a9c87fa86b281e4fcc52acb75d568f6bcaff64d47e2cd235efdf5d1587ba860af4a62d1b04809f8fb934412ae24a9ac4dae0f8b9b6984b08e016c7053aa46fb312790aa0aaf31e7996fbae29c724027a0309818b8abc4baf5c5d96a582f55b79cccea28bcdf5ee03001dfff5cd945dfa476cd9008aea7eea2362719bb57342e15719a80d95056f0db56af34639b334309caddc833513c772ba05b7a365d6e91141112e6570296fce32640717602308ca37497d9efbdf284d2f4e7502ef3072a9e01f4a9a055457ce9db2b66c6e20aac758aaa1d085a1143bafe1ca3bb961379a02718f0fbbc7394f89b6ed096317866c50eb17fbb56ef9e0ef4ba22eed07a2cfa955669b7861ff662fe5698eb2d6e608a209c2286f0a6ab0cea4db0a567747498be3aac058521cb00e1ad7c6b6223f01f755c9ea146ec5c36562116172684cb16ce4479da96c83f6f509002ce583dfaa127db607a494aa3676db46be3e5faf22a990280626e7e39de54b6437302e194aa3ccbda92483130976b902cb621226639664deb1564336ae7f7d60d8c81ede66a154b38c147ce6c8ddfc93e8c20116085603c2d8cbeb6eed491e82d1838809bba6ff51bf56155c1e3e5eb4811bfeb2f68c17e4830a4fdaae17bdc1ff6869aee810941f3a7d3e4b01b47a118bf44f98d0acc3580390a05a4597f3a055498485d7850ef0a30cc412156e872a8ac1525f00c09113370c97ee408b6669998c6bd4513dc1ed023093e93354650c5c9be1c302a3e6f4ea612c6f24b2f1d31344d0f8ba7715ce0d0ca3ec816938fc2b7355c3424a2d2af92c6ed135bb8a0f2e692dfac2a25ca123dac083ad7f379d9316d320da4d4f6df987064e5918b5b55d0e39d97a1e972097f3cc790edf8479aa57d1dc911cb1c584aabef898354e0858b7c869ea0f3b4a827bc681be89ede0af01dcd2856595eaccb0d6ffd4aefaf3c1d406e39faba62ea6d46d24000097db353f506c4054e9309827a32246d6db154689bd3d164e9ad0ee9e662cb4f66e4f191d2adab221a004f49a035ab639d3a7a9044a148b12c4d0809dbe51231938026da42e017e475eca0d6f6f584dae896b73c5c1048ef04d42389728b8ad82479de84600161a731949cf60a2b18093ce01b865c31cb192dcfa58942f15ce2396893408b81b69b02d146412f8c5c57962aef640b774098da0b21442042325efa87ec757e45b40d0b01555296a39172f3f72098c6564a7a40b5526b63208f411e01fe949af04904c39b78775cffca74cd36af636971621ea74c83ad29f2cc55974c5ba7fdfc4e3b743f906e9d739a545f5974bf44e2f695326a2296fc4eba138971507b5c208e78fbaa94a1f0f6332ff6f242679dca0034472a9ac61f993a8f09dae64b860a44aa5e61c527122e4074a24fae53f58b713df614d5a308c91299f13f34ccefb9c4aab4f75baec8d0ac16d8e79115ebb45499a39d5ff53b40eb21496ebbb37c9a691748b80dd24b9d6f869300799059f284c39b97f1f75b773d78793de665ad8af00dddbad2208b6b3add604e1fa2d567f44be4661d4e74d72fb2018dc31a6542e856c9ca57804cbc9b5af7e14ffbc1fc59a65e845ecbdbb8cf1a8797224dfa0c76e8f995f899d541d50dd45cab0f05312ab8ea7a8522a1756d6d2e770d93500f7782670068603ab43ad972b4a55b42e5e0932790eba71343d1da3dfe2490959a3c11c2a1e859134047d18fbf75445d34cbaf139550fe0d9a72f80e13310255cc82b955156746e04272811b74a0b3ca3c2866b59b12dda698d562a619238965ed1200b8b2f41d3e06318067a0c248112a3aaf3aa30a396be7422986e35b41e8d375f76010785ff3eb09489b70e5a2c82984a10812bf9b1c7f4f877df41bab0d7b01e685670c56f3e545329a4a882fb0a7264343d54597346b15df13f25d7a0931a887a7be4465b717ee4552a0192534a10b2d19d086d577d1adb8154787e3af1050370e60599d3636e0d5081e357c5483e2a5eafb2f71c6db5b1213f4a07ffbe2a86eec3960d242fe2106ffbd9865882ae4dfc2d9542cda9a26a3320e72db1fe68c1235c468bffdb20a26791df145dac5a0d2c9527ea841961c45fda95d3d0267955dfef6bd960231c1a8dfd36583533836a1dd94d4b456a97ef41237fd6d8d5b898aa21eed123218ef4b3aedd3e645c9c0166c26e652699bf951f8f6f0af9ae115d82a5829000182e20381e80220f8558d739d8803bc6cebfb9d848e53a6dad4c37fab064e8c705621641b35074ed82a5828000181e20392202061158a40f8ea8f3f04148354f737e70549998404cdc66a5583c2f4e3cd0a0a1d000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x30cf1b6", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xd5c156a48ee21fc31fd16f0102ba7f09ae50f5f040c95fec880ebd46b96c2506", + "transactionPosition": 82 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ae495", + "to": "0xff000000000000000000000000000000002b1e7f", + "gas": "0x402499b", + "value": "0x105e0cd9863c06ef", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000787821958cd590780adcd547b9f1054f3eb179481e1437f981389c937872090f003ddb6e08d753861e6f05e90b9990d98c754dee7d6dddcf1941a48d0ee5bc3570fd52520a5ce3d9da7b40f68761b3cf3a7633dde498283d95055e3e32521d8a720d41321026044280910a2478ff76ce23192d695ab518438f967da0da1912608ad89ea0a50d2702b5032f026c62cce894480256c74c639bab56435da3aefad1ad28659d132ecbce88d3f80b0cc4a3048e94f13803b84404f17dc0a2d57b81ccb1255eadf2a55d5fa892eb861314ca95a1d516f63df0bc93c77529573929e10aa35c8e0ba9c225df897ff0d005be902a2d74c4a693893015f8f14311232d816d1fad5a4e005e685d426dd78c531e9563dd8785ac95e44970fac7f3b7d8d6603329604061b626cae7502fbac2ef0c460af26be45e113b2b7a43d935116511231a1c33ac5d769ef3b0c9c3fc626fbec2f8cfb87572e68c2d617897a9fe18d703b26f4253e931ea3e9c1a4b5077fc7656797b8df389b14bb5e2888a7dcdfdf89dcbc81c34d236d31f021a7a207dc806af6f3c89889efedd24f511562a2d01bea7b55afdea26e486bb729cab9b33a63c3360f8d9f267673eee7aab384b4d6e92d99955b0f1d58dce11f6e41dfe0ad020db66d34b9809c01d2f652a567b0ee46bc2ea4743a3f570d96acbc1525a2691d8bc7e5a1abd34f91189f9aacf36cb833f75be11cf12d47a985667f8b65c5444b1995495ea618484846e5038a8bff82cdb012ffb11bfb14934cc26e4e00331511d49ab2634d2d521a0989aca51ba718f0247dbdb88693f094bbb045a4d9f586340fc388fb9b4e0c65b52a38287cb8614bdad6271c3122e845ce8e74fef8bf6a5764d4384c1273fe5ce3037a951b46409d1518c256e26776e58f7f54f710ee3f190b4706de47e1c4dc0f427fc026a55c4b78deda2aebc381b98de5e200a765da1713999ca2a0d4de1ae76f12442e43e367055cb854ca45aed1115a0e9b20aa1fd188613f7c75beb1a2d26617890e786f2bdbab208b9dad19c1e6ec5dd69b6bc65657fbe561f9efae97eda5d4d855841f450d60fdceeddb45f371b5ed86ddf2cd367ad9a977eb4d9161ee549f5ab243513b70fe3f5708d4f13a47731db9898777079601f103ad01acb74075eda60036ae233fc572e41e546081f852496dad5e90ee06d722cd3835294cda3a31b61a3a167138d00aed08ebbe66a970e80334142cfdc870855c9b6d32e5942d526ed4762a13f9372572cb2555bb3aef5428906719050e1aa6a16ebecd5b35a38f940b3d4b12308a155e5bf035484bc1c2955ca84c23c25801065f7bd907b43e98116262a29c52d313f5355adf15b98f7e9038d780a2ef90049625360d7aa4ad4db577bfd9258d5627932993250fbc0b10b037a899612b74ac70c044044ca1e2c4a4cabe88260646811723ec43609158519e563e44a34ac6ca462c1cc2e6d79b48ed604e5fb890f41f0239ee947dcf6daa11da281778f48e78f9280f54d54c728c2ec8299e710c6c3acda600ec4b55e421c4d77376d8da43f1eb65c473c00f08c3ae15348d1049f75449826c0f9ee69361241491d8261d82daded638e80e042cea44cd702a529329aa603704231b24e933aa6d700662ec989717c7bd430cd3023a53221052ec6c3539c2e15518dc75f6118da892225026bc05c0802b106e6240d28505cd2a4ba725484ca570ed17c7a254e7a338aefd50ae4f45e3bf2d8ffd4695ac84bda612cd4fbc6dd857735594ac3e0457fd9c7e650c66fc77b562c31ef1ce72e50efc6c2ff3fabfd59c601e98a35fbc269d02a7c274f6d814b167217d3053a50fb5181bfe154ccef4a6f83cdffc84d6483072335302dc9210f0370fb49d01fa9675c9952c660f53b0def14bb37043a979df12da6ee45e3ee721b8364c64f9bf80d3b53c67f4888d1b22defc537c0d50cb0fa1c0074ed63333d8e1095b945f902f303a784d070ab17b68765f138598501387130ab052f0056f3881d376b6eef1b256af53d985ae9e8c2613d6859acb171bd1101c29a5b3319eb0b97459948ff358d596fb1a08388368bc9174756855b3b1a629706bf31d16e9dccb3e05b57581da42995a170f8b77103c24ab1e11f287c7dd1c089238da7c24411148130654996d194c5bcab051a092be26332a16b195248002370eb07a43411aad2128e64c0ca0bbda8bb6b25e87628d8bd55c28666bfb999afa7e0dda689c8fb0d7059f418b2af12a70d310c2bb564551159e52f34ccf38f40ec3d9661b57a91c6f8d84a11f86058b4ca466ec86ccd32ac4d69aa0057d65f04dc6f6da6daf63db98605f948b9b9f191eb9a2fb7b4c6c669f446f7fd47300257a50cdfe05030011def8e0d493254dedb9e343a6e0209b91cd3237e2c6d2ae7f36ecfd5aea18d3a6f65568875e0f05b678701f09de308cdc203112568204e2bfe4dbaac2b2b6fd636c96230c2748e8d111252ce1dd1f208c26528d7df70c37546bbb173a609e591782252adc8245049da8326c3d4c5bb9fd4956de0c1204a9c88987c1a3df6973fb1aa9efdb3d9d6ec7aa69e066487eb29edf789e510e3d454eee28598dee5a505644cbc372d039c5ee473eab761ae5ad64a40e389bd65433d15e42de02d7f1bfb64f5d392999f48c0eb5a1d16a0e31453fe75db5aa2992f395d24f0364653bf9c19dedf45ddc3fd0f13454973785990d18c1d6ae2000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x7bf31d", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xfd6e6033273714f103cb791d301d0cc728ec726a9ed10ac961ea47833131ab2b", + "transactionPosition": 83 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b1e7f", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x3b7567a", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002b1e7f1958cd811a045b259d582042b3e721f3f06ce0795d00e70534f16e2e39d65a22e99601ea49474aee1c6ff65820b9d0d8ad40b13f20a3f521674d65d23f718195e3009e84f1a2b06f61fe851092590780adcd547b9f1054f3eb179481e1437f981389c937872090f003ddb6e08d753861e6f05e90b9990d98c754dee7d6dddcf1941a48d0ee5bc3570fd52520a5ce3d9da7b40f68761b3cf3a7633dde498283d95055e3e32521d8a720d41321026044280910a2478ff76ce23192d695ab518438f967da0da1912608ad89ea0a50d2702b5032f026c62cce894480256c74c639bab56435da3aefad1ad28659d132ecbce88d3f80b0cc4a3048e94f13803b84404f17dc0a2d57b81ccb1255eadf2a55d5fa892eb861314ca95a1d516f63df0bc93c77529573929e10aa35c8e0ba9c225df897ff0d005be902a2d74c4a693893015f8f14311232d816d1fad5a4e005e685d426dd78c531e9563dd8785ac95e44970fac7f3b7d8d6603329604061b626cae7502fbac2ef0c460af26be45e113b2b7a43d935116511231a1c33ac5d769ef3b0c9c3fc626fbec2f8cfb87572e68c2d617897a9fe18d703b26f4253e931ea3e9c1a4b5077fc7656797b8df389b14bb5e2888a7dcdfdf89dcbc81c34d236d31f021a7a207dc806af6f3c89889efedd24f511562a2d01bea7b55afdea26e486bb729cab9b33a63c3360f8d9f267673eee7aab384b4d6e92d99955b0f1d58dce11f6e41dfe0ad020db66d34b9809c01d2f652a567b0ee46bc2ea4743a3f570d96acbc1525a2691d8bc7e5a1abd34f91189f9aacf36cb833f75be11cf12d47a985667f8b65c5444b1995495ea618484846e5038a8bff82cdb012ffb11bfb14934cc26e4e00331511d49ab2634d2d521a0989aca51ba718f0247dbdb88693f094bbb045a4d9f586340fc388fb9b4e0c65b52a38287cb8614bdad6271c3122e845ce8e74fef8bf6a5764d4384c1273fe5ce3037a951b46409d1518c256e26776e58f7f54f710ee3f190b4706de47e1c4dc0f427fc026a55c4b78deda2aebc381b98de5e200a765da1713999ca2a0d4de1ae76f12442e43e367055cb854ca45aed1115a0e9b20aa1fd188613f7c75beb1a2d26617890e786f2bdbab208b9dad19c1e6ec5dd69b6bc65657fbe561f9efae97eda5d4d855841f450d60fdceeddb45f371b5ed86ddf2cd367ad9a977eb4d9161ee549f5ab243513b70fe3f5708d4f13a47731db9898777079601f103ad01acb74075eda60036ae233fc572e41e546081f852496dad5e90ee06d722cd3835294cda3a31b61a3a167138d00aed08ebbe66a970e80334142cfdc870855c9b6d32e5942d526ed4762a13f9372572cb2555bb3aef5428906719050e1aa6a16ebecd5b35a38f940b3d4b12308a155e5bf035484bc1c2955ca84c23c25801065f7bd907b43e98116262a29c52d313f5355adf15b98f7e9038d780a2ef90049625360d7aa4ad4db577bfd9258d5627932993250fbc0b10b037a899612b74ac70c044044ca1e2c4a4cabe88260646811723ec43609158519e563e44a34ac6ca462c1cc2e6d79b48ed604e5fb890f41f0239ee947dcf6daa11da281778f48e78f9280f54d54c728c2ec8299e710c6c3acda600ec4b55e421c4d77376d8da43f1eb65c473c00f08c3ae15348d1049f75449826c0f9ee69361241491d8261d82daded638e80e042cea44cd702a529329aa603704231b24e933aa6d700662ec989717c7bd430cd3023a53221052ec6c3539c2e15518dc75f6118da892225026bc05c0802b106e6240d28505cd2a4ba725484ca570ed17c7a254e7a338aefd50ae4f45e3bf2d8ffd4695ac84bda612cd4fbc6dd857735594ac3e0457fd9c7e650c66fc77b562c31ef1ce72e50efc6c2ff3fabfd59c601e98a35fbc269d02a7c274f6d814b167217d3053a50fb5181bfe154ccef4a6f83cdffc84d6483072335302dc9210f0370fb49d01fa9675c9952c660f53b0def14bb37043a979df12da6ee45e3ee721b8364c64f9bf80d3b53c67f4888d1b22defc537c0d50cb0fa1c0074ed63333d8e1095b945f902f303a784d070ab17b68765f138598501387130ab052f0056f3881d376b6eef1b256af53d985ae9e8c2613d6859acb171bd1101c29a5b3319eb0b97459948ff358d596fb1a08388368bc9174756855b3b1a629706bf31d16e9dccb3e05b57581da42995a170f8b77103c24ab1e11f287c7dd1c089238da7c24411148130654996d194c5bcab051a092be26332a16b195248002370eb07a43411aad2128e64c0ca0bbda8bb6b25e87628d8bd55c28666bfb999afa7e0dda689c8fb0d7059f418b2af12a70d310c2bb564551159e52f34ccf38f40ec3d9661b57a91c6f8d84a11f86058b4ca466ec86ccd32ac4d69aa0057d65f04dc6f6da6daf63db98605f948b9b9f191eb9a2fb7b4c6c669f446f7fd47300257a50cdfe05030011def8e0d493254dedb9e343a6e0209b91cd3237e2c6d2ae7f36ecfd5aea18d3a6f65568875e0f05b678701f09de308cdc203112568204e2bfe4dbaac2b2b6fd636c96230c2748e8d111252ce1dd1f208c26528d7df70c37546bbb173a609e591782252adc8245049da8326c3d4c5bb9fd4956de0c1204a9c88987c1a3df6973fb1aa9efdb3d9d6ec7aa69e066487eb29edf789e510e3d454eee28598dee5a505644cbc372d039c5ee473eab761ae5ad64a40e389bd65433d15e42de02d7f1bfb64f5d392999f48c0eb5a1d16a0e31453fe75db5aa2992f395d24f0364653bf9c19dedf45ddc3fd0f13454973785990d18c1d6ae20d82a5829000182e20381e80220a73bc120d1ef9c13aff71a3ca6e094ceab44768e7463c06c5411d342fa01ea2fd82a5828000181e203922020fcf4f8a87325eae4550a7b7833830414cfa062bec0a61e0de1981315771db603000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x382d430", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xfd6e6033273714f103cb791d301d0cc728ec726a9ed10ac961ea47833131ab2b", + "transactionPosition": 83 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cc31b", + "to": "0xff000000000000000000000000000000002cc320", + "gas": "0x30439df", + "value": "0x8abb7a55e7bee0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708199e96d82a5829000182e20381e8022012bebfcae441d607d18402467f62c2ab7f7817897031757fc1e3804dc04442311a0037df6b811a045b14c01a004f9a76d82a5828000181e20392202055b250e600ac801d6356d82b0df8a453360fb0ec7084b1910e2759234e15902600000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x20d62cd", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf56db0f8b6c0adf7d4b97adb155081e3d4159fb405b85419eb6d94d7d2452c78", + "transactionPosition": 84 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cc320", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x2f17cd0", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf56db0f8b6c0adf7d4b97adb155081e3d4159fb405b85419eb6d94d7d2452c78", + "transactionPosition": 84 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cc320", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x2e0b788", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf56db0f8b6c0adf7d4b97adb155081e3d4159fb405b85419eb6d94d7d2452c78", + "transactionPosition": 84 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cc320", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x2cea217", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f9a76811a045b14c00000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x476739", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e20392202055b250e600ac801d6356d82b0df8a453360fb0ec7084b1910e2759234e159026000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf56db0f8b6c0adf7d4b97adb155081e3d4159fb405b85419eb6d94d7d2452c78", + "transactionPosition": 84 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000241c2e", + "to": "0xff00000000000000000000000000000000241c3f", + "gas": "0x48d6a79", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000072818187081a00010a76d82a5829000182e20381e80220c68a4c0a1f9bbacc1b43b94dd70a03f0360cf525da916df0d7021b78895cf1501a0037e0a1811a045b34251a004f93c3d82a5828000181e203922020ee6562b75f62cccc594244cb2f9eb26aca725a060f7eba8ee3c4b32e418c2b230000000000000000000000000000" + }, + "result": { + "gasUsed": "0x347e2a4", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xd8ee13b065e0b4ca8217629d5ad2cce9dd7000642534213e4531e1b3f8a1da29", + "transactionPosition": 85 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000241c3f", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x4820429", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xd8ee13b065e0b4ca8217629d5ad2cce9dd7000642534213e4531e1b3f8a1da29", + "transactionPosition": 85 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000241c3f", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x4713ee1", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xd8ee13b065e0b4ca8217629d5ad2cce9dd7000642534213e4531e1b3f8a1da29", + "transactionPosition": 85 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000241c3f", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x45f2970", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f93c3811a045b34250000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x477613", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020ee6562b75f62cccc594244cb2f9eb26aca725a060f7eba8ee3c4b32e418c2b23000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xd8ee13b065e0b4ca8217629d5ad2cce9dd7000642534213e4531e1b3f8a1da29", + "transactionPosition": 85 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce389", + "to": "0xff000000000000000000000000000000002ce3be", + "gas": "0x31bd06d", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708193f04d82a5829000182e20381e80220ee335a22ee6fe624ad6988dc7323bee4e562b9be560510700bf6d6a46914ca021a0037e0f1811a045361b11a00480bc5d82a5828000181e20392202088a1d5a6621a6fe38348e63ea64d091d8ab3060ee1769ad08c5e608db0ce4b3200000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x21e08e5", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x29a86bf568ea52bc778da4c2f1882f2b2459e8d5e638294f5b3c7341f39452a6", + "transactionPosition": 86 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3be", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x3106a46", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x29a86bf568ea52bc778da4c2f1882f2b2459e8d5e638294f5b3c7341f39452a6", + "transactionPosition": 86 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3be", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x2ffa4fe", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x29a86bf568ea52bc778da4c2f1882f2b2459e8d5e638294f5b3c7341f39452a6", + "transactionPosition": 86 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3be", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x2ed8f8d", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a00480bc5811a045361b10000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x4887a6", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e20392202088a1d5a6621a6fe38348e63ea64d091d8ab3060ee1769ad08c5e608db0ce4b32000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x29a86bf568ea52bc778da4c2f1882f2b2459e8d5e638294f5b3c7341f39452a6", + "transactionPosition": 86 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000000040f", + "to": "0xff00000000000000000000000000000000000961", + "gas": "0x2223d42", + "value": "0x8abb87dff2adb5", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000708181870819b878d82a5829000182e20381e802204b991204e7215867548c947fe66e43215213e86fc030522fb3f35f9cd9a6e2311a0037df12811a045b0ded1a0047eca7d82a5828000181e203922020864c24ea0ee60743452cd1444db396bb8e7d84b77867be2b737c1cc7b3fc7e0c00000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x1588de7", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x21e6ee3748a0d4cbe0125a25f22d5f2611d4c3df2f3aa4296736a8470b3ba6da", + "transactionPosition": 87 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000961", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x20f8033", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x21e6ee3748a0d4cbe0125a25f22d5f2611d4c3df2f3aa4296736a8470b3ba6da", + "transactionPosition": 87 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000961", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x1febaeb", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x21e6ee3748a0d4cbe0125a25f22d5f2611d4c3df2f3aa4296736a8470b3ba6da", + "transactionPosition": 87 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000961", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x1eca57a", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a0047eca7811a045b0ded0000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x477494", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020864c24ea0ee60743452cd1444db396bb8e7d84b77867be2b737c1cc7b3fc7e0c000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x21e6ee3748a0d4cbe0125a25f22d5f2611d4c3df2f3aa4296736a8470b3ba6da", + "transactionPosition": 87 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002c43aa", + "to": "0xff000000000000000000000000000000002c43b6", + "gas": "0x413ba3c", + "value": "0x1a86cf1d84124d89", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000787821925b959078097b3ea67b0001eb0ba6353d1eab30d9a7d5f9d64b1fc95a1ba6bbe5f11044aba6e3b40ec1fbaa7166ffc8c64971a27daab26e31ce688f7e1f24885ee59ecc8677d59583424689338d48829a9e4d58d2e906dd592bd26d202a5759808a1acc7a6063ece776d981d3d8444a369728d65d875a3666e804785f65460925fcae2cad1defebb202b02eda7b981f6430479db06ac90441ffaefd375988c308c894388c83db61e80aa53106012c61a3ba70d4eab30435d7186a9f6c536c556a1c994e11cb4fd170312132b5c59b29c3f6b4b1188dc51c92352419f4504d3122eabd074144ecfcaff2dfa6c27781368afb9f0a49f872f659845c00949f638c96e473010286922edbe98e8db6132594fe9f487f7fb66bdd7f9a27b7813c1f29be7cd833d9711d2d04bac97f5ef5598f73110020987ae2da8f2db9befd50a033cd74c2d878d12f9b69b5d7e3192a76362ddad3c8f00b2da963f9c44818ebde797a3343b196751522f4de82fded7fc43d2ceb05b143c103821d56694a5f7f01d3678affbd8c58b110c139d7c49442a2e591d8edd45e9b9d62975ff4fa4c2f75fd4073dc6647ead728e690971b348c1dae698a402a0d697ec1aad2b423077d9cc55b913d016b217d90d21f310e4342b5fe901f492aa7d093a2e4567bdf41a4d9e86cd7242467c1003908559f717a2ab1e4e2baf7311364544229550a8ba213596202efd70cac595c51408723a68bb0aa5984e32dcf3bba7328dbc7ad8dc0b8650cc9995bb99eef736fb4a5f8929e66b787ec028fec84d7487e3125220ec2db0f96b85314be66b88cdf061dc2b5a1442ff6341a038fb1118f81fb2718031634313e65516b8fe58643d215778c7c6afae8d5d5c569d3726b757cc558705e99d76eab551b2fe464c4a79e432ad4eefd478646f6f61fe1613ecf973d42466c1b3dec9d6e8cbc1681808ae1bb85bdacc9f8cb060ed9fba00fba8694b1dbd950ab5e4a597058faa19e77990be62cbba63f476a752bfe48aaac4a5ff8bc85b1b0c1f0fe9b1caa247196c3394c8f0f8f13708b77213155d5614f5bf7af0b9c9f574717b5a5d72f4f7367aa0593563e9be8c5f2d4c3f2096d1f3c9d82bfec6b8ce2edf5aeaab4fba6d9b72e95c9638ae856af76fe598b088f6ddabaf8c1aa1af5137cbf681819f4e4db91ae336431e7c1c5363673ea9b332393003e031ae9bf4b20dd5a4a7859365ccedb10c2e2e226e1684f249afca9268c23352fb36e9ebd18cf254b9e6c70268275c5b83ff8b30c7abea185f4969bb3673330d959675f76b6bd839620c8dec153d4a7d79d012d994211acefeebf8b15f0f07fd7a4420e5328f7ec096d3292574d492698fb159987beb422ec5c620a211bdff153e57f567ac37843ca7dc3844961836f78ef5f590d9d1d8c29c9797060b2386dcb0b3d02b7ffe3b2e25cb45f4ba0de07b71a9864e7e0a2dd895bf642f49954ca2a7f95007a2ffb7dfa570c6e124fe007e06d4b66aa8ce81f550ec3c1cc4ce9186425b348d191232c76cb4018703b42495f28e113c80411566c85d61523f71aa08976c181005d30b306168d2c5c88ebdb951603f190dfd3fe48ede2858e02a77b90fcdbdcc34fa5098c9f3cf6142be856da830f43d8ab50b990c29e4a957241946974fb63f8da961b82b11ad4034e90528548659e5aaf3156dbbfbfa8d83c3e07981758442aa3be462c5983908a882ed8fb298e3c6bce51db1fa61c0faed67f7014d9f7fab463b088ec32aad30c680f463009f785e2f74003a90bcc05c4dedc376a11daca09891c2cee6242d8ace7743525c6b8988b41612fdbeea48098605b597b4cf3b736205688e3c1cfb2c5d8e9a70f805014159b9bc02a6baf2cf5daeec39bd1057343572c776f66be721e139ac74a30f6630e165d498640b4a3074689e7ee7bb04a8d0611b9323b4141195d94b26d4147ae734f3002c644c671f8c971e539323ce50fc44b6e045faafd3dac798d67cbb4268707aa9ea783eedf2300989fce3e2a519775bdfb01a25ba57ec7d608700fed67cc95b4cad48e9301edbb790e79de8acdac2a6cf2d3201251e6931b2ac655dcab2a41a761082a3b3d565b27455a4a278f70b58ae17cba43cbb7865fa9915a5129d9e20f7ffbb8a46e7861c85da4d780a5a0af6b7396c7a64ba30023ad9871671c13a682b600f9c64894524ce34dc8ce1509e8cc5ad34e899833aa8f67933d2f6521e9c425f77e8a978bc052aba8c8d23c30dfb1dc40ada63d5743705b8a6dc053e918d8b5e7f6840b2d9810900ed076c6cdcf7e143d8a6a759a9dde1f10b714fa57d2e87350a9d3c57f973a9db4576f68703b901938504a87267da534c2b6f6bb517c229002df0a23cc7dbebf1b81e8412c13786ce60cff177b940a76e5b499de0d8cf8457ae43ae9908f3ae0cc22b9778ec04404efbf93afce4c8f015894d3e4ae82d11f61d41ad02e0be7cf7c8639b8d280dc152fd75e9d0efcbdb9b90502d757f7a2d386f8bd001a95753bf81308b1dc88da58b1f221dd4e8e5217345b7cb0b6fe940874920af33481091a1e3ed6024afab00eb8cd3e15e142d9b5e1446a4554923ce97cb385867786e95304fd8c62dc0de08ba942d7d7493120b4b4a54b379594dc0558381305d6fba675582f6d7761513470ffeb5de56e9448922b452ec437704f08699c4831684239f07162b6debfe74f2dd61ac707cb98aec2a00000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x62aed2", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf9b127b6341a6e269f846e39956e38fe62f80c4ba8af19158913490d0d87aca3", + "transactionPosition": 88 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002c43b6", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x3e21973", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002c43b61925b9811a045b1f095820834b0ec1478d47321f74ea8410e823e1b5849bad4ad3f9e99398927317394e795820a6370784af85f07361123ee661399b72bef32bebedf8edc99da47959d5e2c81b59078097b3ea67b0001eb0ba6353d1eab30d9a7d5f9d64b1fc95a1ba6bbe5f11044aba6e3b40ec1fbaa7166ffc8c64971a27daab26e31ce688f7e1f24885ee59ecc8677d59583424689338d48829a9e4d58d2e906dd592bd26d202a5759808a1acc7a6063ece776d981d3d8444a369728d65d875a3666e804785f65460925fcae2cad1defebb202b02eda7b981f6430479db06ac90441ffaefd375988c308c894388c83db61e80aa53106012c61a3ba70d4eab30435d7186a9f6c536c556a1c994e11cb4fd170312132b5c59b29c3f6b4b1188dc51c92352419f4504d3122eabd074144ecfcaff2dfa6c27781368afb9f0a49f872f659845c00949f638c96e473010286922edbe98e8db6132594fe9f487f7fb66bdd7f9a27b7813c1f29be7cd833d9711d2d04bac97f5ef5598f73110020987ae2da8f2db9befd50a033cd74c2d878d12f9b69b5d7e3192a76362ddad3c8f00b2da963f9c44818ebde797a3343b196751522f4de82fded7fc43d2ceb05b143c103821d56694a5f7f01d3678affbd8c58b110c139d7c49442a2e591d8edd45e9b9d62975ff4fa4c2f75fd4073dc6647ead728e690971b348c1dae698a402a0d697ec1aad2b423077d9cc55b913d016b217d90d21f310e4342b5fe901f492aa7d093a2e4567bdf41a4d9e86cd7242467c1003908559f717a2ab1e4e2baf7311364544229550a8ba213596202efd70cac595c51408723a68bb0aa5984e32dcf3bba7328dbc7ad8dc0b8650cc9995bb99eef736fb4a5f8929e66b787ec028fec84d7487e3125220ec2db0f96b85314be66b88cdf061dc2b5a1442ff6341a038fb1118f81fb2718031634313e65516b8fe58643d215778c7c6afae8d5d5c569d3726b757cc558705e99d76eab551b2fe464c4a79e432ad4eefd478646f6f61fe1613ecf973d42466c1b3dec9d6e8cbc1681808ae1bb85bdacc9f8cb060ed9fba00fba8694b1dbd950ab5e4a597058faa19e77990be62cbba63f476a752bfe48aaac4a5ff8bc85b1b0c1f0fe9b1caa247196c3394c8f0f8f13708b77213155d5614f5bf7af0b9c9f574717b5a5d72f4f7367aa0593563e9be8c5f2d4c3f2096d1f3c9d82bfec6b8ce2edf5aeaab4fba6d9b72e95c9638ae856af76fe598b088f6ddabaf8c1aa1af5137cbf681819f4e4db91ae336431e7c1c5363673ea9b332393003e031ae9bf4b20dd5a4a7859365ccedb10c2e2e226e1684f249afca9268c23352fb36e9ebd18cf254b9e6c70268275c5b83ff8b30c7abea185f4969bb3673330d959675f76b6bd839620c8dec153d4a7d79d012d994211acefeebf8b15f0f07fd7a4420e5328f7ec096d3292574d492698fb159987beb422ec5c620a211bdff153e57f567ac37843ca7dc3844961836f78ef5f590d9d1d8c29c9797060b2386dcb0b3d02b7ffe3b2e25cb45f4ba0de07b71a9864e7e0a2dd895bf642f49954ca2a7f95007a2ffb7dfa570c6e124fe007e06d4b66aa8ce81f550ec3c1cc4ce9186425b348d191232c76cb4018703b42495f28e113c80411566c85d61523f71aa08976c181005d30b306168d2c5c88ebdb951603f190dfd3fe48ede2858e02a77b90fcdbdcc34fa5098c9f3cf6142be856da830f43d8ab50b990c29e4a957241946974fb63f8da961b82b11ad4034e90528548659e5aaf3156dbbfbfa8d83c3e07981758442aa3be462c5983908a882ed8fb298e3c6bce51db1fa61c0faed67f7014d9f7fab463b088ec32aad30c680f463009f785e2f74003a90bcc05c4dedc376a11daca09891c2cee6242d8ace7743525c6b8988b41612fdbeea48098605b597b4cf3b736205688e3c1cfb2c5d8e9a70f805014159b9bc02a6baf2cf5daeec39bd1057343572c776f66be721e139ac74a30f6630e165d498640b4a3074689e7ee7bb04a8d0611b9323b4141195d94b26d4147ae734f3002c644c671f8c971e539323ce50fc44b6e045faafd3dac798d67cbb4268707aa9ea783eedf2300989fce3e2a519775bdfb01a25ba57ec7d608700fed67cc95b4cad48e9301edbb790e79de8acdac2a6cf2d3201251e6931b2ac655dcab2a41a761082a3b3d565b27455a4a278f70b58ae17cba43cbb7865fa9915a5129d9e20f7ffbb8a46e7861c85da4d780a5a0af6b7396c7a64ba30023ad9871671c13a682b600f9c64894524ce34dc8ce1509e8cc5ad34e899833aa8f67933d2f6521e9c425f77e8a978bc052aba8c8d23c30dfb1dc40ada63d5743705b8a6dc053e918d8b5e7f6840b2d9810900ed076c6cdcf7e143d8a6a759a9dde1f10b714fa57d2e87350a9d3c57f973a9db4576f68703b901938504a87267da534c2b6f6bb517c229002df0a23cc7dbebf1b81e8412c13786ce60cff177b940a76e5b499de0d8cf8457ae43ae9908f3ae0cc22b9778ec04404efbf93afce4c8f015894d3e4ae82d11f61d41ad02e0be7cf7c8639b8d280dc152fd75e9d0efcbdb9b90502d757f7a2d386f8bd001a95753bf81308b1dc88da58b1f221dd4e8e5217345b7cb0b6fe940874920af33481091a1e3ed6024afab00eb8cd3e15e142d9b5e1446a4554923ce97cb385867786e95304fd8c62dc0de08ba942d7d7493120b4b4a54b379594dc0558381305d6fba675582f6d7761513470ffeb5de56e9448922b452ec437704f08699c4831684239f07162b6debfe74f2dd61ac707cb98aec2ad82a5829000182e20381e802207b7490375005b4ab7a45bfe5fbad53683f38a72527393440345e75a6a7ee7d63d82a5828000181e203922020ef12b88fae9a11c8ef6e7e43fdc9bb2059e9a0fc0e291d68a3c49c9c86b3421f000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x30ed5a3", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf9b127b6341a6e269f846e39956e38fe62f80c4ba8af19158913490d0d87aca3", + "transactionPosition": 88 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b4022", + "to": "0xff000000000000000000000000000000002b404f", + "gas": "0x43a0269", + "value": "0x1a3ede932ea26d50", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000007878219e3e0590780ace586801c6af561f2983ca1ad7268d41a4eb125eb08064de6fd3f4d9b98b7ead52c60ac58919e54c354f2dbfc689aa3b9cf5dfad2c937f4ac2eb1bb4c2c370a795f86b6ebe359c6f8f830294dcde42d14ee689d057aa9db003483040ac387d810cf29f32e6a5d52cc11e158b8cf21e0094a4697627d342027f95edcb02865213b28f44787c89c2ac5571e0728de3eadb465cfc2072da9f60e124cfb93b7d246b609bb3ec8ef3388efd231bc257757745e9c2aa3faf541294f3043aedf93bb889127de8e1458fb10461df73403003017c924cf4300df4468afa4fddd67d0d0733b8c8c31db8897163041e77509498f218757d2773e3dae955a7aeeefd8a83443182de39ecbfa995fc03fe7fdc55c719c8057f9a4c8576a584d9fe4f6b1541d060d8350ef3203cc2367660197004902c7f3d2811adea9a7a498e73ce55e48ab54b41c29312c21576e904c45c2d9360331b2faddbed720ea9a1b9791669cc9d7f00bd1d5ba70deef07dfbdd438078a7bd91e137b4ef11f95f73ff0066abd34ccc6995aef7549db39eaaf2903c5b3f12e6f2a22c33e71515ecd3797f9f675660677ed657863235563b44c6b3ee9c27b584b90fc1752ee2418c4c0982fb820956825f874f4f23e958243064022f46523520570cc99803877aa7a987fd19bbb6d4e150a68864b7d49edd4c8ddefde9093e9b3f7b7e6a3ed19e7babddb4128f56de6cf96bd1e8904e2c915f3ac3beabcb7ec4995772c6eaccea909cffe8f7833dd6673427426ebd071743a95311d02867b2e1b82454dcd143978e42f968ccd112606dca3c83fa8365e5bcb170d4b7483fb0006ef832120a065c4fec044678551b78afe8a9a0c75af5d5c2b1c3aeaa02c47c63ca538d1e45ffacf6ea891295b264f2802e0b8031fc297d94f177edb50b207c20ef742fad5b9788cee3dabb6a6840f2bd91714e5cab90b173ff8c576544abdb8108f995c0dad05db88e13fc829b80341e2921d74b61fb063bf7e15df5e06f00d03a9f0c98979243ef53c5a1e78b1c592d901310b8efbe96b72ddb6c2e73e353cb3b769bcf83cc603bfd4a76643927b256c977ac06e3f679e38b455305df070ac3bb320390d3bc1fe6a6a0f5ad0a2c9a1fb8a1229d462b6289d5bfbbe613acf29d588b1888a9a3fa7ba9075d67dec51a97dc0e6a94ec455f15fe9173219a21a60ccae610697f44776dc63c07c8b0bc76fe20e17db74014ab5b3aed158c7f6db1cc0d339d6d72b5c2143574af4931306f800d754790d2703d5a7b15344de0f78a2e4aff46351654e4086b8807a6c93a74d5873a3c3d82a4572a0a9c8ba6f39f13effc3ae5451422e27b9ce09a0a5781f22d5b173352c76c969fa192c7ad6dcb8f8159e710fe8eacad3966df41f61375c5e74f3b5d8d8915faa5725a5301a4aca928cb9235aa184a7a853df2a384664e9dac20eb12d0b08c014c21e9b8611d516b5ec78093d8b9d10b8b27438958e2b89f4701726a3d9e3ec3d9a74418c0bb63dbde0e163fa2dc77560a4deaf90b691bb4fe9b11bfa00206245df864d52c75a73926395b7fbcf65f5ec4b87c8911d4d6ceb45b2a8313ed8e9136ab6ae614032e42cc3b097925cf6d8a6aef13a01a0ee363627ac36c8e0b82be6f304baf2598631bb0f24bd12a27147aa8b4476b9c29ba54f27339b9c3061d7729b78f26de21a701b8190c4d8adefaf926b8aa9212a849f34c50a8706764adb4ee8e3888f69e28b47ba3a320128254d7f7d994e0b9084e01fa3162ce7eed9109edb81dd36c8e96c226adb2c6660f2142c240213f721d134cb27afdcd1675fd19d517eaff5a3fc7693238f50fa55a2666bfdda36f1f8109e85471505f3c73b64bad8bdb801ee8ca53130d253c31573129359854ab14ae3eabe228ebea10bc1664ca4cf47b1888432b5766b9cb5467119ea7ad7ac2b20f0692b5c846dd9658babd9eb787b52d38d7feda3b3500f7850d8f1b6a94de3e79e5f6068ab487fc487790041a030747e086a375b473e33e477efe6d6b9a823f8ed7d541b038b41f6b43e9d6fd91a3ece1537843ea8a1a7266411f4b39c5573575b16a4293194f10a007f0335d984b4b06ff1b68181fcfa1b8f65d97358b248dd2c040de6fb99c3d1501620e7be07c32c30be700820f91312d6519824af63b2b171319b17813ab66487cccf75177cdb023122cf5c21040843a2c917cae89f34d870b4dfa2b867c49c96a06f073f20468226aa3e7b89454f79fca095ec962a28c5c40efd492d68ecdfc4da742368fd6956288b4d5144e7a826a805bbffdf1c46bb9b37069917e2fa997e375d5ccd9a6f646ba803eebf0bf1f1ffcf9d366e1862c5dc816d6db5aadbee419841a8cdde740f73afe869a91a6899c96f2993a1c882b55b37a1032639d1968a9e4046b7af3007d0293af5e4b74389847d6a7faa602f6f97610a14b2316c9045bfb9698c95a6da1bb682f44a269eea77e633cabb12b1d5993a97032b85a929584407182a0349ec834fbc6c87e5198e59ac0eb6e9bc83ad4ceca1bc7bca8cb34349e342e22ba2aea22125d46ebb473c47f6269b73d597fa9f82bbc70ea5198b7f48e016392d189125a15888054763581c87424a486b22e775c70ae9913d57df4428af047814233ef6998f4ea5179318af9872bde490053bd8f778257d599d0f481444f1c922010273acf2026fddc17dd6e5b24184515f6f7f10b5b300000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x80a80b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc30e8b748b22427010e409aece99f62953c57de4f68383af4fd363beb0801e8d", + "transactionPosition": 89 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002b404f", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x3ea6944", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002b404f19e3e0811a045b29de5820d92f7e1060a1f96fa21101609789a7b51e4dad941c5ed5345bccc93f19e12cc45820e886746cf4ddfc8999519b72e3a9d45b7d53ebdc4865e810d9d7b78a186c479e590780ace586801c6af561f2983ca1ad7268d41a4eb125eb08064de6fd3f4d9b98b7ead52c60ac58919e54c354f2dbfc689aa3b9cf5dfad2c937f4ac2eb1bb4c2c370a795f86b6ebe359c6f8f830294dcde42d14ee689d057aa9db003483040ac387d810cf29f32e6a5d52cc11e158b8cf21e0094a4697627d342027f95edcb02865213b28f44787c89c2ac5571e0728de3eadb465cfc2072da9f60e124cfb93b7d246b609bb3ec8ef3388efd231bc257757745e9c2aa3faf541294f3043aedf93bb889127de8e1458fb10461df73403003017c924cf4300df4468afa4fddd67d0d0733b8c8c31db8897163041e77509498f218757d2773e3dae955a7aeeefd8a83443182de39ecbfa995fc03fe7fdc55c719c8057f9a4c8576a584d9fe4f6b1541d060d8350ef3203cc2367660197004902c7f3d2811adea9a7a498e73ce55e48ab54b41c29312c21576e904c45c2d9360331b2faddbed720ea9a1b9791669cc9d7f00bd1d5ba70deef07dfbdd438078a7bd91e137b4ef11f95f73ff0066abd34ccc6995aef7549db39eaaf2903c5b3f12e6f2a22c33e71515ecd3797f9f675660677ed657863235563b44c6b3ee9c27b584b90fc1752ee2418c4c0982fb820956825f874f4f23e958243064022f46523520570cc99803877aa7a987fd19bbb6d4e150a68864b7d49edd4c8ddefde9093e9b3f7b7e6a3ed19e7babddb4128f56de6cf96bd1e8904e2c915f3ac3beabcb7ec4995772c6eaccea909cffe8f7833dd6673427426ebd071743a95311d02867b2e1b82454dcd143978e42f968ccd112606dca3c83fa8365e5bcb170d4b7483fb0006ef832120a065c4fec044678551b78afe8a9a0c75af5d5c2b1c3aeaa02c47c63ca538d1e45ffacf6ea891295b264f2802e0b8031fc297d94f177edb50b207c20ef742fad5b9788cee3dabb6a6840f2bd91714e5cab90b173ff8c576544abdb8108f995c0dad05db88e13fc829b80341e2921d74b61fb063bf7e15df5e06f00d03a9f0c98979243ef53c5a1e78b1c592d901310b8efbe96b72ddb6c2e73e353cb3b769bcf83cc603bfd4a76643927b256c977ac06e3f679e38b455305df070ac3bb320390d3bc1fe6a6a0f5ad0a2c9a1fb8a1229d462b6289d5bfbbe613acf29d588b1888a9a3fa7ba9075d67dec51a97dc0e6a94ec455f15fe9173219a21a60ccae610697f44776dc63c07c8b0bc76fe20e17db74014ab5b3aed158c7f6db1cc0d339d6d72b5c2143574af4931306f800d754790d2703d5a7b15344de0f78a2e4aff46351654e4086b8807a6c93a74d5873a3c3d82a4572a0a9c8ba6f39f13effc3ae5451422e27b9ce09a0a5781f22d5b173352c76c969fa192c7ad6dcb8f8159e710fe8eacad3966df41f61375c5e74f3b5d8d8915faa5725a5301a4aca928cb9235aa184a7a853df2a384664e9dac20eb12d0b08c014c21e9b8611d516b5ec78093d8b9d10b8b27438958e2b89f4701726a3d9e3ec3d9a74418c0bb63dbde0e163fa2dc77560a4deaf90b691bb4fe9b11bfa00206245df864d52c75a73926395b7fbcf65f5ec4b87c8911d4d6ceb45b2a8313ed8e9136ab6ae614032e42cc3b097925cf6d8a6aef13a01a0ee363627ac36c8e0b82be6f304baf2598631bb0f24bd12a27147aa8b4476b9c29ba54f27339b9c3061d7729b78f26de21a701b8190c4d8adefaf926b8aa9212a849f34c50a8706764adb4ee8e3888f69e28b47ba3a320128254d7f7d994e0b9084e01fa3162ce7eed9109edb81dd36c8e96c226adb2c6660f2142c240213f721d134cb27afdcd1675fd19d517eaff5a3fc7693238f50fa55a2666bfdda36f1f8109e85471505f3c73b64bad8bdb801ee8ca53130d253c31573129359854ab14ae3eabe228ebea10bc1664ca4cf47b1888432b5766b9cb5467119ea7ad7ac2b20f0692b5c846dd9658babd9eb787b52d38d7feda3b3500f7850d8f1b6a94de3e79e5f6068ab487fc487790041a030747e086a375b473e33e477efe6d6b9a823f8ed7d541b038b41f6b43e9d6fd91a3ece1537843ea8a1a7266411f4b39c5573575b16a4293194f10a007f0335d984b4b06ff1b68181fcfa1b8f65d97358b248dd2c040de6fb99c3d1501620e7be07c32c30be700820f91312d6519824af63b2b171319b17813ab66487cccf75177cdb023122cf5c21040843a2c917cae89f34d870b4dfa2b867c49c96a06f073f20468226aa3e7b89454f79fca095ec962a28c5c40efd492d68ecdfc4da742368fd6956288b4d5144e7a826a805bbffdf1c46bb9b37069917e2fa997e375d5ccd9a6f646ba803eebf0bf1f1ffcf9d366e1862c5dc816d6db5aadbee419841a8cdde740f73afe869a91a6899c96f2993a1c882b55b37a1032639d1968a9e4046b7af3007d0293af5e4b74389847d6a7faa602f6f97610a14b2316c9045bfb9698c95a6da1bb682f44a269eea77e633cabb12b1d5993a97032b85a929584407182a0349ec834fbc6c87e5198e59ac0eb6e9bc83ad4ceca1bc7bca8cb34349e342e22ba2aea22125d46ebb473c47f6269b73d597fa9f82bbc70ea5198b7f48e016392d189125a15888054763581c87424a486b22e775c70ae9913d57df4428af047814233ef6998f4ea5179318af9872bde490053bd8f778257d599d0f481444f1c922010273acf2026fddc17dd6e5b24184515f6f7f10b5b3d82a5829000182e20381e80220602e2506c0e5e031448c0607714e86e9c234bfdc00bb8639580eec0e8ea41373d82a5828000181e203922020d5796125fc67380346b8b2734a81ff08e1e9f457606ef2a4528ea411888fef23000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x312ead6", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc30e8b748b22427010e409aece99f62953c57de4f68383af4fd363beb0801e8d", + "transactionPosition": 89 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cc336", + "to": "0xff000000000000000000000000000000002cc33b", + "gas": "0x3528aa9", + "value": "0x8abb7a55e7bee0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708198b12d82a5829000182e20381e80220a4c966fc3621b677c749e58fce4a19d87f495a2bb82e5f5a294900321f38ba3c1a0037e0c2811a045b34061a004f9b81d82a5828000181e2039220200a7617f7ebeee2028f3677e7d760301d0c595d3d786d2daef70aae4cc7fb162100000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x24c0262", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xdd94aee23e9cfa008afff1f60c7eb2a27e2896878f2be8ab834cd71caf5d3ff8", + "transactionPosition": 90 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cc33b", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x33fcd9a", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xdd94aee23e9cfa008afff1f60c7eb2a27e2896878f2be8ab834cd71caf5d3ff8", + "transactionPosition": 90 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cc33b", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x32f0852", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xdd94aee23e9cfa008afff1f60c7eb2a27e2896878f2be8ab834cd71caf5d3ff8", + "transactionPosition": 90 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002cc33b", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x31cf2e1", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f9b81811a045b34060000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x476eaa", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e2039220200a7617f7ebeee2028f3677e7d760301d0c595d3d786d2daef70aae4cc7fb1621000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xdd94aee23e9cfa008afff1f60c7eb2a27e2896878f2be8ab834cd71caf5d3ff8", + "transactionPosition": 90 + }, + { + "type": "call", + "subtraces": 2, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000020bb1", + "to": "0xff000000000000000000000000000000000020d3", + "gas": "0x1d9e63c", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000408181870819321dd82a5829000182e20381e80220613a2f762e0bbf0a1fdfeb573b16c9ef105d5ce19c5ec21194cfccdfe2c9c6231a0037e043801a004f8a90f6" + }, + "result": { + "gasUsed": "0x1653202", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc4a33ab23d55c88a9eb2745a97e37dd6b44d96b9f14a00a9a1c06df904b759c3", + "transactionPosition": 91 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000000020d3", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x1cea250", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc4a33ab23d55c88a9eb2745a97e37dd6b44d96b9f14a00a9a1c06df904b759c3", + "transactionPosition": 91 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000000020d3", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x1bddd08", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc4a33ab23d55c88a9eb2745a97e37dd6b44d96b9f14a00a9a1c06df904b759c3", + "transactionPosition": 91 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001bbcc3", + "to": "0xff000000000000000000000000000000001bba4e", + "gas": "0x608c127", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a002138aa5907808c23dc69fd600fac65df2b08b4ee5595049dce3df8a1ca6c1a0b47067300709c7bba1e1b7edf0f1ae8a11dced9fd646c87042bbbfbf8e2e77651c10597cb746fa0ea2caaddb219a8b79a0fdd9bc84242471f8b097d6e6ee1dc547c7f068905f103fff2cb2220608de1f525e034d46bed5968ad6799e44af0ba7805e86c2124dc262c3c6b8d73c10ab458a651ffde6b77a4621bd6af909eb88bb583559717d37c0363d75329d4f444c47a71e44670c1d7c9f6e08fcdbcc8fe70542e445893dd5ea51f205a5d4695eb2ff2a484df3be74a6462cccced5c7c552ec2e3568d02d4eab26e05da573b652f1c5a6bd51092350f83f803a8e496c069e866a9bf26c956bbeb82488d4410d2cab92a151abcc41526e74a57b4131ddcfa7c2d3e0706b6e8310481e5d7e2bf283ab1ae5650716b4ead437aed5bc50aa57138e976e885c28a1fc6e6e9a09619974b09bca55f34348d718ce52829d085704dab7715d49c2541c4a9301c7b45d85526e4829b4c07505c07508879e27a1f61bd261bf5d64d599f7ba195237ddae768b0fd06c377c24beffb203055e3ffed44a4a8584f5433ab586182a403f41d2ed434452ba32a08f9c24d8fe621b8bfc6c7e3d914c706e589e68563c1ee9c18f561d569dfc486f6765e30bdf165806e41b0648e82f1c9ebcdf574145c3c3bc6b92ff866337d46005ef56b786233c9d87f8793081bb7f903cb1e783152b482e86577a5df8cdb63744a47ab8f6d27b6380b9be7a4c53aba1f8b985e06e9623280468ea84aae5c6b4835a20fc43a40f4a0bbbbf2160586f5072fdbf999a08763587c500af0629bcd25bdf32c542e7e7af6835a6949527b804379ea137a17c623aa5917c802756919ba64a76ea8dcd49cd3478dd5d2f6d8d2f00204c2028c1b49136d750360613e277542de1930ee1559c581f1a6b3842abf100034380ecadd6dd1bc13adef306133549d463f45dc29968ad5c15b0adf9c13e051ebae7cf2a5bcb1a858df6daa3e19c2f01120af1b38e1ce0412135c8a1426fc0f009888ecdc65e0d91ad80e1fd42856598cd06e4dfbc6a9c7600fa56a48f755599fac92e816b9dce6982768819a6e24ecd3e0d8ab12fb35283b974796b9e304cdd961b15770331f0186c6db6966ddbbf7bef2a74ea8881ddc1b5cf22c6f764b0e68782b6df21f4dcd69ead4744298eb49c1ad87f086c1236e1e3ca477a7048c480c781254bc5a68886f5d94ff26d00439ac1805266f20547f11753b0d50e724e8930ce5be8af4d3b325d40eaca084d7050143831ec1bc40b5ad1b06aad472a0ff5cefdaa94aa33e5ad0ed5299436799a9a6b42a1222f56995de79fd7979b712eb8212810f8651832c7f4341ef4be3e2801de16898be3fdcd20f45aecc718c7066e160e0867afb2ac813a572645b20857d24388edfb15259db541187c2c5cb2c438b2068c64b2bf275004dabd8259134108df796d4a9603f9e26626521c718a28fc9a10a30613abb792b2a9e9d464295867fdd94968134d3895e7efc6ce4c884d213a81692625c28db3e18d5e03e3e6dd39fe9894eb02c50689c09890cdd1a27ce56623acd2381e7b38250bfa880d59049cdf1adecfe224446513fca3df71db5b5500ca038fb9569adf1f526926b9bfa66e27df4470baa2f1429bb1c2676cf8877f173c8d8c2e0c152e17cb6794cb80083cce0a2e7a57c31a5ba41913ed9806f2d50e3a889fdc888e24841b62b5f1c9cdc74f1152e882fb789846533a5e4caf0d5f1ea050856b5f7f82f8728a859107978cd95d62652a1e7191b1a296096dd8cf250de8b22e948d4102f2955b61a3effa2b667a49cb58c688bf2510a018e360db50394e57c41d39b866f0144e9bf9856108c83572149b6621a5f9491d732ede5b7bcd7b34f810066df96dbcc341b32a0c7756f05345b66ffddffc5eaa5de22928efcc6c80a949374db5a546008c47ef4fbf296b316dd03a57a40a89c730b9f3602df445547ea5ba4262e51919e2cb4c71ed4f5c63740d347c1471d05a9a266fe318ec306fdb9ddc6211caef273867e3e409dc317e4e04d49e40b474e29456f2f3e6252d2a18ed90e5188d13c74cde5e904d8cdb95c55c636c83c085c58ebaccea0ce2b1ec5b0696932007b538bd977ca26f630e9df8a16c2b031692a160ffa45f5d6e9a625412163c2771a54c4432d61f49ce9de85b475dc33eae4b2886cf4343ce2ed591a9f3f48b1b6bfafe3e59515ff9ee1895596e80c9295004d71ec645a4b840a37057c8b07b7ec4b2a2fa07c3624a48b34a49173b6713e85418fd52bf222ab43029c6d1a629633d78153423134ab11ad45b15376c4bf132fb440b81053df7b1b4f25761ea895ea89b5c512f753859fc182cef73d8a33b958d2cc2d4383f0ada70d8d6d466654f62130959ac09a4c380f8f552d8b1afb6ce83abd2fff317cb0d7b2b347a2b0b46e29ebb6a23810ac8e0c4b90dbd81e268f2ce8659fdf1da7c97c41096d9a0a19a69ccf3f7880d49b669384562670c4255e194bd26f3cca9773feaba4c280ba2e357a6c2981f5b28f711f730bb01db7401c184bda05241d71cf8c10694ccd1f852f9c849133bf5cd34dc343735aeabfdea12a318ebf2a94fedd7161d34c30d724f968741c1738ad0fd94e9667b291342901883f0ed894402afa5d3a113a640e230d4ed85b8fc2af361d15402b618eff62b5acc012fe9dc52b245b0000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x7ab5df", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x42d6ff73ca53378a960a6b23df67cf45e01045369c0dec7d21a7a9eabb2cc8f9", + "transactionPosition": 92 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001bba4e", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x5beec87", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000082e8808821a001bba4e1a002138aa805820f39f2cdf9f09e7637320a65dc2e350ad731fba586b3fe5cc1838cfd58ff596fe582081773ad568e0121c5b3b01461b0b703611f4bf3317c88a87897e2e8e123489d85907808c23dc69fd600fac65df2b08b4ee5595049dce3df8a1ca6c1a0b47067300709c7bba1e1b7edf0f1ae8a11dced9fd646c87042bbbfbf8e2e77651c10597cb746fa0ea2caaddb219a8b79a0fdd9bc84242471f8b097d6e6ee1dc547c7f068905f103fff2cb2220608de1f525e034d46bed5968ad6799e44af0ba7805e86c2124dc262c3c6b8d73c10ab458a651ffde6b77a4621bd6af909eb88bb583559717d37c0363d75329d4f444c47a71e44670c1d7c9f6e08fcdbcc8fe70542e445893dd5ea51f205a5d4695eb2ff2a484df3be74a6462cccced5c7c552ec2e3568d02d4eab26e05da573b652f1c5a6bd51092350f83f803a8e496c069e866a9bf26c956bbeb82488d4410d2cab92a151abcc41526e74a57b4131ddcfa7c2d3e0706b6e8310481e5d7e2bf283ab1ae5650716b4ead437aed5bc50aa57138e976e885c28a1fc6e6e9a09619974b09bca55f34348d718ce52829d085704dab7715d49c2541c4a9301c7b45d85526e4829b4c07505c07508879e27a1f61bd261bf5d64d599f7ba195237ddae768b0fd06c377c24beffb203055e3ffed44a4a8584f5433ab586182a403f41d2ed434452ba32a08f9c24d8fe621b8bfc6c7e3d914c706e589e68563c1ee9c18f561d569dfc486f6765e30bdf165806e41b0648e82f1c9ebcdf574145c3c3bc6b92ff866337d46005ef56b786233c9d87f8793081bb7f903cb1e783152b482e86577a5df8cdb63744a47ab8f6d27b6380b9be7a4c53aba1f8b985e06e9623280468ea84aae5c6b4835a20fc43a40f4a0bbbbf2160586f5072fdbf999a08763587c500af0629bcd25bdf32c542e7e7af6835a6949527b804379ea137a17c623aa5917c802756919ba64a76ea8dcd49cd3478dd5d2f6d8d2f00204c2028c1b49136d750360613e277542de1930ee1559c581f1a6b3842abf100034380ecadd6dd1bc13adef306133549d463f45dc29968ad5c15b0adf9c13e051ebae7cf2a5bcb1a858df6daa3e19c2f01120af1b38e1ce0412135c8a1426fc0f009888ecdc65e0d91ad80e1fd42856598cd06e4dfbc6a9c7600fa56a48f755599fac92e816b9dce6982768819a6e24ecd3e0d8ab12fb35283b974796b9e304cdd961b15770331f0186c6db6966ddbbf7bef2a74ea8881ddc1b5cf22c6f764b0e68782b6df21f4dcd69ead4744298eb49c1ad87f086c1236e1e3ca477a7048c480c781254bc5a68886f5d94ff26d00439ac1805266f20547f11753b0d50e724e8930ce5be8af4d3b325d40eaca084d7050143831ec1bc40b5ad1b06aad472a0ff5cefdaa94aa33e5ad0ed5299436799a9a6b42a1222f56995de79fd7979b712eb8212810f8651832c7f4341ef4be3e2801de16898be3fdcd20f45aecc718c7066e160e0867afb2ac813a572645b20857d24388edfb15259db541187c2c5cb2c438b2068c64b2bf275004dabd8259134108df796d4a9603f9e26626521c718a28fc9a10a30613abb792b2a9e9d464295867fdd94968134d3895e7efc6ce4c884d213a81692625c28db3e18d5e03e3e6dd39fe9894eb02c50689c09890cdd1a27ce56623acd2381e7b38250bfa880d59049cdf1adecfe224446513fca3df71db5b5500ca038fb9569adf1f526926b9bfa66e27df4470baa2f1429bb1c2676cf8877f173c8d8c2e0c152e17cb6794cb80083cce0a2e7a57c31a5ba41913ed9806f2d50e3a889fdc888e24841b62b5f1c9cdc74f1152e882fb789846533a5e4caf0d5f1ea050856b5f7f82f8728a859107978cd95d62652a1e7191b1a296096dd8cf250de8b22e948d4102f2955b61a3effa2b667a49cb58c688bf2510a018e360db50394e57c41d39b866f0144e9bf9856108c83572149b6621a5f9491d732ede5b7bcd7b34f810066df96dbcc341b32a0c7756f05345b66ffddffc5eaa5de22928efcc6c80a949374db5a546008c47ef4fbf296b316dd03a57a40a89c730b9f3602df445547ea5ba4262e51919e2cb4c71ed4f5c63740d347c1471d05a9a266fe318ec306fdb9ddc6211caef273867e3e409dc317e4e04d49e40b474e29456f2f3e6252d2a18ed90e5188d13c74cde5e904d8cdb95c55c636c83c085c58ebaccea0ce2b1ec5b0696932007b538bd977ca26f630e9df8a16c2b031692a160ffa45f5d6e9a625412163c2771a54c4432d61f49ce9de85b475dc33eae4b2886cf4343ce2ed591a9f3f48b1b6bfafe3e59515ff9ee1895596e80c9295004d71ec645a4b840a37057c8b07b7ec4b2a2fa07c3624a48b34a49173b6713e85418fd52bf222ab43029c6d1a629633d78153423134ab11ad45b15376c4bf132fb440b81053df7b1b4f25761ea895ea89b5c512f753859fc182cef73d8a33b958d2cc2d4383f0ada70d8d6d466654f62130959ac09a4c380f8f552d8b1afb6ce83abd2fff317cb0d7b2b347a2b0b46e29ebb6a23810ac8e0c4b90dbd81e268f2ce8659fdf1da7c97c41096d9a0a19a69ccf3f7880d49b669384562670c4255e194bd26f3cca9773feaba4c280ba2e357a6c2981f5b28f711f730bb01db7401c184bda05241d71cf8c10694ccd1f852f9c849133bf5cd34dc343735aeabfdea12a318ebf2a94fedd7161d34c30d724f968741c1738ad0fd94e9667b291342901883f0ed894402afa5d3a113a640e230d4ed85b8fc2af361d15402b618eff62b5acc012fe9dc52b245bd82a5829000182e20381e80220c76afb4b41e42e410d2e7e6626ff6b8b33164323e57d3ce64128fbc5010c6860d82a5828000181e203922020077e5fde35c50a9303a55009e3498a4ebedff39c42b710b730d8ec7ac7afa63e000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x316261a", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x42d6ff73ca53378a960a6b23df67cf45e01045369c0dec7d21a7a9eabb2cc8f9", + "transactionPosition": 92 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001bbcc3", + "to": "0xff000000000000000000000000000000001bba4e", + "gas": "0x6adeeba", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a00213857590780b1007b4492daf38cf92b272c7955ed27cd37a1eb17c5e88286ec68b3e21e1461cc1e34069af515a13e1835681f32b368b80a88a4bcb813e6e266251c6f33426c0ee3e8522218409424fdfbb592efc4d5ba8707eb6179df411f1524ee1949b98610e80b9d4b7be48c8bab6b64122a65146aa69f06a7bfe7dcf37596e1d1ddd1b6bca0d1aee19bbbc64f93a79e325cf2e69313642489c202aa3f20834782f9dc0b39e47d670d7ee6a9582d42bd06d77442076d3af3fdefacf2883c2bf52a87e25395748a4c99ff87d6ed45d6343c695bd515adaabeb7a79f4a333ff05c0e73c138a2dc3e540261bc57d7517327c8f81d0a8ac554f0b4407913e9dfffd128c3d0f8a7231f6ecf1023174389f7857f93ab45187fe90ed964e1b63cecc666b202f3f8031eb67cdd680f5ac27bb20aafe403baef23887c9e5983f5222f29bebb67b81ed4e9e4bb32b5c93120b2a13cd83b658d8f5989ff15d3aa81f86fca8850e414c2ea642b71d30bdd6a394394abc13e0d29b32bcbf9cb1843c07124c1c3a960b16c9796b59d275b98430fba4ffcbeb084f96238b1e936727613135ae0dd145fb72a87494f04f54f4855b2e4e699b1eababeabdfb405110110e0e55a3f93c7e89fe6c41f12f79f1f49c45331d2cc3e4653902901092b2b8ef047e2c717aa633d2596184a4b41834bc8473c80348699db9f228964bd4c86f6a8dd41963b3f3cf6673dc47a7fad3b13cda21489782d85aec609a2b03fe1ee546c367eaeba5e010456c7bca14242391787781874d71b7c04e302c7eea0731e869cf55f43a50b4660ef90b59b498b3c00eac63b5f163f921ee30438a214dfb3f1a152578b59d12ffac6f41613edfcacf34d517ab03fee2559f97c97cf212bb8aaf891ca30703ac9d1c06e1ddda63152027199c6c9e6f84e57b801357d3b9db691330d7ad1832cd70a9e821847e10113500d47424c7b7964aee8003dfe2f00f96d31575ac5228e7ae8dd60f1987fa5e2df0b9f733db586b65ad998ad6d88d622756de188a9b03c2311163a6e4771566976fbe573104a637f4888947cf15c0c0655dbce862b386a3ca53081b3b76f81958a5bd20bb5dbcdf009c2a7088c3d8784d51f682669b8f1b45f4764cd073aaa77a8089cee546ecdae57bd94b36d24bfb7e9199407c3cfcdf47fb4b6f681a8dd46017b1da29f0ec3b1d18f14166c5089c7ef26620f7503ddf6d4c1640f4d12e820c8ccc72598d33702f7d2483712306c217b646f938510c81afcda60bfcfbad6a134e18654b85180e4b08285b6b2c79c561af610d6c8ada60e88bc28e7ef39844611db8cb095a2ecfa622b31397cb93711e9207b77cf53867bb9993a97822c38fb055750c9a8022a0f0d90face484c80044eb15c2cafd7cdefb48476ab92cd458e5cfddd975ae85439522466afe858a792d929451c9fc080817e1be3056c3380f912b660d3ec2bf0d42a6fbe609d410e25367b219926c4105110766c1286e389ee46e47d3b1d3434b5365d851bad4e1f77d3e45f8ee3f65365c50f88ad65fa31b0b5701f0d7e3336d462d271a518c1642dbd94f8282faf96f00d85ef614ed27d10bd8479de6ead9109e94091154c162276d5c66fe99b3ffe43e5cb2c8dbc545a0dd131c4a30dc7a0eddb3ae96cace625bf72b9140ed306d90ac5057fbfd4b4b9679e1392f8df971cc1cba61a82779c41a3ba13ca860541c5aded732e46aa71bb64385f83ffa339548afbff8bfa9d289f41125e47fade43eaf9ca210a0d850b1b313023290bf0766f252b83450325221f80262b144db67c40f49b410cad37925cab42e97b1b9f37a485df0ff0b37ef87632ae07c593730bed2538a47fa2717561e0cfaa1c7b3d81e82c0947aac9ea8ec8a40055c1bbebd8cd5e8352bb82c4bfc5c90bbcbb6e6b5388fc7f158b7d1c6d4a4eafb0e496e801ac010aa6989b4d6bb0490642aa76bb4349bee326a8b00c06e9ba01ce15165a4dbb5e7da927fa59cd9175f5ba95e800ff43cdb1e65e19d48e552b2608850ef47e9b40ca7b1c0a3464937f4480211d6f4baae37ca8c956dfcebc6fe4e9389ac61c359ed6a4c9fa2edcfc70ded57ec626f4b7fd758613b104a9caf590cd07c4dd82140b06cbda7aa6d051d7b7d3842b4bd19971ae089e0a6816b3fa483a3d43fe8a4c722ab531a380ba1f50214f63cd0478ba8a1692e724d0c8f66cf4ef24f105e19d6dd2f4a889d5870009daf12259400e055f47799a8af59e5af090f063000bf9249e0873416878ae2e5d96b99559ba442fc66135822748dca90a17b62aba124c9dce11f4bc10de045ef27c250b0bbd300340abb044c0650646aed8570e3001c881254040027e8e8292f539b3cc41cade3427ce1e2fac0925efae5202961ed0eda80f590a19af89bef00fddabc96ee60dd5b9a7354bfd368de599d3f39fe6d00ca22ca43411abdd9519c4ba55e35799b1769f23509637131f493bd9561bb6c284a0711f22b702d7e3d8bc1adcc135bfcf7612277bf88801919448799005e25fa86afabd45820ec3178b0d62de904693b93366a31c9963bafb678f81168abed95b5efdfdb88f181bed43e298bc88ebce505591e9a31f1de187e713f22c1b01299f73e1e69c396d246576157bbec8200ac414ec7ea11c880682b2c2727d4ff5c28b1a049e3b369d992b932605b571f87d8cc9bae645ee150d9624f911e704c64cf9a3c28223d60000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x7aae4a", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf099c22c6ed9edfc55c1546600f7467d56f5e43350d300b9e11e76de226edc2e", + "transactionPosition": 93 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001bba4e", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x66421af", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000082e8808821a001bba4e1a002138578058206b302393a7aff44a8f259b4ca67a22a687cc8e7df35eda9dc30dd32aed8a1afc582091901695a71c68c04728449c97e6f2842d92b0bb83026300a51b3bc8583385ca590780b1007b4492daf38cf92b272c7955ed27cd37a1eb17c5e88286ec68b3e21e1461cc1e34069af515a13e1835681f32b368b80a88a4bcb813e6e266251c6f33426c0ee3e8522218409424fdfbb592efc4d5ba8707eb6179df411f1524ee1949b98610e80b9d4b7be48c8bab6b64122a65146aa69f06a7bfe7dcf37596e1d1ddd1b6bca0d1aee19bbbc64f93a79e325cf2e69313642489c202aa3f20834782f9dc0b39e47d670d7ee6a9582d42bd06d77442076d3af3fdefacf2883c2bf52a87e25395748a4c99ff87d6ed45d6343c695bd515adaabeb7a79f4a333ff05c0e73c138a2dc3e540261bc57d7517327c8f81d0a8ac554f0b4407913e9dfffd128c3d0f8a7231f6ecf1023174389f7857f93ab45187fe90ed964e1b63cecc666b202f3f8031eb67cdd680f5ac27bb20aafe403baef23887c9e5983f5222f29bebb67b81ed4e9e4bb32b5c93120b2a13cd83b658d8f5989ff15d3aa81f86fca8850e414c2ea642b71d30bdd6a394394abc13e0d29b32bcbf9cb1843c07124c1c3a960b16c9796b59d275b98430fba4ffcbeb084f96238b1e936727613135ae0dd145fb72a87494f04f54f4855b2e4e699b1eababeabdfb405110110e0e55a3f93c7e89fe6c41f12f79f1f49c45331d2cc3e4653902901092b2b8ef047e2c717aa633d2596184a4b41834bc8473c80348699db9f228964bd4c86f6a8dd41963b3f3cf6673dc47a7fad3b13cda21489782d85aec609a2b03fe1ee546c367eaeba5e010456c7bca14242391787781874d71b7c04e302c7eea0731e869cf55f43a50b4660ef90b59b498b3c00eac63b5f163f921ee30438a214dfb3f1a152578b59d12ffac6f41613edfcacf34d517ab03fee2559f97c97cf212bb8aaf891ca30703ac9d1c06e1ddda63152027199c6c9e6f84e57b801357d3b9db691330d7ad1832cd70a9e821847e10113500d47424c7b7964aee8003dfe2f00f96d31575ac5228e7ae8dd60f1987fa5e2df0b9f733db586b65ad998ad6d88d622756de188a9b03c2311163a6e4771566976fbe573104a637f4888947cf15c0c0655dbce862b386a3ca53081b3b76f81958a5bd20bb5dbcdf009c2a7088c3d8784d51f682669b8f1b45f4764cd073aaa77a8089cee546ecdae57bd94b36d24bfb7e9199407c3cfcdf47fb4b6f681a8dd46017b1da29f0ec3b1d18f14166c5089c7ef26620f7503ddf6d4c1640f4d12e820c8ccc72598d33702f7d2483712306c217b646f938510c81afcda60bfcfbad6a134e18654b85180e4b08285b6b2c79c561af610d6c8ada60e88bc28e7ef39844611db8cb095a2ecfa622b31397cb93711e9207b77cf53867bb9993a97822c38fb055750c9a8022a0f0d90face484c80044eb15c2cafd7cdefb48476ab92cd458e5cfddd975ae85439522466afe858a792d929451c9fc080817e1be3056c3380f912b660d3ec2bf0d42a6fbe609d410e25367b219926c4105110766c1286e389ee46e47d3b1d3434b5365d851bad4e1f77d3e45f8ee3f65365c50f88ad65fa31b0b5701f0d7e3336d462d271a518c1642dbd94f8282faf96f00d85ef614ed27d10bd8479de6ead9109e94091154c162276d5c66fe99b3ffe43e5cb2c8dbc545a0dd131c4a30dc7a0eddb3ae96cace625bf72b9140ed306d90ac5057fbfd4b4b9679e1392f8df971cc1cba61a82779c41a3ba13ca860541c5aded732e46aa71bb64385f83ffa339548afbff8bfa9d289f41125e47fade43eaf9ca210a0d850b1b313023290bf0766f252b83450325221f80262b144db67c40f49b410cad37925cab42e97b1b9f37a485df0ff0b37ef87632ae07c593730bed2538a47fa2717561e0cfaa1c7b3d81e82c0947aac9ea8ec8a40055c1bbebd8cd5e8352bb82c4bfc5c90bbcbb6e6b5388fc7f158b7d1c6d4a4eafb0e496e801ac010aa6989b4d6bb0490642aa76bb4349bee326a8b00c06e9ba01ce15165a4dbb5e7da927fa59cd9175f5ba95e800ff43cdb1e65e19d48e552b2608850ef47e9b40ca7b1c0a3464937f4480211d6f4baae37ca8c956dfcebc6fe4e9389ac61c359ed6a4c9fa2edcfc70ded57ec626f4b7fd758613b104a9caf590cd07c4dd82140b06cbda7aa6d051d7b7d3842b4bd19971ae089e0a6816b3fa483a3d43fe8a4c722ab531a380ba1f50214f63cd0478ba8a1692e724d0c8f66cf4ef24f105e19d6dd2f4a889d5870009daf12259400e055f47799a8af59e5af090f063000bf9249e0873416878ae2e5d96b99559ba442fc66135822748dca90a17b62aba124c9dce11f4bc10de045ef27c250b0bbd300340abb044c0650646aed8570e3001c881254040027e8e8292f539b3cc41cade3427ce1e2fac0925efae5202961ed0eda80f590a19af89bef00fddabc96ee60dd5b9a7354bfd368de599d3f39fe6d00ca22ca43411abdd9519c4ba55e35799b1769f23509637131f493bd9561bb6c284a0711f22b702d7e3d8bc1adcc135bfcf7612277bf88801919448799005e25fa86afabd45820ec3178b0d62de904693b93366a31c9963bafb678f81168abed95b5efdfdb88f181bed43e298bc88ebce505591e9a31f1de187e713f22c1b01299f73e1e69c396d246576157bbec8200ac414ec7ea11c880682b2c2727d4ff5c28b1a049e3b369d992b932605b571f87d8cc9bae645ee150d9624f911e704c64cf9a3c28223d6d82a5829000182e20381e80220b004dbb253f292969d2a45ab68bd917a5bb6b2f9031f74638403a29361b03e0fd82a5828000181e203922020077e5fde35c50a9303a55009e3498a4ebedff39c42b710b730d8ec7ac7afa63e000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x38bc17e", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf099c22c6ed9edfc55c1546600f7467d56f5e43350d300b9e11e76de226edc2e", + "transactionPosition": 93 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001bbcc3", + "to": "0xff000000000000000000000000000000001bba4e", + "gas": "0x65623bf", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a002138a5590780938809e4804b3536e70cb0531840c36f70f8131906dc55738323fd254b0f8f18832e0f414eecb7ab09e0c9fa971b9524a2df8199e2302002c584d0cfcd96dc2551dd37f06539dbd8721dfda01c3cdb7e8dbb9d4a3fc353056055197b0841ea550a104147ed57d86249ee9838f05fc23ac3656bd18c527862d624b75f4fb29cf1c86cdb9b232b44e35695742b71800b68824d4d88eb47c7ba20babaca7beb5fb74007d26e5f285fce5ecaeac4bad411b8987d887dc99d0030b2861eb7dc4700a9b82b3371e8454fab416288f44520e1308947cc468c68b489340804774cf5c8a4577f7749a84202b8f855170c681dcd0994bbdd6753f174ea6efbadf483278d57cbc93eb3dc9dc841cd3e7f70c93280117a3c3080a498a56b9dc66fcad5732ec70cec935046665103a1d0ed7629409d8cb0f5b8280ec38b07752a7b52bad8142d8c1a1bdf9fa05da66508e62ed1076ea78f4af3062b5546c4fe03dfcf49079c442d4ec450dd90a193ef337323b15c6076feb095dafda9640abcbe8a7c4233226c991ad259bcfaf2db6b55117614b6dec5dcbaadff4fb2ecc0152f74b6f78d7eea32ba4262e26e52246a91c2862f1477a38ea6bc1fa5805a16996af8b23fdb702f4ee31616c827af71b730c19392162b08c449cb72407d720b27ffd9b2b59fc3370db1eb60969f3b4f7c52796d8047963c2872bda2dc3f53ceb0e5bd9861e9ba55fe2ec7efd872b47f4841f4629dc0632197dff41ffdbb9f76176d9255d08a9f757c534b4dd6ecfb6f2700b11208d9f247d2b13661e6c761283d89824688d66049a1fbcc4dc6f623fe513f878a5c5a9de8b89482f9fe61e7dc9ba1f94d53b2162050f03a9056dcbdf255447e2a342e64a281daff99c80fc4460d23571a7bde6f3ec7c74e80adbf0e53b17e88b505d4bd5e54d4daa881819cdd5143d174130ace2e0a4ae39d81d37b2f2130b9fac40b14540bbaae81a0f156e76bb6c40000fae97789f1bd64a43f7ac4869b7d82b1dc569faaf5f4e578001dc2dc4e4b693f78464257d056de197ca2ebf24d02db47ed078a005a3653d3fecc334b6db1c37594781fac82018e230badade7730f75d55c29a594a73539a675fac512fdced85a95b79a874a585ff8a560c0e95bff0cfbd0790a96981373361d59342de7aa41b2592a98e6188626495895fc92b48fef4a34b39b07186fa9c1385e1d4cb12c1046f1483d1736f3881f98162cb507827c265c2f7d20f0a56334e75e056cc245fd34eaba22535ac6f6b81a94d78499a2659908a4af8ef74e19c6c1750589b1168a14cd43f4ba90eeabd5db32ca22056357aa79c914c87430f8e033e9d1ca73a60dd593c8d9b3150771833b871514f590d261ee8172649cbcb1c0af79cddf194261e0b0953f14edf1ded13a435e02dde8a7afa0aa2e858165fd467dacbfd8ad5cb59e51c11dadbab16a24aa34e8bb20a5d52bb4126dd9ef86420ec17a96cdf05aad211c7cec103a46c2b4ce956c73b51cb8421deef8ee19b13d8aa58daf0ae56e202436c193919b844b06fb9b21e9e2a900387a76e68cc654e16def4047035f485fc8b1618e987319d7950ccb42d6a70c8725e49dafff9a476a374a14cd09adef5a5c4b04dfa4887c6b64c4355d3a444c435f22275f7b5d8fc491236e62a0edeb5d2ac2a6d9d68ba7c3e1e6906a03d995934fbd9849b791ae648cdc0f1494ce758bff37b0116f3526cdafab18120f491323065fa345c2066e99d9ba2337bfec891a27e4ab0918d78652c329926d4186bccba63aba373b410e48301bebb03aadc8c36efed11575dd2ef1cbc5b9495dc498733dd4c7eb97c3019ec2f7a13da5c61f4130f4e89fad38b65b3bfaec1b846f70456b78869e25db44e67fbb97c6a680a9812b105c60b77074dca69c0d16a904da5388c5f935835f48e40eaf1e2da59fa2f5598b9653adf96dcaf14c68d5aadfa744fbfcb8688f195d6b8260b9f25015a58779b0ce0c00b1ef06f241effb557985c0680d131f9ba8e8f5d1bf351df45766fe4dacb08602352ac292f27e875c1adce61cc9a71bef08f567bb77e5f7586354711eb1c968bd4aa18966cbfd945b706e1cac579278aa5392da1204b1857c72b2ae68cbedf01c632e4e0aac3f88f691711d80ac557ae31ef84e69007fe74ff3195a3137f363a783418ab3cf1dc2df032e9d0ff227426b003249254b786dfa00b3a950dfd7225b6a372252e0ab276734ca6d61b86b268ca42a5aaf8134f9597ad3f6e6892d3d6aae36305cfc23c52cf519f882d61f8d43303fef819be2a11166fe83170cd28701662da9f08853b2853c09999197098cd894b0ac636192dee9d773a1d4299d93a64fe7f3b4c3897ba23652bc6635e6bbb66120e108e4d1f65ad78aa5ab3f170d7ca0e42626c279e685b3dd432f15a521c0a0e98e5fbcd0ba0d9eefcaa7b01d68b04b31c71ce9799a86f88410e84a3b61201ed738f737fd408cc284dfc7c66fef73490d4b39a0e23430833764084f60c98923296607f8c4fe4ca962118eac687c891fa444c9ccc72147f948be34b412cffcf5a8d635640388bea532cbca5cf32704c3139459d07dc2a82f77d2046008d1bd797a318b07a671879392e67e0bace396957faf2ed64393ea7ae07df1d70381b0bc3e2c70cac7c5f2274c87fac7c56bd4cd3af4297c538ec1fc4f8e862edbf759b59f052bf3aa6e87acbc2ccb78944d0000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x6e6346", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x293e1eb922ef1b490b4d2c4c14b8e51c383ab38933509fdee3de8a7b4b96d0dd", + "transactionPosition": 94 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001bba4e", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x618a1b7", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000082e8808821a001bba4e1a002138a5805820fe51bab9960b3c80d4c6c8357c418a5593f2c35c2a7337a94a6e6ca9486bb990582081773ad568e0121c5b3b01461b0b703611f4bf3317c88a87897e2e8e123489d8590780938809e4804b3536e70cb0531840c36f70f8131906dc55738323fd254b0f8f18832e0f414eecb7ab09e0c9fa971b9524a2df8199e2302002c584d0cfcd96dc2551dd37f06539dbd8721dfda01c3cdb7e8dbb9d4a3fc353056055197b0841ea550a104147ed57d86249ee9838f05fc23ac3656bd18c527862d624b75f4fb29cf1c86cdb9b232b44e35695742b71800b68824d4d88eb47c7ba20babaca7beb5fb74007d26e5f285fce5ecaeac4bad411b8987d887dc99d0030b2861eb7dc4700a9b82b3371e8454fab416288f44520e1308947cc468c68b489340804774cf5c8a4577f7749a84202b8f855170c681dcd0994bbdd6753f174ea6efbadf483278d57cbc93eb3dc9dc841cd3e7f70c93280117a3c3080a498a56b9dc66fcad5732ec70cec935046665103a1d0ed7629409d8cb0f5b8280ec38b07752a7b52bad8142d8c1a1bdf9fa05da66508e62ed1076ea78f4af3062b5546c4fe03dfcf49079c442d4ec450dd90a193ef337323b15c6076feb095dafda9640abcbe8a7c4233226c991ad259bcfaf2db6b55117614b6dec5dcbaadff4fb2ecc0152f74b6f78d7eea32ba4262e26e52246a91c2862f1477a38ea6bc1fa5805a16996af8b23fdb702f4ee31616c827af71b730c19392162b08c449cb72407d720b27ffd9b2b59fc3370db1eb60969f3b4f7c52796d8047963c2872bda2dc3f53ceb0e5bd9861e9ba55fe2ec7efd872b47f4841f4629dc0632197dff41ffdbb9f76176d9255d08a9f757c534b4dd6ecfb6f2700b11208d9f247d2b13661e6c761283d89824688d66049a1fbcc4dc6f623fe513f878a5c5a9de8b89482f9fe61e7dc9ba1f94d53b2162050f03a9056dcbdf255447e2a342e64a281daff99c80fc4460d23571a7bde6f3ec7c74e80adbf0e53b17e88b505d4bd5e54d4daa881819cdd5143d174130ace2e0a4ae39d81d37b2f2130b9fac40b14540bbaae81a0f156e76bb6c40000fae97789f1bd64a43f7ac4869b7d82b1dc569faaf5f4e578001dc2dc4e4b693f78464257d056de197ca2ebf24d02db47ed078a005a3653d3fecc334b6db1c37594781fac82018e230badade7730f75d55c29a594a73539a675fac512fdced85a95b79a874a585ff8a560c0e95bff0cfbd0790a96981373361d59342de7aa41b2592a98e6188626495895fc92b48fef4a34b39b07186fa9c1385e1d4cb12c1046f1483d1736f3881f98162cb507827c265c2f7d20f0a56334e75e056cc245fd34eaba22535ac6f6b81a94d78499a2659908a4af8ef74e19c6c1750589b1168a14cd43f4ba90eeabd5db32ca22056357aa79c914c87430f8e033e9d1ca73a60dd593c8d9b3150771833b871514f590d261ee8172649cbcb1c0af79cddf194261e0b0953f14edf1ded13a435e02dde8a7afa0aa2e858165fd467dacbfd8ad5cb59e51c11dadbab16a24aa34e8bb20a5d52bb4126dd9ef86420ec17a96cdf05aad211c7cec103a46c2b4ce956c73b51cb8421deef8ee19b13d8aa58daf0ae56e202436c193919b844b06fb9b21e9e2a900387a76e68cc654e16def4047035f485fc8b1618e987319d7950ccb42d6a70c8725e49dafff9a476a374a14cd09adef5a5c4b04dfa4887c6b64c4355d3a444c435f22275f7b5d8fc491236e62a0edeb5d2ac2a6d9d68ba7c3e1e6906a03d995934fbd9849b791ae648cdc0f1494ce758bff37b0116f3526cdafab18120f491323065fa345c2066e99d9ba2337bfec891a27e4ab0918d78652c329926d4186bccba63aba373b410e48301bebb03aadc8c36efed11575dd2ef1cbc5b9495dc498733dd4c7eb97c3019ec2f7a13da5c61f4130f4e89fad38b65b3bfaec1b846f70456b78869e25db44e67fbb97c6a680a9812b105c60b77074dca69c0d16a904da5388c5f935835f48e40eaf1e2da59fa2f5598b9653adf96dcaf14c68d5aadfa744fbfcb8688f195d6b8260b9f25015a58779b0ce0c00b1ef06f241effb557985c0680d131f9ba8e8f5d1bf351df45766fe4dacb08602352ac292f27e875c1adce61cc9a71bef08f567bb77e5f7586354711eb1c968bd4aa18966cbfd945b706e1cac579278aa5392da1204b1857c72b2ae68cbedf01c632e4e0aac3f88f691711d80ac557ae31ef84e69007fe74ff3195a3137f363a783418ab3cf1dc2df032e9d0ff227426b003249254b786dfa00b3a950dfd7225b6a372252e0ab276734ca6d61b86b268ca42a5aaf8134f9597ad3f6e6892d3d6aae36305cfc23c52cf519f882d61f8d43303fef819be2a11166fe83170cd28701662da9f08853b2853c09999197098cd894b0ac636192dee9d773a1d4299d93a64fe7f3b4c3897ba23652bc6635e6bbb66120e108e4d1f65ad78aa5ab3f170d7ca0e42626c279e685b3dd432f15a521c0a0e98e5fbcd0ba0d9eefcaa7b01d68b04b31c71ce9799a86f88410e84a3b61201ed738f737fd408cc284dfc7c66fef73490d4b39a0e23430833764084f60c98923296607f8c4fe4ca962118eac687c891fa444c9ccc72147f948be34b412cffcf5a8d635640388bea532cbca5cf32704c3139459d07dc2a82f77d2046008d1bd797a318b07a671879392e67e0bace396957faf2ed64393ea7ae07df1d70381b0bc3e2c70cac7c5f2274c87fac7c56bd4cd3af4297c538ec1fc4f8e862edbf759b59f052bf3aa6e87acbc2ccb78944dd82a5829000182e20381e80220a85c7858b9d76a0adb990410e518b6022325a8bd1743404b270c1ce80c9af42dd82a5828000181e203922020077e5fde35c50a9303a55009e3498a4ebedff39c42b710b730d8ec7ac7afa63e000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x3f9c875", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x293e1eb922ef1b490b4d2c4c14b8e51c383ab38933509fdee3de8a7b4b96d0dd", + "transactionPosition": 94 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001fb931", + "to": "0xff0000000000000000000000000000000012d687", + "gas": "0x18c2204", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285058182004081820e58c081685d1d10d34edf3142ea3abfb4d9265e7ea2389b907852444da5f0952c0046f45b50589f6653be572e2590f82f26a4ada3ba4aa1637b7831390365b606352026d216bfc959d08c69ca7965439b86c03356b396062bcb98a458de9f302598960a06fdcdb4ac9ad54cecd47fffda00ea3b8a6fc666669325d25980c3ed20bfaf296f814dbc68978a23a30e03c28fc03fac83f85f9a7c23f15894643a8bef5046b4800a556ef86ff61691c7adfd35382506baf6713194c9f8a3c28be624078ba01a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" + }, + "result": { + "gasUsed": "0x149e229", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xa66054d53dc628a9f4132fad4beb3b4c8b77a9ecd878c31d58d9773310643125", + "transactionPosition": 95 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000235fdd", + "to": "0xff0000000000000000000000000000000001dd67", + "gas": "0x4e8c5d3", + "value": "0x1a604e782960a295", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a000a0023590780b1f46cf7da5aa8c412fdf20a3d29ffcb51e2090c60eca8ce7a6755cf86aac57d7dd57e66936da82659fbd0c263a7e9f38f3f539c4612078bb4cd042c493dbf3f240d6d2cc8716015b23e328f81bd082f1eacc23be4f00d1a36ae41db43600b4f0e95245fc967a52846d2510376596d14beea198356a3bebb4fc3cfff9b6239b25f5aa7c7ae64601deaed2be87b43a690b207bde83a79cb313f63bce1733fb94edae6bf878fa48803ef520cc8ae554ac1f34af9f55fb2717ce4d8dc9a59f1883d88c2b30a9303dbf7e63d1ba121ccb1d9105105d677a218602469cb651779b2fb8833e3a2fb949662511eb0e48ec57ad3b897e378aade2102729778bf7d9fac14e84a170aaa8a859ae05bd1617bbdee6d18e4f6742f19fcfc4d060f829cc2a19c1901d1c0a05e1ed11268b6218f9b393b29aa9c38fecabfdf11bf3781144e1e8b9aaf33b49b5ac2998cd0bcc19da4393eadf4334ab0053ee3076f4d6df1ed36fce20e00e4a735d3afc854a8752e50b7719a5feedaaa19e3067deea209e79a4bb0aee06ada2bdd419f02248ae079c7d9948c97b7d448e95326fd49a6abdf9506e3ed3a6dec1aecf431b0db2e42092f194fa9a71f648f9037c0a60ff5cab539b4e2ff47cca05d9c68b57acd0659005362fc753908599b75f2e403881bf3a17ea0550d9a1983496c891611b29dcec699c1332ff50847c22ec04a8e102f019aadbec4ef48c92879f449987188031bb4ed8cb68d0512ce2ec9204f85365793b9d9ea58941baab7823223a15805bd194ca9866d74dc3abf6ddec70633fe928518b951f39671b4d21b2c2c12500671848cbc84bb6cf98057fce0cb90f993cbc69c1e106d6195581eb8eb441a7c1502057a9f68f28ef4856c69a3643c3490129176378adfa1a297857a7f2f2ca80383c8ad7fa9969ef8bab649368a92c268b730ee01302e12604ea9644e5b0d1e640d7aca21e09f0cbedcf0eb6327a59d3e98503437ded0fc5894ef0440130f16727512ffbec3bfa4fd02753b12a0c65a040b71984bed42457725c9b2c9f494709f17fe698c157a690cf89339cc91f412109e3765bf76e6951ff2be4cf037b0463266d0bea907d01e76b1ce8e8240fdf9cff6c89baf70b232063f849885461b9d6206663e4d2833819de0187c675cb90d5bac958312fca8e926a113933f99251f8366a39fe273d1f2964c98e007acd7d15afa67ca70f8ff19f6a810ccb02a9e88991b7f54fc1b7248f5e740a6a90e362beb8a3c8bd20971ea11e3a5d5a541015b0690cca58f1e528fbe3b738e24a30e51dd775989f2dba69d89a2abe38865dde95af7a69bda1c610d2944f14de8edc0bdced10dfc0f3fd98e840f0627444e20da4b7947be112db42c1adb81230a603fa69fc0a8f3c5c87da309297a8c5d4a47eb3ee07000f81ad7b96dc27cd2639b359b08f63f79be56d41675c9af34d60f9142fd8c16d8489192ed4587cf2fc8029036d12a134b4c00c8002123b500acda1257dd1c501cf76f84c153cf5c018a3b18eeb1d63d39771938799043cd413e7b8e68b5a42d7054e609b0b5e857b878591dca7c5930fee7e313af3b8c1b48d49b3bafd6710d5ac22f13b4f63f12103bc779855772b229add4c5a21aa769fe3d0f6cad677e2d8a1c6c6da878f2cf49d4b42be7a617536bd924c93645bac6b43fef51efdccbc8679525fcaef83e89a8d41418644b2ab90813ea70a9d999f6b9a7eb233b48dc7928eda4917b07e3e0cba92674dbc8b0168d5d821d0bc093e7ba8c4c020e7a2374a96299b6f85c94a5ea467baab46b24fe798efc5e8debfd737aba2899b9bc759aec49102fa2e50915f3fbbad9ae2cb3f0ceaf287753a97330067c4e45df6da8bb01a34a6e76ccf228a28be3b6de92d158967a65898358bd47b4605ff64e320a62e4a72a394bc35df7e90cec68d8e4df0a771e3958474af79eb4d5376125e3f0b363714b9986d6985263f45f8580e05ec91e51123c868a9470fc490597432364ad04f20653b8335114b15f5c18de5e362ebc32ae2512dddfccc5c4db97e77bfeb4394ba114e2f9447c953ec89a94c39a25f8ec0bd2b0f89e3762043501aed99d6159ab4abeab32f66967fb6cab04fb56ee6ffd652095daa21642ae9083d5afec856642f9ecca82cac18df1f1fef5c868d946759af794f04319696b6fc63a83e75de58192c5f4bf21ef5e6c69a8b6afa88f4daa6c5cba2f03787b3c6cbf33421012bd9788a58dabd91d3b7769c901a46c31ed41d0459afeece50ac8de9d97c91c40c041b1173537a1f49b183ae68b6944756a63fd6000f6f881584d8e7e4863a9f3247956f108ed85a11c3b3d9e60634a62828b38322c845cd5ac80420146154d55fe1bac709803c803ece6de386d0df9d379f9ab10b82f0a29cf2d98e6ba564d2a8368508dc85c8b9876ce150fe7d88e4684723e0190c12de8200207fa88fe3af9590c8f0b19d0f0cc4ea1276779fa6815ce4294147f9189355a4dc71446560f06948889f4b7ef9b2c720260607cd9b01a69de0eec1432cafea0f87c5a1b6d0d11cbdc51c20a724238cdfd949e1221e13e8b29ea7c170eac8d9981eed96e72d59443212096d6299e286db80e661fd68bbd2d49434b90e11db7627860b17531086e59e049d5a510fb61c86c94fbc7fed1474c59280346f879698715675210bedbeab847af0e6c88d478dc20148d564fba21e557dcec0000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x9b3e40", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc209712792b9bb2ca548df72c743c2efa109181e953fac3f07e8e7026d7aa78f", + "transactionPosition": 96 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000001dd67", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x47e9165", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008338808821a0001dd671a000a0023811a045b18855820e846cbc689aebc3ec97bcdcb44da2e58c3e092c75ee715dfa353496d8d00286a582040123884cdb875d63ca4b9ceeb2f02840ac99070376e535012b6a5ed5323deee590780b1f46cf7da5aa8c412fdf20a3d29ffcb51e2090c60eca8ce7a6755cf86aac57d7dd57e66936da82659fbd0c263a7e9f38f3f539c4612078bb4cd042c493dbf3f240d6d2cc8716015b23e328f81bd082f1eacc23be4f00d1a36ae41db43600b4f0e95245fc967a52846d2510376596d14beea198356a3bebb4fc3cfff9b6239b25f5aa7c7ae64601deaed2be87b43a690b207bde83a79cb313f63bce1733fb94edae6bf878fa48803ef520cc8ae554ac1f34af9f55fb2717ce4d8dc9a59f1883d88c2b30a9303dbf7e63d1ba121ccb1d9105105d677a218602469cb651779b2fb8833e3a2fb949662511eb0e48ec57ad3b897e378aade2102729778bf7d9fac14e84a170aaa8a859ae05bd1617bbdee6d18e4f6742f19fcfc4d060f829cc2a19c1901d1c0a05e1ed11268b6218f9b393b29aa9c38fecabfdf11bf3781144e1e8b9aaf33b49b5ac2998cd0bcc19da4393eadf4334ab0053ee3076f4d6df1ed36fce20e00e4a735d3afc854a8752e50b7719a5feedaaa19e3067deea209e79a4bb0aee06ada2bdd419f02248ae079c7d9948c97b7d448e95326fd49a6abdf9506e3ed3a6dec1aecf431b0db2e42092f194fa9a71f648f9037c0a60ff5cab539b4e2ff47cca05d9c68b57acd0659005362fc753908599b75f2e403881bf3a17ea0550d9a1983496c891611b29dcec699c1332ff50847c22ec04a8e102f019aadbec4ef48c92879f449987188031bb4ed8cb68d0512ce2ec9204f85365793b9d9ea58941baab7823223a15805bd194ca9866d74dc3abf6ddec70633fe928518b951f39671b4d21b2c2c12500671848cbc84bb6cf98057fce0cb90f993cbc69c1e106d6195581eb8eb441a7c1502057a9f68f28ef4856c69a3643c3490129176378adfa1a297857a7f2f2ca80383c8ad7fa9969ef8bab649368a92c268b730ee01302e12604ea9644e5b0d1e640d7aca21e09f0cbedcf0eb6327a59d3e98503437ded0fc5894ef0440130f16727512ffbec3bfa4fd02753b12a0c65a040b71984bed42457725c9b2c9f494709f17fe698c157a690cf89339cc91f412109e3765bf76e6951ff2be4cf037b0463266d0bea907d01e76b1ce8e8240fdf9cff6c89baf70b232063f849885461b9d6206663e4d2833819de0187c675cb90d5bac958312fca8e926a113933f99251f8366a39fe273d1f2964c98e007acd7d15afa67ca70f8ff19f6a810ccb02a9e88991b7f54fc1b7248f5e740a6a90e362beb8a3c8bd20971ea11e3a5d5a541015b0690cca58f1e528fbe3b738e24a30e51dd775989f2dba69d89a2abe38865dde95af7a69bda1c610d2944f14de8edc0bdced10dfc0f3fd98e840f0627444e20da4b7947be112db42c1adb81230a603fa69fc0a8f3c5c87da309297a8c5d4a47eb3ee07000f81ad7b96dc27cd2639b359b08f63f79be56d41675c9af34d60f9142fd8c16d8489192ed4587cf2fc8029036d12a134b4c00c8002123b500acda1257dd1c501cf76f84c153cf5c018a3b18eeb1d63d39771938799043cd413e7b8e68b5a42d7054e609b0b5e857b878591dca7c5930fee7e313af3b8c1b48d49b3bafd6710d5ac22f13b4f63f12103bc779855772b229add4c5a21aa769fe3d0f6cad677e2d8a1c6c6da878f2cf49d4b42be7a617536bd924c93645bac6b43fef51efdccbc8679525fcaef83e89a8d41418644b2ab90813ea70a9d999f6b9a7eb233b48dc7928eda4917b07e3e0cba92674dbc8b0168d5d821d0bc093e7ba8c4c020e7a2374a96299b6f85c94a5ea467baab46b24fe798efc5e8debfd737aba2899b9bc759aec49102fa2e50915f3fbbad9ae2cb3f0ceaf287753a97330067c4e45df6da8bb01a34a6e76ccf228a28be3b6de92d158967a65898358bd47b4605ff64e320a62e4a72a394bc35df7e90cec68d8e4df0a771e3958474af79eb4d5376125e3f0b363714b9986d6985263f45f8580e05ec91e51123c868a9470fc490597432364ad04f20653b8335114b15f5c18de5e362ebc32ae2512dddfccc5c4db97e77bfeb4394ba114e2f9447c953ec89a94c39a25f8ec0bd2b0f89e3762043501aed99d6159ab4abeab32f66967fb6cab04fb56ee6ffd652095daa21642ae9083d5afec856642f9ecca82cac18df1f1fef5c868d946759af794f04319696b6fc63a83e75de58192c5f4bf21ef5e6c69a8b6afa88f4daa6c5cba2f03787b3c6cbf33421012bd9788a58dabd91d3b7769c901a46c31ed41d0459afeece50ac8de9d97c91c40c041b1173537a1f49b183ae68b6944756a63fd6000f6f881584d8e7e4863a9f3247956f108ed85a11c3b3d9e60634a62828b38322c845cd5ac80420146154d55fe1bac709803c803ece6de386d0df9d379f9ab10b82f0a29cf2d98e6ba564d2a8368508dc85c8b9876ce150fe7d88e4684723e0190c12de8200207fa88fe3af9590c8f0b19d0f0cc4ea1276779fa6815ce4294147f9189355a4dc71446560f06948889f4b7ef9b2c720260607cd9b01a69de0eec1432cafea0f87c5a1b6d0d11cbdc51c20a724238cdfd949e1221e13e8b29ea7c170eac8d9981eed96e72d59443212096d6299e286db80e661fd68bbd2d49434b90e11db7627860b17531086e59e049d5a510fb61c86c94fbc7fed1474c59280346f879698715675210bedbeab847af0e6c88d478dc20148d564fba21e557dcecd82a5829000182e20381e802209e50cb8175ce18db05fdce8f8651f33386cf196129b4be40a075c99187914609d82a5828000181e203922020af0d7d76927d5d2e47081e7d3d428177f43834c9386416c54fca266b3444f21700000000000000000000000000" + }, + "result": { + "gasUsed": "0x31bebc8", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xc209712792b9bb2ca548df72c743c2efa109181e953fac3f07e8e7026d7aa78f", + "transactionPosition": 96 + }, + { + "type": "call", + "subtraces": 7, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000001bac42", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x1273e026", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000eb8181828bd82a5828000181e20392202056bc2504599e6513bd273965038a95a26005013ceb8a54569a0046a513b162371b0000000800000000f555010f29c20971f6e29cead00b57277fb5975fd83f914400a29f74783b6261666b726569686572366e326c6f657735777166783777797768616e63347a74346c6a7776703263356f62727968346a756f356a6371616637651a003814d61a004f81164048001b6a51c29fe8e04058420192040362a715acc12b1e0985cf7fa25f4935d562a4e79f1699945b56c51e1cd242a0363d28cb1f9d735816b771f086829db7b4c88fc94f03a4814cfb597a9d1601000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x8e9f7bd", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000982811a045b576b410c0000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", + "transactionPosition": 97 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff000000000000000000000000000000001d0fa2", + "gas": "0x12604c76", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000014c1cb970000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000054400c2d86e000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x13301e", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", + "transactionPosition": 97 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x124c779f", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", + "transactionPosition": 97 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x123babbf", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", + "transactionPosition": 97 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 3 + ], + "action": { + "callType": "staticcall", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000021f2a6", + "gas": "0x1228a5ee", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000ea82584192040362a715acc12b1e0985cf7fa25f4935d562a4e79f1699945b56c51e1cd242a0363d28cb1f9d735816b771f086829db7b4c88fc94f03a4814cfb597a9d160158a48bd82a5828000181e20392202056bc2504599e6513bd273965038a95a26005013ceb8a54569a0046a513b162371b0000000800000000f555010f29c20971f6e29cead00b57277fb5975fd83f914400a29f74783b6261666b726569686572366e326c6f657735777166783777797768616e63347a74346c6a7776703263356f62727968346a756f356a6371616637651a003814d61a004f81164048001b6a51c29fe8e04000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2e9853", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", + "transactionPosition": 97 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 4 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff00000000000000000000000000000000000007", + "gas": "0x111c5e8e", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000c26ddbd50000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000064500a6e587010000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2ce23f", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000104f00120654f8b6172b36ea1e89d8000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", + "transactionPosition": 97 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [ + 5 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff00000000000000000000000000000000000007", + "gas": "0x10ece94f", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000d7d4deed000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000067844500a6e587014200064d006f05b59d3b20000000000000584d8281861a001d0fa2d82a5828000181e20392202056bc2504599e6513bd273965038a95a26005013ceb8a54569a0046a513b162371b00000008000000001a00176c401a001b60c01a003814d68000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x378d1f2", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043844f001205e5f30079f016ea1e89d80000500006a8d5434e2fdfc905110000000000520002f050b9c8c93d441ca1915680000000004d83820180820080811a034d18bd0000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", + "transactionPosition": 97 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 5, + 0 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000007", + "to": "0xff00000000000000000000000000000000000006", + "gas": "0xddc0709", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000de180de300000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000006e821a85223bdf5866861a0021f2a606054d006f05b59d3b20000000000000584d8281861a001d0fa2d82a5828000181e20392202056bc2504599e6513bd273965038a95a26005013ceb8a54569a0046a513b162371b00000008000000001a00176c401a001b60c01a003814d68040000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x2807281", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000d83820180820080811a034d18bd00000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", + "transactionPosition": 97 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 6 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000000005", + "to": "0xff0000000000000000000000000000000021f2a6", + "gas": "0x311f73c", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009c8258948bd82a5828000181e20392202056bc2504599e6513bd273965038a95a26005013ceb8a54569a0046a513b162371b0000000800000000f54500a6e587014400a29f74783b6261666b726569686572366e326c6f657735777166783777797768616e63347a74346c6a7776703263356f62727968346a756f356a6371616637651a003814d61a004f81164048001b6a51c29fe8e0401a045b576b00000000" + }, + "result": { + "gasUsed": "0x9858a", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", + "transactionPosition": 97 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce389", + "to": "0xff000000000000000000000000000000002ce3be", + "gas": "0x41499d9", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782193e18590780912ab6e26f928bffa3c68c9a467677eec142ecf7a2d7c57711f54aa9bebf313780e8f80cc5646a351ad95444b99ed0dca55ae5fad63d8694ef89818973ae10a1aeb4b104ae7f4803b0465d687d24c4311ff1a29125137f091a1f384e83c6dd6f0f31df5cea648ebaf3bf23e192e5570a56c6146a1a8b60124d047a11b0985d11ad2611bdf7042e2cd719dd55845779cc93293d56ec0c121918b2a2f69de6f516102859fea9b3e7aae7ccbc556e73015f404fbe8f2a6084a013f1b746d69c755b8533c06e199c3a3cd991793311106e59a79f319b007afbc7ec2b8cb93f9a42f14f2e5e7f39e26aa6b677313eca6a1f00ab0ce08d0eb91c0f1c9fad8069012697d54aa1ec9caaedad169001eaa0665504405c765b25f4d1209bb69b2f4b3f52390f1e0235828501c87f43b5bae1f51d54c5bb5e513d33441385086b40c08b2787d88350ef8b2e4e26ae68eb77d6e47c91b19bf6180433e342c57b7128d755a3bb1508a42134043ff697ca4915e666cb1e98ee0176480144a92c4d2a239e16343090965542b9073d87d453707027cd41b3cc4bedf13cd490a355e4da51322757aeb1c42aa193a7c848e55ab81a6e43696084f9c5965d467b8b797db16b4cdd8f15576a7f2efc5ec18c356c7a079edaca1be4b842a820c2776049f49c3e237a6f8d096f7473ca10b1b419adbce98aa92d0d34e7926d463166bf2b7afdaad927c6649d3b719a14db07e358a9b817ba1df393813cea360cf3754a12449716490218ec147c01f394fd5a78e1fd1efd1825f74b6c5ca1640b328ad5b7078f3d00b8988f85acb0bd284a1515d31b7daf53f780c22179fceafcbbef9e4cf3e99c4028671aba7c2adfcf2b455e914f962705f1c96e97042c1eedc665844f2bdf90159975255aaef853a9198cbfcbfdd982ea59ce4ccf92b9463035534c450743eafa9148eb11fafb866c07d79d4cdc3ae79ed0f2b95579adbd9ed25fa3d18a9a3ed4c936a47c9d670cc583a98d71490c569bc4637f93deea73cfa34ed7fd465c93e714b33431255c3300d0d03055df972b067d19bacbfe7fc79b25db29ac9718f188ec0421b2defd12c408a48c5d33bbc655fcce869995f33f9fd53f3dc36a73b7a362430bca272639b40d134e1d7405bdd8983c7bac2d8fb7208c6219b0899ae317015eaebcceee8fc9b93d573cbe70d378e50ba9a969f4a69407524a4023ef97aef3c01d128576f63b5699cb5c691a7039c4ef3ca9af3baf713495f149d7425cb417c52052f3e530e1e438dcf8b627f0263a27caa010917169b91952820d1a46a4b4f54357fda87463a5c866ce6afe7d9b23eefe2e173d737fe492470fa481ab883e0a82b86637364dcdc03c47318423f7f512839510ad64b82dd392b5d0c6acc2b824a9727a7717819dd8aba714e2eb11bf32348c632f06d66ab968e070ee328e357c90db353536eb2a2edca946abafd2f26b6f9ad6096fb44894d5a4d50e573d9c1c2d0fb3bfa5ec202c581b1cc08fd7594ebe6892ce53457fd7268bac26471341a438f811c9130d5397d61a98351e79d64ca387ede4e93eeaced4a56528a04f1299d9086887b2c726eb504e111af2445ed0aa76363a0ee535be1dd027a7adb43426caa0d90cac9116b5c36d120430ca5dae1c98644b72c6f847d74a0e71ae69604a4a4bab5628fdac4f92aaa86c70c4533334b366a751f23473cf65616aea0661fdbee4b9d3f2cb82578f44703e8ec514a72ce9b299e617d37106dd23f4c53dbe27780dcdb0312007fb60c2d43b3257ac99e75b71cbb611ebfc72c1fb724b15a8fe58b2ddcc2a5cd976a15ce41b4dd09cb9d09054e835d6d60b0172bf904074853ca9c6613c0fae8f792586238416b054c8710fd387e4d46c229a6d0d8f9b653bac5386284710668bb3fca857163f6db48e0420f0087dcb0dedde46fe29a90641b6c3c49db5d351a267305eeaea95c6e62e16a91917b7816972524fea118f26bf851baf069b8ecf2081cf91451d697be0b6887bccf0a3b67c96d44d1bff69cef9546d08e8dd66cb7417e7a2723440a6b6442c2a58bece8cba21ab9f197c3d0bc74c781e30efd25cf2542639ac3acfabdea3f7af221ad28c797930d368076cbcd112c0a1bf198d0c081d7f7f216889bcb5b446d720fa7f1333fba0441216568b55b9e3a869c47afaabdeb282b65c774c98db3d00c00406276b12d86c44f8dd8970bd7dc738bbe1ad712a10137ed87968c0f0c8afc53ba3f9a070e700dcd22b47340d17c2c7b2f1cd2039c89334e722e25b7d619c8319a6727f4b68279c64538ce4c03318853f4e1944b8d81d8b8007aca927ffbc02e69a77c45d2632a7c0afcaa90f5e6f2736f58bc304dd1170f685e8b47fccaded0661255d6ab8a290e4a8c44dedcf3c9660a7d35f040f0d63e14bde335b7968d5ed6e984a892b0a6a8139d96eb54a8d1022e8db082e062e0c252c3e01d6403afab8aaf6bc6f24d8157ed6cecad155e259ad5b018db69ccc08c0f15811ea78917fb72ad9e9573c7645fcf59cfa531b078ac8fe5b51efb61d8c1343ec3d890f34d6095d6000f0be39e9d0dfeb532f2708168b75f28ceddb56aa4581da4d5aca0b39a456ea7b8e43e883c9d3b12d51c0826ccb69d1e77956afd2adfc115a1e44ad78e68c2b93b61d71e0ddd4cdaaa7fd3589a1b9d2a42df325724d181b7c96af26643d47603af1289ede8f54c634ddb400000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x604a1e", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x2fcb2ae181befb6e530f111ad25c983e1d2e208d11caa73145c811266bdf34bf", + "transactionPosition": 98 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce3be", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x3e52cf6", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002ce3be193e18811a0453643c58200b7238a7ac085ec5b663a2ac9babde2c59068dd6e6e61e57784fee049b0eab2058201015e3ffb83aba7ad6eed57e89a09176c78e97da5c95b02f947c84ec8868be3e590780912ab6e26f928bffa3c68c9a467677eec142ecf7a2d7c57711f54aa9bebf313780e8f80cc5646a351ad95444b99ed0dca55ae5fad63d8694ef89818973ae10a1aeb4b104ae7f4803b0465d687d24c4311ff1a29125137f091a1f384e83c6dd6f0f31df5cea648ebaf3bf23e192e5570a56c6146a1a8b60124d047a11b0985d11ad2611bdf7042e2cd719dd55845779cc93293d56ec0c121918b2a2f69de6f516102859fea9b3e7aae7ccbc556e73015f404fbe8f2a6084a013f1b746d69c755b8533c06e199c3a3cd991793311106e59a79f319b007afbc7ec2b8cb93f9a42f14f2e5e7f39e26aa6b677313eca6a1f00ab0ce08d0eb91c0f1c9fad8069012697d54aa1ec9caaedad169001eaa0665504405c765b25f4d1209bb69b2f4b3f52390f1e0235828501c87f43b5bae1f51d54c5bb5e513d33441385086b40c08b2787d88350ef8b2e4e26ae68eb77d6e47c91b19bf6180433e342c57b7128d755a3bb1508a42134043ff697ca4915e666cb1e98ee0176480144a92c4d2a239e16343090965542b9073d87d453707027cd41b3cc4bedf13cd490a355e4da51322757aeb1c42aa193a7c848e55ab81a6e43696084f9c5965d467b8b797db16b4cdd8f15576a7f2efc5ec18c356c7a079edaca1be4b842a820c2776049f49c3e237a6f8d096f7473ca10b1b419adbce98aa92d0d34e7926d463166bf2b7afdaad927c6649d3b719a14db07e358a9b817ba1df393813cea360cf3754a12449716490218ec147c01f394fd5a78e1fd1efd1825f74b6c5ca1640b328ad5b7078f3d00b8988f85acb0bd284a1515d31b7daf53f780c22179fceafcbbef9e4cf3e99c4028671aba7c2adfcf2b455e914f962705f1c96e97042c1eedc665844f2bdf90159975255aaef853a9198cbfcbfdd982ea59ce4ccf92b9463035534c450743eafa9148eb11fafb866c07d79d4cdc3ae79ed0f2b95579adbd9ed25fa3d18a9a3ed4c936a47c9d670cc583a98d71490c569bc4637f93deea73cfa34ed7fd465c93e714b33431255c3300d0d03055df972b067d19bacbfe7fc79b25db29ac9718f188ec0421b2defd12c408a48c5d33bbc655fcce869995f33f9fd53f3dc36a73b7a362430bca272639b40d134e1d7405bdd8983c7bac2d8fb7208c6219b0899ae317015eaebcceee8fc9b93d573cbe70d378e50ba9a969f4a69407524a4023ef97aef3c01d128576f63b5699cb5c691a7039c4ef3ca9af3baf713495f149d7425cb417c52052f3e530e1e438dcf8b627f0263a27caa010917169b91952820d1a46a4b4f54357fda87463a5c866ce6afe7d9b23eefe2e173d737fe492470fa481ab883e0a82b86637364dcdc03c47318423f7f512839510ad64b82dd392b5d0c6acc2b824a9727a7717819dd8aba714e2eb11bf32348c632f06d66ab968e070ee328e357c90db353536eb2a2edca946abafd2f26b6f9ad6096fb44894d5a4d50e573d9c1c2d0fb3bfa5ec202c581b1cc08fd7594ebe6892ce53457fd7268bac26471341a438f811c9130d5397d61a98351e79d64ca387ede4e93eeaced4a56528a04f1299d9086887b2c726eb504e111af2445ed0aa76363a0ee535be1dd027a7adb43426caa0d90cac9116b5c36d120430ca5dae1c98644b72c6f847d74a0e71ae69604a4a4bab5628fdac4f92aaa86c70c4533334b366a751f23473cf65616aea0661fdbee4b9d3f2cb82578f44703e8ec514a72ce9b299e617d37106dd23f4c53dbe27780dcdb0312007fb60c2d43b3257ac99e75b71cbb611ebfc72c1fb724b15a8fe58b2ddcc2a5cd976a15ce41b4dd09cb9d09054e835d6d60b0172bf904074853ca9c6613c0fae8f792586238416b054c8710fd387e4d46c229a6d0d8f9b653bac5386284710668bb3fca857163f6db48e0420f0087dcb0dedde46fe29a90641b6c3c49db5d351a267305eeaea95c6e62e16a91917b7816972524fea118f26bf851baf069b8ecf2081cf91451d697be0b6887bccf0a3b67c96d44d1bff69cef9546d08e8dd66cb7417e7a2723440a6b6442c2a58bece8cba21ab9f197c3d0bc74c781e30efd25cf2542639ac3acfabdea3f7af221ad28c797930d368076cbcd112c0a1bf198d0c081d7f7f216889bcb5b446d720fa7f1333fba0441216568b55b9e3a869c47afaabdeb282b65c774c98db3d00c00406276b12d86c44f8dd8970bd7dc738bbe1ad712a10137ed87968c0f0c8afc53ba3f9a070e700dcd22b47340d17c2c7b2f1cd2039c89334e722e25b7d619c8319a6727f4b68279c64538ce4c03318853f4e1944b8d81d8b8007aca927ffbc02e69a77c45d2632a7c0afcaa90f5e6f2736f58bc304dd1170f685e8b47fccaded0661255d6ab8a290e4a8c44dedcf3c9660a7d35f040f0d63e14bde335b7968d5ed6e984a892b0a6a8139d96eb54a8d1022e8db082e062e0c252c3e01d6403afab8aaf6bc6f24d8157ed6cecad155e259ad5b018db69ccc08c0f15811ea78917fb72ad9e9573c7645fcf59cfa531b078ac8fe5b51efb61d8c1343ec3d890f34d6095d6000f0be39e9d0dfeb532f2708168b75f28ceddb56aa4581da4d5aca0b39a456ea7b8e43e883c9d3b12d51c0826ccb69d1e77956afd2adfc115a1e44ad78e68c2b93b61d71e0ddd4cdaaa7fd3589a1b9d2a42df325724d181b7c96af26643d47603af1289ede8f54c634ddb4d82a5829000182e20381e802202f151b4c0c83a349d0df400c799410b60872287923ed67236c6fded247435711d82a5828000181e2039220200b139cb28169e7bf96a3e893f06114798319b67738e3f1d3e25c15251fc05809000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x31d48a6", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x2fcb2ae181befb6e530f111ad25c983e1d2e208d11caa73145c811266bdf34bf", + "transactionPosition": 98 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000194d9c", + "to": "0xff0000000000000000000000000000000018b644", + "gas": "0x420cf8b", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782197cae590780af74af52a5e7cfc11112a58658f3464000126bb0bdbe9cac881aab7373a5b01444a96ce7cac4da6cab7931ad1302d103ad1a909beafb5e6da3a12dc834107ad467b58db1d330b54187c5e76133ce0dfae5286e16dc77f21200b431311385ccae08f31082cb5fe315e2f03763339b2a8e7f5d4aa22394ab9b1bd2e5a2ee9f457878d7922cc963fb7c9df63dac57bd3bd1b4e474c042691e7691229c7832cd385e5dcf57d63f89f56b417d6a0454f6f7ebe10ddafec855583091e9f0a182fc8efeb873b773a6a2a5ec2f78424c1b274d629653ab7f501ca8e153b5bd133bfbd4875b708ab19a70dba7ade20d6c711e228a96a87a7b889c690cab7afe89b65bf75a1320383fe2575e886a9f2e540c73c683ebcf237a7e9249e1e79897026f5410211837ebfbd01106ada77feb2fddd1594668877f93c92007f819e2958174cfee14c3a9f19e0cb82184ca9a0bcbad69dc7fb54baed0248d1ab6523a03a483b8894ff90a82f720b05cda51ba3473d78070ef62dfcb4db8381b8bf00fce3b1e47c599b29c4e4f3d3537cf32cb0b414a43b05ecac3008bee1e41f7b0e434330b144ca8943d0eb355a21800b489c8747a8df65799ce85c5278b8af057ecaa0094ecc3b7125326e168bb9e3a6840305c138f04e8ac6c9833050e67d2f5b5bae0b82e704f0e478ad5e249af7c71e5f813f4e2762789e22db59bd998a600a96f12ebb4cccbbc0167250e5068db6097b417820193ffb61b14ed1decd0a4c4ce4aa22e0a4b4345b112cc70fbe58e39b55d8658d0293f2e0553fb9296203aab2a8e3921bf12e3abc25b8a68a82b8a32f780d643706c2fdd95aa83e0f80a1148ecdeee6d82f68b7d0970d26535d0f8a45ecbe83ff1736890aee1ca3b2c3c16c04b7a79be725e27a83d2a650c953c7618359ff8248c341387fe194553c7aa2d7c36b90427b78bb9011137de00fa755df244a478ef86182e1154bcb9704693d2a4b36bbef19817ca6fba18738f3ab605231ca40458590ce8a25215e86e4fded86922730312c0e1a9fce06819f308e197e58cc9df7505ef779f011945960debf96779a0d3b4853865953aac108b3d10cbece20950a0612147e65edc8a59aaf7c8ca518f9fdb452b316f4ebf3fd18664b692cf0195b2f66467b9b8c398e118c1e90d45be22969e12a226d9c51d1f5e437de700096f7bca6b5b52b72ea04b89828b0d5d2f2349b11e35150b53c844616524804b13ff6e50bcc2cee62a91bac6c97ef5601a4c6d26b70e3ae78c18b41560995ed358bf8bce8a40aa30218576eab0068c6f4d7a9f16524830fe8418d98fe8475a1ca81174b85c84e70944a47cb840aab45b22036cf0a428b3f42ae6f5b44a9f9cf8a8e160659c887db1eb574690f261328c2d7d61d77e2571823a0e071b541b25779deda2b99aee9712bb5f78d30b6924e67a466cf6e5cee885fd7dd1cbe54e19a0acce5a4ec125b4191402fec6f7cd9e6b74f693b6e1a11552aa1d0c74db96db59cecd8165540ed97de16ae68161ffd0004e46e94db01f6e210ba0befbcc545c75f26f63f6a5d4b4c3390118c4f9d250a816bb83b162ae29dfe7f43161f5383e6a6514a1dfe089af68a6050d8628af0ae8ed721ac2eaf797d0a8291139c8c4f0803ad4d1e1476d641f65ce498b78f28e779aaa3b509105e295b178789c0a88a002a698c32d6500900bc46da2152ba8b48e1adb116603a56ac12af02a647547f6bab816ce8b90909c28b88be764c8534344fea5b4a98d6d13427c0a1a8b3479fd6ab5fcd298c5bbbd53073ac3162a1d71573b03bed5fac16fa8180dbd516bdeb05f8194fba24014950634092426f4a3894967e29fa3911776d59c2c96fc592c73e90542ae654a087a5773045e35dabf4607f78cd01df0c2b14687a7f041dfb54ea9b2edc2e13c91df1d99fab8354d8ac2d7397c332c05573deb2482054e35f750e6b36c337abf0292a2b7dacc7ca94a1b8aae9f2d2355a79fd807e28fa6b7a05ce0bf4246422361a8f92597f47ac6d0536fd34a0e43ce101097d99bb0ca17255046025305e2847ed242c64e86e131b71de29848e8a731dbbf72fc21df1ef705064a4fec6e8dcc64a2e80de3013a9969563667fa312044c74bb6fe0b548f01a00434d80d3f16ee2f0023995c758050f2023e44cdc2d409b98ca630e47e0889e322adff03315a959e517b3ea1bec48e754c7507ffcb6aab665673033a950ea575006f67fe715a380f8c157d40b5a8cb1c9feca96555c1993672a737f13a375700ff5402e77440163eb33d751e7395011d6ec56bad25a7471a154199067a029e6a456b64728fb5551c75d578e6fa7d4c208a5d5121c8068ccbad10d37097f4eaa65ab1fe0aaaf4a246a938d6532465c9ee33568fc97829bc3e8ebe41006890cea32107e374d877f5145541d6c42c9565835f304b072dd680f7b28e0636c37c77c83cf94c7995ed2dd6e3de65145100e31530df58cf3c033e273d8dd958d04c24b0b4d025dddd658c91ac7d9a533d5cfdbd053b222df4fbfbef900da6a930ae232efa070958c210dcc655d513504248ed405924bc76e35ccdba1787732ff9f074899c01a79af782c9ad343869a7b3cde3812d6c51d453469a99f81b1eaca9e1eb1cf672dff039f9790cb72cfc70e23b1810a2ff1fad6f18e0bd7e45b7669f206444383f572c31555dc9aade67df6a23e7f458b47041d2185d4300000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x6c75d9", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x0e60536ae59f9e7056602af5758917ceb7b122029a30d36b81f755578da9b6f9", + "transactionPosition": 99 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff0000000000000000000000000000000018b644", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x3e5247f", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000082c8808821a0018b644197cae805820459182f6c450d64f4ce1b2cdd1f1b72010f8d595fd9303f5cea43b716af1082a58203982169b56bf07db8724ad2c00f8855d497d4eee8ca4ad3562991728ee37cfd7590780af74af52a5e7cfc11112a58658f3464000126bb0bdbe9cac881aab7373a5b01444a96ce7cac4da6cab7931ad1302d103ad1a909beafb5e6da3a12dc834107ad467b58db1d330b54187c5e76133ce0dfae5286e16dc77f21200b431311385ccae08f31082cb5fe315e2f03763339b2a8e7f5d4aa22394ab9b1bd2e5a2ee9f457878d7922cc963fb7c9df63dac57bd3bd1b4e474c042691e7691229c7832cd385e5dcf57d63f89f56b417d6a0454f6f7ebe10ddafec855583091e9f0a182fc8efeb873b773a6a2a5ec2f78424c1b274d629653ab7f501ca8e153b5bd133bfbd4875b708ab19a70dba7ade20d6c711e228a96a87a7b889c690cab7afe89b65bf75a1320383fe2575e886a9f2e540c73c683ebcf237a7e9249e1e79897026f5410211837ebfbd01106ada77feb2fddd1594668877f93c92007f819e2958174cfee14c3a9f19e0cb82184ca9a0bcbad69dc7fb54baed0248d1ab6523a03a483b8894ff90a82f720b05cda51ba3473d78070ef62dfcb4db8381b8bf00fce3b1e47c599b29c4e4f3d3537cf32cb0b414a43b05ecac3008bee1e41f7b0e434330b144ca8943d0eb355a21800b489c8747a8df65799ce85c5278b8af057ecaa0094ecc3b7125326e168bb9e3a6840305c138f04e8ac6c9833050e67d2f5b5bae0b82e704f0e478ad5e249af7c71e5f813f4e2762789e22db59bd998a600a96f12ebb4cccbbc0167250e5068db6097b417820193ffb61b14ed1decd0a4c4ce4aa22e0a4b4345b112cc70fbe58e39b55d8658d0293f2e0553fb9296203aab2a8e3921bf12e3abc25b8a68a82b8a32f780d643706c2fdd95aa83e0f80a1148ecdeee6d82f68b7d0970d26535d0f8a45ecbe83ff1736890aee1ca3b2c3c16c04b7a79be725e27a83d2a650c953c7618359ff8248c341387fe194553c7aa2d7c36b90427b78bb9011137de00fa755df244a478ef86182e1154bcb9704693d2a4b36bbef19817ca6fba18738f3ab605231ca40458590ce8a25215e86e4fded86922730312c0e1a9fce06819f308e197e58cc9df7505ef779f011945960debf96779a0d3b4853865953aac108b3d10cbece20950a0612147e65edc8a59aaf7c8ca518f9fdb452b316f4ebf3fd18664b692cf0195b2f66467b9b8c398e118c1e90d45be22969e12a226d9c51d1f5e437de700096f7bca6b5b52b72ea04b89828b0d5d2f2349b11e35150b53c844616524804b13ff6e50bcc2cee62a91bac6c97ef5601a4c6d26b70e3ae78c18b41560995ed358bf8bce8a40aa30218576eab0068c6f4d7a9f16524830fe8418d98fe8475a1ca81174b85c84e70944a47cb840aab45b22036cf0a428b3f42ae6f5b44a9f9cf8a8e160659c887db1eb574690f261328c2d7d61d77e2571823a0e071b541b25779deda2b99aee9712bb5f78d30b6924e67a466cf6e5cee885fd7dd1cbe54e19a0acce5a4ec125b4191402fec6f7cd9e6b74f693b6e1a11552aa1d0c74db96db59cecd8165540ed97de16ae68161ffd0004e46e94db01f6e210ba0befbcc545c75f26f63f6a5d4b4c3390118c4f9d250a816bb83b162ae29dfe7f43161f5383e6a6514a1dfe089af68a6050d8628af0ae8ed721ac2eaf797d0a8291139c8c4f0803ad4d1e1476d641f65ce498b78f28e779aaa3b509105e295b178789c0a88a002a698c32d6500900bc46da2152ba8b48e1adb116603a56ac12af02a647547f6bab816ce8b90909c28b88be764c8534344fea5b4a98d6d13427c0a1a8b3479fd6ab5fcd298c5bbbd53073ac3162a1d71573b03bed5fac16fa8180dbd516bdeb05f8194fba24014950634092426f4a3894967e29fa3911776d59c2c96fc592c73e90542ae654a087a5773045e35dabf4607f78cd01df0c2b14687a7f041dfb54ea9b2edc2e13c91df1d99fab8354d8ac2d7397c332c05573deb2482054e35f750e6b36c337abf0292a2b7dacc7ca94a1b8aae9f2d2355a79fd807e28fa6b7a05ce0bf4246422361a8f92597f47ac6d0536fd34a0e43ce101097d99bb0ca17255046025305e2847ed242c64e86e131b71de29848e8a731dbbf72fc21df1ef705064a4fec6e8dcc64a2e80de3013a9969563667fa312044c74bb6fe0b548f01a00434d80d3f16ee2f0023995c758050f2023e44cdc2d409b98ca630e47e0889e322adff03315a959e517b3ea1bec48e754c7507ffcb6aab665673033a950ea575006f67fe715a380f8c157d40b5a8cb1c9feca96555c1993672a737f13a375700ff5402e77440163eb33d751e7395011d6ec56bad25a7471a154199067a029e6a456b64728fb5551c75d578e6fa7d4c208a5d5121c8068ccbad10d37097f4eaa65ab1fe0aaaf4a246a938d6532465c9ee33568fc97829bc3e8ebe41006890cea32107e374d877f5145541d6c42c9565835f304b072dd680f7b28e0636c37c77c83cf94c7995ed2dd6e3de65145100e31530df58cf3c033e273d8dd958d04c24b0b4d025dddd658c91ac7d9a533d5cfdbd053b222df4fbfbef900da6a930ae232efa070958c210dcc655d513504248ed405924bc76e35ccdba1787732ff9f074899c01a79af782c9ad343869a7b3cde3812d6c51d453469a99f81b1eaca9e1eb1cf672dff039f9790cb72cfc70e23b1810a2ff1fad6f18e0bd7e45b7669f206444383f572c31555dc9aade67df6a23e7f458b47041d2185d43d82a5829000182e20381e802200b3169218eaabe0a254cadd20d2eac54c4e57c1839c602b8ed7a1fb2bcfa0e5fd82a5828000181e203922020077e5fde35c50a9303a55009e3498a4ebedff39c42b710b730d8ec7ac7afa63e0000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x31f607f", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x0e60536ae59f9e7056602af5758917ceb7b122029a30d36b81f755578da9b6f9", + "transactionPosition": 99 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002d44e1", + "to": "0xff00000000000000000000000000000000116ff5", + "gas": "0x501f15", + "value": "0xd015638ef2350000", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x12c553", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xdf41cbcc8c905708453c4636c5b0ebf709ca0464982158a996000a244a3dcd13", + "transactionPosition": 100 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000241537", + "to": "0xff000000000000000000000000000000002d44d5", + "gas": "0xbbacb", + "value": "0xce8869a46a946e435", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x12c03f", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xd0e4426b5949939e81e1aa223c77a6d1fa37d7687e32f319ad9dbfcf608128e6", + "transactionPosition": 101 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0x53bdfdea92f7a60aef82228926d02878018acb4e", + "to": "0x8460766edc62b525fc1fa4d628fc79229dc73031", + "gas": "0x6b64a5", + "value": "0x0", + "input": "0x5535dbf60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003b6261667962656965677273376934636a6e6a767072666a356d67653367747076767a6261796e3672657a6b6433666a36746a6a70797763707a68690000000000" + }, + "result": { + "gasUsed": "0x6143fc", + "output": "0x000000000000000000000000000000000000000000000000000000000000043e" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xf73074b893cce66d2f798bc0862e12501da242db7004d3bd4aab36d7e0fcbc91", + "transactionPosition": 102 + }, + { + "type": "call", + "subtraces": 1, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000172b9c", + "to": "0xff00000000000000000000000000000000172b21", + "gas": "0x5205362", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a000a651a5907808fd515f991b4f6a33de58875255e78be7d80628951a6f23f375280c3ec01e5aac19d252b2f8ddbe279ddb5758c96dea3b5be4a3f34581d5eba0e152cf8e649c5c272759b0b268aaaf09336e5ef9a9d0eea3b4d6e346f16370709411194f1274f03d6160a838e2d85e954e0070d434f05033a791cd1e0c149df1dab881b11423d43851c40be685faca80d7cc78959beb3975f8c49164ca96c11a2ede913c5da8cf168bd6407080e794bbac843e44f84c57b987103da83c6a9761c4dc00dbfa072b64ba1914e5588853cc3975f3463023dc27f04d6294ccf24b512c371066f3dc102db9c89172683d405c605e81488a03184c0b2996e963bc2297813598f82fa0ebdf4a13e95284e8807ffbb81573b8e73d495d18a53b9a25e39e8ccf36a50078101f9cbb2c68ad953c9e98e99dea30d8f65b821286709adcd4b99754a696e4f782e8715ac7458dda55863e46c87c10365a36bad7db606b42f49dad977252ba8fc2b615a9a68b6e4c5db36af1959788c9c01f6c42e30aec6b9618d475723feee7985d8d8a0119154f3a0cc0b44cfa94acb31e8d34a1eb9db44d389f5e6b840c80997fa17428581ce07cebe0e5a72a57d8b8df76fe7162c5fcd22771f6f39e7f09c420c298016be276d70715ad584b7e27545258c0c082f9df2a642e3f27b85db410aa8cd1e46b323b673d27f93c0aa1508e387d86a474565af8540c5912201af661ad35a7ccf0b7557d3047e6c467afce9b99ef937a026f17c63b6c56cfa2c075555e687959e1ac7d4ef164401b2dab427c4b8925eb4961db305123f932d15517b91d87218e46cad09696eb1b70f82d588c8d52d920ed13bf990f469bbe15e95c04402738842f4690e0d6963eaca9a214e9539f274424752c6855cd172f134fee67c397179ebabc8c67d87ac3862e9cb38b609aadfc476aa45acefeeaf1dde8e1d12cf3be18a24904f5261c36912844abbc6f986be2c730731556cb83ba4fb0a2e1c1f61836b805b0b404ef458aaee2ec5901f50c87c245772d68a062988be6c0178910957cd03a46d9d60cb3c6522a03e84fa2c4e752bffdf0faa82a1fe17374287456fedd11dfd4ea22163d1b3e0d4bd7c84ee73f63f8f6511ecd3297591ceb9bb59a6a52ca4d9316fa852ca949b496e8bc3ababaa8256ce13c413ff4c863fac0abb195ed523fc7ac26750864b33dcbfeb53febbd388ce3cf2cc541d9be71a8f0aa7ea56e806597ca6d08a4b763a68a1d8be498a1bec737872fe8ef18db169071932627a8f9192a498d2cb6a73ac23b3b88a0b1d3bfa271ce50abbe7d1b60cc1b9c0525b7d39daa328b854390c1538a69b6e73168143378d540cf1f11236bf4896da263ea751fe44d06031f0d65d52c735cfe0cbe82809adc1e09691e75a14cfb7c6e8f120544525c0ead12a3471bcfe8bf9aa3555c13c12a07ae2aae1ddf970b9cf714cfad51ff458228e051e00f2b46e1ef86870b6085b8e5820aaac1543cf1348d1fe21bb39dbe6352b52641688b2c2c666c8eba27394edcfe1500633a623ec4e65157ce59cbba97b8dc75694f238b9d980e93e579cc5b3c11a357b5158cbe89dd04a61397cd88524ba05daa6fbdf592ccb92821b05a5a09cd214a6c11162b1b0b250d1556573222dad8504cdf1dfa81b83b4456ec6ed7424597f602d6aab1641fa1e109d1e5ed934780e8f95a5ca829a4877cc3ec49e7bd5abb9ca0ff0af71ef2dd252824457b87f9d5e54dcc5443d8de0490d16517332458d89e25582890016f96e20bbbd6d81b9846d168eed1b6bffa6dabad8df76e144afddb8b5bde341ce94654ee214f7dca3d8ea5a5b925cb3749a3350f700d62380b187aa7e0279fe528d3d752bb741ba9e973d00720302f3ad688c7d538d1d968691578e29ea1bad131aaf34192c96e9753768f80b2ff98bbcb4123ff4bb8023aaf6d8e548c0d834a4d351da4ad560be24ab0a9bc72119a9bf2a8d971a7bf5ebd463410c6eaeb7398b8d563354b54ee99274701b0efcae547875679f7fc9acd9695f569f2d88c51849ad90aaa9c92a5884ccfd8b70c4e95c881d759fb98e0a003b1ad27fa629745a708507d6dddfa415c4e882e960e319960601751a6b4b3dbf9d5c735c72a32d711de64eadaa44becb4230fbb737f5363b3b2cee9b64036872356012cb66ad34b06663639d792681d5aca663e80a103529c6a23e2a229f5ea3bb33651a023c0d8503d3c24feddc4182830fccd14176088ef4c363b154e4d2bb4323469831489e33496294cabc1f764ee81beb08d1995ad6fea8e474bdd2ae988b2e5dfc2508340b9db7a7ad87549061e3cb90a167f894d1a11642920b20afdd87278191e680b07a5c84cab915974e042ec97af0c0ec8eb728f9dc062a0e5b9eaf8bd9aef74e29b0a8ebd6a179cbceadfde17e47a397cc96e46f25f6e2634a1a8d3b9a17b5e36b8560f6c8f508f2ef9eeafb5497300cc7f8a459675f4136f042f92a05a75ba8e9c885d51202888170173b04568d187a18914c246f6359938928a1e799e02f0580fac0c7c0271270c47db574362902cbab47f4ef90f0fe2f25c3aefcbe255abd1915edec356e67835397832bd854d8b191f664e1e721960ee3e4857974a6762f13ae7528366986ad0eaab18e53da543e6eb480782f5bb0932a1a6347dbbdb5d939cf4eb26bbbc1fe6ca38bb130f0aab1fd10dfac48fc5e165a16465840c33ed8e70000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x9b4f05", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x0bd64342ca2f272d82c6a77786e961b5fb1983158a496af3aeffca02bd7fce6a", + "transactionPosition": 103 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff00000000000000000000000000000000172b21", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x4b6bf10", + "value": "0x0", + "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008338808821a00172b211a000a651a811a045b2dc45820c84705285a78a4839d7dd9614c60acc16c72e827794f991eccaf716f0cff6ed95820fe634357a254919411f0fa829564b42326cc3ba9d7f50492d56d3a19b48234e85907808fd515f991b4f6a33de58875255e78be7d80628951a6f23f375280c3ec01e5aac19d252b2f8ddbe279ddb5758c96dea3b5be4a3f34581d5eba0e152cf8e649c5c272759b0b268aaaf09336e5ef9a9d0eea3b4d6e346f16370709411194f1274f03d6160a838e2d85e954e0070d434f05033a791cd1e0c149df1dab881b11423d43851c40be685faca80d7cc78959beb3975f8c49164ca96c11a2ede913c5da8cf168bd6407080e794bbac843e44f84c57b987103da83c6a9761c4dc00dbfa072b64ba1914e5588853cc3975f3463023dc27f04d6294ccf24b512c371066f3dc102db9c89172683d405c605e81488a03184c0b2996e963bc2297813598f82fa0ebdf4a13e95284e8807ffbb81573b8e73d495d18a53b9a25e39e8ccf36a50078101f9cbb2c68ad953c9e98e99dea30d8f65b821286709adcd4b99754a696e4f782e8715ac7458dda55863e46c87c10365a36bad7db606b42f49dad977252ba8fc2b615a9a68b6e4c5db36af1959788c9c01f6c42e30aec6b9618d475723feee7985d8d8a0119154f3a0cc0b44cfa94acb31e8d34a1eb9db44d389f5e6b840c80997fa17428581ce07cebe0e5a72a57d8b8df76fe7162c5fcd22771f6f39e7f09c420c298016be276d70715ad584b7e27545258c0c082f9df2a642e3f27b85db410aa8cd1e46b323b673d27f93c0aa1508e387d86a474565af8540c5912201af661ad35a7ccf0b7557d3047e6c467afce9b99ef937a026f17c63b6c56cfa2c075555e687959e1ac7d4ef164401b2dab427c4b8925eb4961db305123f932d15517b91d87218e46cad09696eb1b70f82d588c8d52d920ed13bf990f469bbe15e95c04402738842f4690e0d6963eaca9a214e9539f274424752c6855cd172f134fee67c397179ebabc8c67d87ac3862e9cb38b609aadfc476aa45acefeeaf1dde8e1d12cf3be18a24904f5261c36912844abbc6f986be2c730731556cb83ba4fb0a2e1c1f61836b805b0b404ef458aaee2ec5901f50c87c245772d68a062988be6c0178910957cd03a46d9d60cb3c6522a03e84fa2c4e752bffdf0faa82a1fe17374287456fedd11dfd4ea22163d1b3e0d4bd7c84ee73f63f8f6511ecd3297591ceb9bb59a6a52ca4d9316fa852ca949b496e8bc3ababaa8256ce13c413ff4c863fac0abb195ed523fc7ac26750864b33dcbfeb53febbd388ce3cf2cc541d9be71a8f0aa7ea56e806597ca6d08a4b763a68a1d8be498a1bec737872fe8ef18db169071932627a8f9192a498d2cb6a73ac23b3b88a0b1d3bfa271ce50abbe7d1b60cc1b9c0525b7d39daa328b854390c1538a69b6e73168143378d540cf1f11236bf4896da263ea751fe44d06031f0d65d52c735cfe0cbe82809adc1e09691e75a14cfb7c6e8f120544525c0ead12a3471bcfe8bf9aa3555c13c12a07ae2aae1ddf970b9cf714cfad51ff458228e051e00f2b46e1ef86870b6085b8e5820aaac1543cf1348d1fe21bb39dbe6352b52641688b2c2c666c8eba27394edcfe1500633a623ec4e65157ce59cbba97b8dc75694f238b9d980e93e579cc5b3c11a357b5158cbe89dd04a61397cd88524ba05daa6fbdf592ccb92821b05a5a09cd214a6c11162b1b0b250d1556573222dad8504cdf1dfa81b83b4456ec6ed7424597f602d6aab1641fa1e109d1e5ed934780e8f95a5ca829a4877cc3ec49e7bd5abb9ca0ff0af71ef2dd252824457b87f9d5e54dcc5443d8de0490d16517332458d89e25582890016f96e20bbbd6d81b9846d168eed1b6bffa6dabad8df76e144afddb8b5bde341ce94654ee214f7dca3d8ea5a5b925cb3749a3350f700d62380b187aa7e0279fe528d3d752bb741ba9e973d00720302f3ad688c7d538d1d968691578e29ea1bad131aaf34192c96e9753768f80b2ff98bbcb4123ff4bb8023aaf6d8e548c0d834a4d351da4ad560be24ab0a9bc72119a9bf2a8d971a7bf5ebd463410c6eaeb7398b8d563354b54ee99274701b0efcae547875679f7fc9acd9695f569f2d88c51849ad90aaa9c92a5884ccfd8b70c4e95c881d759fb98e0a003b1ad27fa629745a708507d6dddfa415c4e882e960e319960601751a6b4b3dbf9d5c735c72a32d711de64eadaa44becb4230fbb737f5363b3b2cee9b64036872356012cb66ad34b06663639d792681d5aca663e80a103529c6a23e2a229f5ea3bb33651a023c0d8503d3c24feddc4182830fccd14176088ef4c363b154e4d2bb4323469831489e33496294cabc1f764ee81beb08d1995ad6fea8e474bdd2ae988b2e5dfc2508340b9db7a7ad87549061e3cb90a167f894d1a11642920b20afdd87278191e680b07a5c84cab915974e042ec97af0c0ec8eb728f9dc062a0e5b9eaf8bd9aef74e29b0a8ebd6a179cbceadfde17e47a397cc96e46f25f6e2634a1a8d3b9a17b5e36b8560f6c8f508f2ef9eeafb5497300cc7f8a459675f4136f042f92a05a75ba8e9c885d51202888170173b04568d187a18914c246f6359938928a1e799e02f0580fac0c7c0271270c47db574362902cbab47f4ef90f0fe2f25c3aefcbe255abd1915edec356e67835397832bd854d8b191f664e1e721960ee3e4857974a6762f13ae7528366986ad0eaab18e53da543e6eb480782f5bb0932a1a6347dbbdb5d939cf4eb26bbbc1fe6ca38bb130f0aab1fd10dfac48fc5e165a16465840c33ed8e7d82a5829000182e20381e802209cefc42ae516244f0407001eccfd20d923218826471acf64a8a94d665ae51b2dd82a5828000181e20392202078d7f3d6cd9ba40272efa201f6dfb13dc5fd85d8cb3812bd5af6a23f0204c02e00000000000000000000000000" + }, + "result": { + "gasUsed": "0x32089f0", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0x0bd64342ca2f272d82c6a77786e961b5fb1983158a496af3aeffca02bd7fce6a", + "transactionPosition": 103 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002af885", + "to": "0xff000000000000000000000000000000002afa9a", + "gas": "0x6aa2041", + "value": "0x8abb7a55e7bee0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000072818187081a00019146d82a5829000182e20381e802202690c7f394488d63c61b511be885dd34412491e418f3022c5f2a70594a45da401a0037dcf0811a045adba51a004f7977d82a5828000181e2039220205fd60a468ccf211022611f407c8898cc32d838d4da91777a3eba380889390b3e0000000000000000000000000000" + }, + "result": { + "gasUsed": "0x4f77363", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xda7c7b60dc15d026b250310e1b2db33b40e00542386f18c560612ebae86ff301", + "transactionPosition": 104 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002afa9a", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x6976309", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xda7c7b60dc15d026b250310e1b2db33b40e00542386f18c560612ebae86ff301", + "transactionPosition": 104 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002afa9a", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x6869dc1", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xda7c7b60dc15d026b250310e1b2db33b40e00542386f18c560612ebae86ff301", + "transactionPosition": 104 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002afa9a", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x6748850", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f7977811a045adba50000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x487d76", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e2039220205fd60a468ccf211022611f407c8898cc32d838d4da91777a3eba380889390b3e000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xda7c7b60dc15d026b250310e1b2db33b40e00542386f18c560612ebae86ff301", + "transactionPosition": 104 + }, + { + "type": "call", + "subtraces": 3, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ade3c", + "to": "0xff000000000000000000000000000000002ade40", + "gas": "0x375dc86", + "value": "0x8abb7a55e7bee0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708192040d82a5829000182e20381e80220359da1ae33a951238ad1c46d5a5a8af8728e9f3dfb8c420a3c6989e1d451dc351a0037e08c811a045b324e1a004f084ed82a5828000181e203922020d07f551de5c94f8a5e6772d7a22d34450b43bfda443f5a059e35ede55ec0593100000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x268464a", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xbf0919237db95ca6842af1209bd67f3c15587a92fb9f7f1343dd4e66c5828a06", + "transactionPosition": 105 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ade40", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x3631f77", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xbf0919237db95ca6842af1209bd67f3c15587a92fb9f7f1343dd4e66c5828a06", + "transactionPosition": 105 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ade40", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x3525a2f", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xbf0919237db95ca6842af1209bd67f3c15587a92fb9f7f1343dd4e66c5828a06", + "transactionPosition": 105 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 2 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ade40", + "to": "0xff00000000000000000000000000000000000005", + "gas": "0x34044be", + "value": "0x0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f084e811a045b324e0000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x476423", + "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020d07f551de5c94f8a5e6772d7a22d34450b43bfda443f5a059e35ede55ec05931000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xbf0919237db95ca6842af1209bd67f3c15587a92fb9f7f1343dd4e66c5828a06", + "transactionPosition": 105 + }, + { + "type": "call", + "subtraces": 2, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce88b", + "to": "0xff000000000000000000000000000000002ce88f", + "gas": "0x1f6eaed", + "value": "0x8abb7a55e7bee0", + "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004081818708190491d82a5829000182e20381e80220e679eb60d986e754a677a0b5219976140fff0f798521f46d166a7e597b7c20211a0037e09f801a004f8a90f6" + }, + "result": { + "gasUsed": "0x17c8c83", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xb9208ab347ea30b568c4f9ab4fe310eeacfa107e9e5d970b38517334e51b2b2e", + "transactionPosition": 106 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 0 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce88f", + "to": "0xff00000000000000000000000000000000000002", + "gas": "0x1e45019", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0xfabb0", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xb9208ab347ea30b568c4f9ab4fe310eeacfa107e9e5d970b38517334e51b2b2e", + "transactionPosition": 106 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [ + 1 + ], + "action": { + "callType": "call", + "from": "0xff000000000000000000000000000000002ce88f", + "to": "0xff00000000000000000000000000000000000004", + "gas": "0x1d38ad1", + "value": "0x0", + "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result": { + "gasUsed": "0x105fb2", + "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xb9208ab347ea30b568c4f9ab4fe310eeacfa107e9e5d970b38517334e51b2b2e", + "transactionPosition": 106 + }, + { + "type": "call", + "subtraces": 0, + "traceAddress": [], + "action": { + "callType": "call", + "from": "0x4ecdc893beb09121e4f5cbba469d33f5ff618442", + "to": "0x8460766edc62b525fc1fa4d628fc79229dc73031", + "gas": "0x2aac9395", + "value": "0x0", + "input": "0x603119b1000000000000000000000000000000000000000000000000000000000000043d0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000580000000000000000000000000000000000000000000000000000000000000002bc0000000000000000000000008908d75fa9b38aff2997fbb405ecea53ea376235000000000000000000000000066ce586cee61c33b6a7cb88b424f2597b03fdd200000000000000000000000054b19c8921b4b9b7bfc63a7c1267b8b361edcb37000000000000000000000000e5e0ff05a5cbc02fd0b386cfd626d3c537e49103000000000000000000000000ce53e6defbd1f0810bfcd691660ad311d0db24f7000000000000000000000000e6235c47b9aeb4309ad568945dfda530ba3ea4a6000000000000000000000000d93910f8dcbcbf07e86a0b3235d47f04b9b0a8a5000000000000000000000000280f1cee998b784d87f39047f5c757783a131bcc000000000000000000000000ea0fd4a321a9f1fc06e4d46a9e991ebe91302a35000000000000000000000000b0a258e65801e52ad9b709a06af61282194360790000000000000000000000008ad34724ea8c951cb0fefc1cb3b1184bc802f2ad000000000000000000000000d096dbb2a8eec6ae3f1baf9ce6e46ef4e1e0989800000000000000000000000018be4742d88664350e0333af9b493afa77b3b71c00000000000000000000000069eb6aab03510f6d90b206a5cc3e9f7b92349e390000000000000000000000008f3b852c5394ad9618c4fffc0db895e8570033740000000000000000000000004e89c37b3f7d9fdc01075f252c4c9c0e8e1db6e80000000000000000000000002379198a71e2a3cb94e78628a8ebaceee1c0c6480000000000000000000000007f2645e86dbf5fd6fcf613073bd795d0aa8546f5000000000000000000000000137536822037b7fb2e78eeb824af708e1aeeacb2000000000000000000000000aa60b7c819c4fe893e4f24a81ec91b4a22cb526c00000000000000000000000062ef4551567eada4d52c719f810bf27ba90136c800000000000000000000000043457a112c3065f05ac9fba437d184972aa12b03000000000000000000000000dad969443185072d2e1f657aed34c8d970b3bb370000000000000000000000004a64d6b9ef098ed25fcc47a537bb483e2963d0860000000000000000000000005700c1928f7e9cf7af2205de589bf12263db6530000000000000000000000000a31688aeca874dfcb51c26d4adef91bd18f09490000000000000000000000000a6de99edc8a11f761eaadd5b646d2cde410bfb290000000000000000000000000a4979051b9bfe1bc19be8f7793f7a7858292f3e0000000000000000000000001aba27436b6add4de90b81c813764c8a7ebf79210000000000000000000000009e36e3d36b8811be651a9bc7b6557dc76cf43d47000000000000000000000000d472d30a5f14cfe9e489e32b670c11f1b658d28a000000000000000000000000449121e6099b23e3064056106841ebcb5d9f82e30000000000000000000000009872ae4277420bb1cc0c543f63933c044b4aaccf00000000000000000000000098b622ceeac4f1b0118d1ad9dd8ffff809f2075e000000000000000000000000e5d713f13538289634ddfedbfa06fc9180d928960000000000000000000000008bf19b7bd21c6e1b1c562a5dc72b1222de467d9c000000000000000000000000e6badb95e881d2fbef09fbaf07fb5ba12dc6fa670000000000000000000000008dda03750aeaf62032b769ec0441268ebc716d9f000000000000000000000000a547820f60ce4e1133f69eafdd6ef8e2a0d2bb6200000000000000000000000041311a5cb9430a255e91306c0a2134a09c723f76000000000000000000000000d4ed562aefba750c3086b468f19e99848672c9160000000000000000000000005a45f28aeef32da96db5b6c435081a677bdfe938000000000000000000000000ccd5ac13bf889d8e87bc726403cd354fe5e0331b0000000000000000000000007bd4557c830d1b55877aefb30c7bd85dd833d938000000000000000000000000cb0a53f1e7f20a5b4d983ea043989f895daa5e07000000000000000000000000114238b63efd246c18dbabaf0992b5e6c60e5cb2000000000000000000000000be7217c9e2daa6b5401baa8e7e7a79cb4ea873fc0000000000000000000000000f9269499c33a7ba0e23082f058fc1d8c116ee640000000000000000000000006366606f86ae38cd817f3688d49efaf79909409c00000000000000000000000060697cc054ac2e09b82952783b943f98fe75054a0000000000000000000000005d48ebd30cb6f1b3c901764df6472266cebc44eb000000000000000000000000fc253c6c81a5f2b74b804914d53f823b1548f81300000000000000000000000025cc895a5a051cae6874ce6c8dde6637317605fc0000000000000000000000006cf7a2c61da635eca30c5935a400f078a59254a60000000000000000000000003bdb257836e15857a610aac284af3502fc2aca200000000000000000000000009e665e20d2a11a71c9c3a37891b9a0a92e0f01ad0000000000000000000000006e5afb67b5978b1f7e56358577c6bf93789fe572000000000000000000000000f64ec0c50cefa072ff9147dc5a7dc4d3d8a0e6a8000000000000000000000000671f941215ff9db2f38d49cf6836ab0e79afc2c0000000000000000000000000faac9ecb2638acadd9698ba42591a1ec743bddae0000000000000000000000001fe73dd41b686b586fb8f9df8b3660812f608bd3000000000000000000000000d7ce18c2d099c482af073156db2882e8db851ade000000000000000000000000a319ea952a8dba1442c82a4f4f600d3a45dc0e18000000000000000000000000320d66c180785e5c16eb2ad89edca7ca5eb0b0c1000000000000000000000000b160e2fb32b315f0680d0e24a802a4a4ce9598c9000000000000000000000000fc96efc7e05aaf90c3fd23a6bc9d8d4c37b915ef0000000000000000000000007648ab9829b0b50bb73737707c5326952cd7cd23000000000000000000000000aeec7e0100c6f7d0a21666dddfe135cb4fe82306000000000000000000000000162161f0688b0056fdee01153b49f72ed1bffa6c0000000000000000000000007c3847a5876f80296b8504466d28cb92d44957ce00000000000000000000000010f376cd97c8ac7971e256f423fd63be80ca02be0000000000000000000000006899f9339471fbcebf0b78d87404a4bf7f741d38000000000000000000000000d49654bc9eaea08bb21f95e6f1839e92b3fb3352000000000000000000000000dd228dd2a81ec9f32fa17eb82ad0426c644d74960000000000000000000000009b88d5a03487335a05071e1291f416fb9641b8a600000000000000000000000081f6e5e5a83af4a6a153554a76a6948a7ba89664000000000000000000000000d5280e81f1116febd726a8b77501db93c65b783b000000000000000000000000a786214c5d2a4d5ec024fa686134dd73251dcd8f00000000000000000000000078fd38673ea90fa7f68c3c89004b373f7473e9f6000000000000000000000000859bc6c9a987a086aad26c0a0f910ce5cf5daa710000000000000000000000009a1f203bb3280fece69bada8a1ff1c0ec8d27cf4000000000000000000000000f68d6242c590bcb5298ff28f3968db1ae0841a020000000000000000000000007935c0f5c4231ebff41ce526a50e039ea77ab120000000000000000000000000d12b882f7cd12919e9feba6bf62d43aae942197c000000000000000000000000239886f67d8ea66d918f36c56d36115cc497f3d0000000000000000000000000e7bd0aed16097e245afa588dc2bb03c0fbc6786e0000000000000000000000008d0c1b15756be79279c30d13a9590fc1bc8a88b7000000000000000000000000369eab5c688d93d4d5126d848dd9a6d979df51a2000000000000000000000000e919edecc18a570c521ca0964a06464bb4aad2430000000000000000000000002f6a1b9c81f67094d11242a0c33ce10637ee8f30000000000000000000000000b53f321c0fe1e5e60929071e250191425983341e000000000000000000000000813df108a698b94adddcd11783999f649192d7aa000000000000000000000000fcd36d472645cdef280d4b7c3993ec2a90d01cfa0000000000000000000000001885724fd53172e5d862455949f9fde38c80c9d2000000000000000000000000a15bbd0012dc46ef6d222346e6bc4d55010753cc00000000000000000000000012341357e848b29e926ab86f3e5acfd6af451bfd0000000000000000000000004873ca40c9dcbb5fcdb6e5aeef5375dffd8e539300000000000000000000000048ede89338c4ddbbefd4743b3cde9e5672e6b6d1000000000000000000000000d155e64d9bb676006b64998c05508324f4f9f9710000000000000000000000006b0931570e75e89bc9f5ead3b2863eacc0e3172e00000000000000000000000091c291f1b8a414a5049ab689f85195822a377e23000000000000000000000000cc072c2a5bdab10fed049446ce12377f9e033e0d000000000000000000000000198a584e24d3ada3fcbdec1d7b0038833314fd9500000000000000000000000008ffeef28946ef3dba23372d7898be39f16a2ba2000000000000000000000000caea702803dfde8941f87919ce233596800a043b0000000000000000000000006166dc613bc40506042daa7396932adae383bd3e000000000000000000000000a494748369b21af5c869fcf71fe2971c64d21256000000000000000000000000cf9e38d0d01e4eb0f26008c4f67847e702f172ce000000000000000000000000390a9d994552822c16b84aca0117c9ff3dd8a2fe0000000000000000000000008e3fab1bae9d560c14529e0843427f69c2c328b9000000000000000000000000ede338b3c6ba9103a9c903d94a62a9a95edfa93900000000000000000000000088c0d01274f0765ca12f9b594ac1bdb70db09a3a0000000000000000000000000b573e104878da66d49cf33f9bc0e522512438db00000000000000000000000052865c5a84d51760824820cafb5e266554efd5890000000000000000000000006e6b41de78051f7c9ab381bd2e0256ecbc8272a7000000000000000000000000599b04bb8266f01b0fdbf547e253c37210a149920000000000000000000000005c7a4e019866bc832c8eb38a1cb59690683cf2310000000000000000000000004d037ad3b8b55b0ec8b44b2432f9743bf3c3a88c0000000000000000000000003b1732a79f372bd5700ae1eaa3d6daba833f0f76000000000000000000000000118bda236a0532b4a965fc04eb0c8c1c7a0c109300000000000000000000000081011ce12f603d8777abfe4fb4019eb4961f6160000000000000000000000000ec25125cd1bb1d4a8869bc987d305e3f4838a56e0000000000000000000000006294b6595081279238e5d80fd067b874271babc30000000000000000000000002eea10652c5fad91bcc827dd9020cd76e164456d00000000000000000000000011530b782cab259b0a36d62cf6463549b733d4f50000000000000000000000004bdc0e598d89a97072706b31a893e6d73d8c4859000000000000000000000000405b791237c5038f302566f2ed0be69ea8cbeda800000000000000000000000029422f22e47b48594b5c51033035f7213e43f362000000000000000000000000ecdefbf081c278da64dd3fd1937e1a0268d760870000000000000000000000005b0cbe0273d74f22018cf1b37aeb9ec8e3a1d4d8000000000000000000000000ab1be877bf15d074854b89536ced1d9088b55d3c000000000000000000000000988f66a135e579b5cf873d86496215f37401c4620000000000000000000000008699232f37afe708c4388acd0399ddf915c2f110000000000000000000000000a9912b8affd2b4eaf15067218b948a29a54ed6df000000000000000000000000af0aecf4a4d7f87a367b8ddb69e306e5cb7077710000000000000000000000002698ab33b993eb72ab833cb6bbb648a349bda2d70000000000000000000000005cbe0f8ca58fa82692e902ff0eedc79aa25f6d3e00000000000000000000000038169754af0f75f1de60cb768d3af55c588f52960000000000000000000000009e596e717f205b1a6582d86a16b773516df62ad9000000000000000000000000c60927d5794df3b57926a283e5a9bb211d27ed16000000000000000000000000eec6bba315c715931cebaa87d80336b468cbfb0200000000000000000000000053485d58437895522058b3869a6202498d2513230000000000000000000000000e83a2d60778fac08aeb930f3c320801fe2ccca200000000000000000000000042728c70b8abf85a8108ccba50ead645b8105440000000000000000000000000f2abf91e6d6b44e2a213350aa95ae3feb9046f840000000000000000000000007ccb0c9d2fd0bb862265df11b4425f74b77a44120000000000000000000000006a5d831a5171344ad921a4402fad0b961e76e17d00000000000000000000000056e9194e18db791f5b25937c6e570f7ed7b15358000000000000000000000000c841203d9104b3d3f58d5dbabb93f9411c0eff54000000000000000000000000b462cdc9777759a70f1f3377744aac293f40ac58000000000000000000000000279816532ec96b16eda9bb36aed3ca911c7db29e0000000000000000000000001fc416d6ce0af7aa72a0e3d2d622cfca73581bdb000000000000000000000000e393e4d2302001a928572eed995e5c61b4ee6350000000000000000000000000ef2935317885ff5750b58caf90c2addafbda7efb000000000000000000000000254934859cb06d70e72ee28e1852a0f1e321430d0000000000000000000000004f73b09126c5bbf0ee674ae7ca3bbc21b531e0ef0000000000000000000000000a7e2bdf61df32a1907a3d9325c065a3d76ede190000000000000000000000006eefe64ddb2cf3d59e63dcb065154de1512db2c800000000000000000000000055b51c17fc82818b32c0ae1464a3fcb0534e4d5900000000000000000000000037b6c31b4f5f17ce9e67c2144e908185db4d7a040000000000000000000000006213a491ea8a3c34cdea62844b4b2e58270ef6c4000000000000000000000000db2b72ee0c7adf4f6fe85ccde475c67baa29f4d400000000000000000000000086eee8b8d175341ebe65bc7b579fd90be72d59100000000000000000000000000e9bdbd393697e495e3eba1bbdd0b8e3afba0f98000000000000000000000000dd4bed194cf26e3ff836b5c6735a21a1d4fa8aef000000000000000000000000d9434d0aef7f702c558108e8ded78ca4f18ed93e000000000000000000000000ad8826c9b289d4f60e03faa004aaea160353f09900000000000000000000000089b124570529d9f8c89d85b32362a430626dad90000000000000000000000000d53492c49f4fac94027597fa35bc0b7bf42858130000000000000000000000003109643e03cc8d0af5577819854e37f1c3aa944400000000000000000000000012f760c4c3ddab0f7594b76edd807be229dc488500000000000000000000000046a8b144591746de657b136ad79c5f274ef61c86000000000000000000000000c99c12c10832c7762eac23d89cedf48249537ccf0000000000000000000000000573371f08f8e2b70dca981b255f19389f02a0ef0000000000000000000000001254d674dfe65f3f46d8c5d208df0005762dd1f7000000000000000000000000dfb86b184f801327540b19ee64f0cfd17faf96350000000000000000000000000f4fe5a26349044865cb55fcbed6f42522d89be8000000000000000000000000654bd531b78d52ec2bb8b9ae3e4a6592786e1b6f000000000000000000000000a84f80e1d5f1bdf6a01523d4b6f4549b5b0511ae00000000000000000000000011466ded9018d521dd4014605c337a3d3bad09b4000000000000000000000000093d1543dcf93722e1b37d13ce2c2c7b32531b980000000000000000000000007b7be2e99b0bfc47c30519cd2a61bf7cea479f79000000000000000000000000fb559565d4a10c730c0678ef58b739ae7adb75b2000000000000000000000000c0ac795873db5a707e64c2142c663262e2cd8f460000000000000000000000006b99e9cb2c3424b370e42383938f06249222e2c200000000000000000000000039287b94404df817797eb887cb3a387e2a27928f00000000000000000000000060ed857098ed6047bb468a16aaf25dcf273c1c6c0000000000000000000000007dd0923c0cb4d5b331eba921f05af043b796e6b400000000000000000000000075b35cd84d485c768297f8f9afe4b16feadc9104000000000000000000000000015053de878fcefb7ad5db719d6bdde6412e0321000000000000000000000000a3e56c6df3bfadbfcd65d2c7c1046284ce6d6067000000000000000000000000dd0a1890b0f3213102a9f858ccc798b3aec6b94b00000000000000000000000076301ef56a524fe83b891dccefb31178790de3520000000000000000000000007fe88717c62f0ca72ee22e8641bf92c0894638d1000000000000000000000000bcb4ea5da37b9893ab58803306e7150cb83e9752000000000000000000000000fff2abee60d164a8cbfb244d10a2b18dce0713b2000000000000000000000000cea1f873d995ec94b373eb69db2af632311182250000000000000000000000006ad8fd1a1d1916e7aa6f3cabd9c3f478dfef649b00000000000000000000000039d9293694f2b3e9302019b51b4e491429aedcd900000000000000000000000041f4622644bc6cab32938a5bc27f4eafc873efd7000000000000000000000000364dfbbf3861d39aca8fcd6b57123611e98dffca00000000000000000000000092da7004d5cf237bdf4658c9ac19caae13676b22000000000000000000000000e847268ca3b533b00338bf53615fb4c38f70492400000000000000000000000007c58779155a925ed1f5ba2d610d6b02462283cc000000000000000000000000de7c9084a48266320bbd883b5e3e1fbc8ec41e28000000000000000000000000e28ae523d8bddf57e1ecc370cd87d8c16443cbbb000000000000000000000000edd0eb4f0e8bfb28c1ac997c8d1a05c3571eacec0000000000000000000000000779ab62a4587531a1508f1364c0176ecce0b8980000000000000000000000001eabf73ab1c362c2ee70a47b1e0cc8e45f301294000000000000000000000000477cf615451cda2857a049f693e7614f45e548180000000000000000000000009c93175043bf08bd37f8c83b9094602470ca8bfe00000000000000000000000061ce230157c791e12d021001621e11d9f6a4f552000000000000000000000000452283b7c46bba04305654502b0a5d0553680fbe0000000000000000000000006afab857230275fcad0c75114a2d2f7fc154e6c7000000000000000000000000c34e83eabe5e64e903a9aed1b76a8942a286730f00000000000000000000000007221f9fab99c7298e20f182771222dfe118109100000000000000000000000039e41a1c062cd4fbbfa1d7aefe828384daca508c000000000000000000000000622c611ede07222a30f070bf0865437515c21bf9000000000000000000000000db18785ea18f2d4936be51850f368cbf6a6b505700000000000000000000000057177e72844c7633cb14b2ad702b3f1cb7b8924e00000000000000000000000001447dec147917844cea959b914fdc48d16c652100000000000000000000000095eddce628a95cdcf037f84977786b4daa993cd80000000000000000000000002ab19c7edf2e73e58bf987253561e2d1fef34308000000000000000000000000cc313f29d7b55da624cf86728fc1558f353582500000000000000000000000002e061715d5db6b03003a6d5d3745854a8908b383000000000000000000000000705e48b7acc67dd54d90ceb3aba33d4953352e9f0000000000000000000000001bba8da0a4550bb23f0c94f660d8794fa03d27620000000000000000000000004afdc8c6c9a8b3b8661c242c0145c491bc2a283a000000000000000000000000d6850a4a2d9ab65ae27563c543eebf20749341e70000000000000000000000004c68e6e61f7ec16cf79268bfc678e4a97b487f3000000000000000000000000058125994f6f0eabcb9bf80632726ce9610fceac3000000000000000000000000a2e3e1bf252b9eb1437ea2b2f621ae88f5f028080000000000000000000000005fc2c4b5a8dad8794947d134bf0de97ecb220a170000000000000000000000002ce5644f2e470b664e0b87dea744e9ec5e448180000000000000000000000000902aeb78ffaf5e5002ac1646da4c976e3cd20edb0000000000000000000000009f4ed99a67143d9b6f33e7860897d5319b380867000000000000000000000000906c38909af9cd416973553105553b2d1c0b1b670000000000000000000000004a05a2a6083924d195e2d0d656fcd60cf8061d0b0000000000000000000000006035cdb4e3fb3c99b5409da5fe9faaf041bb3c68000000000000000000000000dd3f1c9ea6c073174941de8c10bb6c977e5ccf67000000000000000000000000bffd404ab45201de5e73b8a45c37d91fcf556d340000000000000000000000000d75b9ec54fd3ea0231d7a5f566ece388075306000000000000000000000000069bc8512208d70cde4773ea1796061d222ed4fc7000000000000000000000000fe58475f3d6bece4d8d1493574be8b2a5df72dc5000000000000000000000000b67cee1374515722284e03e432477dda8aa3de130000000000000000000000007e9957075c46708b11bd5cbfbd1b0fa4c9365e17000000000000000000000000864150f2da1e98a6cd6bea45d93630d26a3c9e95000000000000000000000000c60554f08a76a6fbbfb1a0649d461d442e9882df000000000000000000000000a00b4f0670d28ce92b9d317f0c69278646348d9e000000000000000000000000c6a33c5cf611816110e1acc59f6f6cc551119bc6000000000000000000000000cd489fdd0ef4d501c8bc4077b5dd04709077f0b2000000000000000000000000a0e855a4618c4c65985be4850dc8cd6b769c1855000000000000000000000000b7976f651df2ec32e7f061b6db02c3f329a0c8ce0000000000000000000000005f8a6eaed330fc1731762364a11e375e28a7aa820000000000000000000000006a7c6d615feec103835779ae1e61dc2080ab0028000000000000000000000000abb29ea8981e83c989fe7cb608abc4b1a19cb18100000000000000000000000073a2f14385c8d7b7c739a43bf91019893920f953000000000000000000000000b9f5726ae8ae704d1ae39e6635913d1db654446c000000000000000000000000079476d18a486f67cd177462ef9cd0a24fa3d783000000000000000000000000098fe625f917e1d8ae190c1b9cdb9e7d854a94040000000000000000000000006f214b804485cbeb9ac3458019b6312ad734ca0600000000000000000000000047e58e865eaa11214e3560b74a3565c51ee19c3b000000000000000000000000a27d25421f4a0caa73c0482b2888ceafea15896d0000000000000000000000009be0c14dd4d17092e66643b384f101ae41c366d6000000000000000000000000650bd8722b4f6d1e291411788421e21b9682dd43000000000000000000000000b119de3bdf476b726261fddb34f574a19111f1ce000000000000000000000000aff6acef00e48495324920c61dbe226e362aea5d0000000000000000000000000e880fa4f87bd1231349966576ea34999083ab0c00000000000000000000000082da0a99f731bd3cede407a5082dd5ad38a0fa660000000000000000000000008dd09ea0c1486bbce042ab63bd1f00575ec57c10000000000000000000000000ae9fd3be2ad3ab993e8b12f53acb3370d2cf85c30000000000000000000000003de5e8bfa47dc9f7067543196dbd51c8d3a03cda000000000000000000000000f77c1491ea35053886c32542779add9b1bd8ff4a000000000000000000000000d1cc923e28d5cb89e60752f6640f923bc4f8450a000000000000000000000000f606c34a4f67344c1aa9008c77d3d1097005d42c000000000000000000000000d68aea6028172cfd417bbffb1065e2baf832bf5a0000000000000000000000006e763631e535de674135c4f64e78110cd3fc891a000000000000000000000000848823f7dafe1bb36b7e64d188488b70aa4f6c6b0000000000000000000000007dcb17cfc40f9236b08c08adb262b4099a3477e3000000000000000000000000c987478064f7a6339f7a0c63e11053b85b10c88d0000000000000000000000002c69f0b0b32e997ca9fb1adb9b122383c01bc96600000000000000000000000079a87ba6fac6983213b6c1421593a7d64e2fa0be000000000000000000000000497a1f6d9b13905db63f2b9f16fb7cd22aea406a0000000000000000000000007f8139c8269c917085537ad68e3600ddd9eb89c90000000000000000000000007d094037d0f0d5a5ebf67258cfe783ecf57a74040000000000000000000000008ad198abd937666cb3376a20ab6ca257f96f4e2d000000000000000000000000c9549d267d39e494770297a589a5a0bf3171d1690000000000000000000000005d6edea6017e54154e5aed9841e4623bc8877c04000000000000000000000000008880fefb10a5999473943edae788a6d54e4bc900000000000000000000000037668b5e0fc9bf6a45e367d6030b6609bbc32b61000000000000000000000000ae6ca04ed7eebdd5dd256e595072c54c304149d900000000000000000000000008d347e43e361c42a31daeb18ef2cc0d190d6bf10000000000000000000000007f50d5d38ea40b4e331d32aa85dd74bf7ad7e5110000000000000000000000004846c8dde5747a69960ed0dd8538c1b7ba393e940000000000000000000000000df2b635075fa358eed7c126baff1c9b862d3bbb0000000000000000000000001deb1e3a9a33e99886c7b3a91e34f2ec0d9b52c9000000000000000000000000ae4f25c994b518b4a0fd37f666e6f8f77daf16d20000000000000000000000000c2c5314dcd0a7bc928ab028c8ab384f9ac26ab7000000000000000000000000e1d0cfb183ca26466b81bdfcfb1d6775e3f4bb370000000000000000000000005b3c938e6f0303047b57c7d8c13e37783f795ba20000000000000000000000007090e22b5f2d4215a635beb85441b6128d011c76000000000000000000000000a1a93a8431d224f5f99a741d26433d90a4cd069a000000000000000000000000739dee60308f5f7a05be524b0785b36c95153402000000000000000000000000745ca64c4b7519399505ad7ea9ffce90f9cf3d3300000000000000000000000098c9c491b129a73fbc67fcc6afe4b32803bb34ba00000000000000000000000072d721c88690147421092956b0e58bab0bdc3034000000000000000000000000584c6e21412c7a6567c357571204acc2bc57430400000000000000000000000082735888d1c4043bfa804ffff5eda0b0e52bb4b5000000000000000000000000f9b84befdfcd0882aeeaea24f850430ef8666aac000000000000000000000000eb75d14fbdf23f1e79033e99706de359e4316dab00000000000000000000000035c443afd895ffb2b769a3416191a9ea500f946a00000000000000000000000081cd0d8c36d9f43073a6beea7bc2a143ec66885d0000000000000000000000002fe1bb61cc67cf64cce87fbf835b836f6a3c04e0000000000000000000000000c228d0a9137cc5e35bb9770926d138a19d1f73470000000000000000000000007816aae5c3a50999deb2c4467aaa4a1fe34507e4000000000000000000000000c6aac9efe2f7d96bbc1bcfed1612318daf302c69000000000000000000000000226bf460f545730b0cda9cf96c0393a55cf714fc00000000000000000000000017d0827fd96765a7af1018cd93006ccf1fabc5c60000000000000000000000005fd9239392ed3759eb48691b58ca41cea79b831100000000000000000000000011c337ecb620bcbaa3b8b9aaeaf397bebf93eb8e000000000000000000000000f37c8510bf7e21c556e13b625b32e1ab3b790d6700000000000000000000000051761896987eefacf1be63e919d26cbaaff33a300000000000000000000000008d0245ffddb94e6b0012e7a7c44728140aeb1503000000000000000000000000ccf9a84d7eb7eb332377a4ade4bc18448ac47eae000000000000000000000000400acd777f435768d5714d59544dc212cc750b47000000000000000000000000ab67ab90b3e8d8d1d31cde0d69097006819122d20000000000000000000000006261c7f5628c552e245db265b45d878a416b059400000000000000000000000002b7439d09fb3cb7e4c9d4124c06feff55c09a1b0000000000000000000000005cf6052840ecea31369e7bf426145ac2aa59cab6000000000000000000000000e14e36961222c06258eb01e9cd870905d24453e9000000000000000000000000ae1a546aa603395dd8ad9cbddd1c10ad36db904a000000000000000000000000c1b369f561a27830559bbc770ba4fbf96c255eca00000000000000000000000024b7c11f7e06ec108f2b924fc90a49120431fd0b00000000000000000000000071f5a573e4e06e590757326e6a63b02706d319bc000000000000000000000000c4df171fa405df3fdabb136143055cc04ebe97140000000000000000000000002676198d802660bb4121db3afcf1e11540f5d6f9000000000000000000000000244b8ca6996c29bef8ff7d83c6faf4efe9387d6400000000000000000000000094c15b3e5516ec5bb616d17a5833cab29c7d63f20000000000000000000000004b81de6df8bad151f2593c3ffa97c93879e8d9ef000000000000000000000000d32ca710b42fbd87f835b9b595dd1538b605f2f8000000000000000000000000edf1f554a63311572f8683dcc8a09b872d926928000000000000000000000000179f95c078bfb5cc303f54d08da04696833077f2000000000000000000000000d819e937426a84be689a63dcd4adfb2941ff22100000000000000000000000008c54c93ee4b0e630513f195368b615b9d6e236c1000000000000000000000000a758969c692085ef88173c392f2954270b9827bd0000000000000000000000007f1973e5f493ade52b98575bb513c2ea8bc52d93000000000000000000000000522695d7e879c5650759412a73745c7dd76cbaa7000000000000000000000000dde758ca495cbddce69c6fda4acdc855af11d4a30000000000000000000000003d7ff7c5c8bdcc4b0d3e746e20e44024762546ba00000000000000000000000054c58a2db5b2a58dbba43dd1e939daec6445872f00000000000000000000000058331c199e67a9e991e2061cbff70bcb15d65a49000000000000000000000000fc7ce4b74e8b1b5835caee6d34a6072ccfb36d8d0000000000000000000000004bb9a03990ffacea5d23e722091bc5d2718d1cbd000000000000000000000000f46b13d14a9d74ace7557fa069c7d63733de5fc5000000000000000000000000d2bac8ebb2f6f5a92cd13c0b5477f916d0d29bef000000000000000000000000bd1a4ed3b262ac6432602b7a18ca65ca29a364700000000000000000000000009e9e83275d92d3dca640b2f8c14d08e389a75f9d00000000000000000000000014cb0351377e3b206e63dbc435d3f185b958e0dd000000000000000000000000ec820902c78106daf869d2bd1a5ddb7419ef40cf0000000000000000000000000fa9dfd93a1a51c8eb7dcf025730d1ac02f83fa60000000000000000000000001cf0088c14a5a4571a41507736815b00c5e532980000000000000000000000006c1d498debc51fb25b6d06492cfb057e74ee6be00000000000000000000000009d7ec8b52818eeebb6b893b2f09d781c024e0425000000000000000000000000dfb671a4f29e9a7747e5a9bfdede8f9fb81982ce000000000000000000000000d865cab8d312053cb5dc3a8e2b47450a1f9f25ce0000000000000000000000005c6ef10648213c486570c17c76cf0922f9edca4700000000000000000000000088d3a16433ae3833adbc0ac1d3fb30e69cd3bdf7000000000000000000000000615a1d028784a273ab0f465795d793398b3887c600000000000000000000000091f6add8c6992bc3efadd528c20ace3ea9e2218e0000000000000000000000007f3f80ca889de690aece0db872e4c39f52ee0f170000000000000000000000001481f4f9cadbadf25f873631afb6ae22d25503a4000000000000000000000000d5c0356c800e7191a5af4cac2b7ae9c44f07b049000000000000000000000000d778ea38e4ccf3511a0f59c9806f37c7bda92cff000000000000000000000000ced5a895b7970f020819eddde9b0616d2ae053d700000000000000000000000034470cfaf0fbccb5935a6bed3baa686989b04eed000000000000000000000000c936019d1847acc03d01362ff05b19c521be154d00000000000000000000000016157022e231bd391cbe55711d492faa7021b805000000000000000000000000e91f81b0057862d8c5ca642a05db00ddfd6cbd02000000000000000000000000942f6da946cf77fb53170e8e416e595f5e3eb953000000000000000000000000fbc8688736727304d06252134cbba29ba95d1a6d0000000000000000000000006580ddac5674726ac60dba4470024615037df0350000000000000000000000002f7e4cf14adf900672408a7d21070ac34363df0d00000000000000000000000074f6e058315fba3ebf55d33cc6505ab5ab28cc4f000000000000000000000000f21eaeedfb2d605ba091bd08e7c30b23450dea2d000000000000000000000000642df68305263e02627260098c6e023ae29bdc75000000000000000000000000c3037ef8ec35c709f90f2981f322416c8d0fb8830000000000000000000000000d5a5df0f56b7c0c2d77e15a4b066034c30ba6690000000000000000000000008ce354651788679cb1385171412443fd1b51c16f000000000000000000000000d89f429469eaf8e36014149f748ada5404c584e300000000000000000000000019dee827123e28196c6785ab13a8d15cb6721ada00000000000000000000000009cc1b107188bd4b2db85ff1a9731ffdf2735cad0000000000000000000000008d4535ef821c773a905c7bb5aabad9865a4fe9a20000000000000000000000004399555e2fe753f7ae3ca5bf1a6735219733303a00000000000000000000000027af5c6b3b5606ad2d495c90738ed36c2acbb26b0000000000000000000000008e21dc17387e56705289d8fde6aedd71570b0598000000000000000000000000b884787b220a857af3f7d87da295cef35a60ab900000000000000000000000005a33449e1068622d4b9189a10e6b06f282668ed4000000000000000000000000dc69eec0c0c5135d11375bea03b76ca939566a06000000000000000000000000cb3eb3331d3a3021dccc0695f7f5bb356b38ccc80000000000000000000000000a489b5fae86079803fd840dec2cd6a8c9fcbad20000000000000000000000000af8d0491d94389ea61e5ef79f3924581e2e82c1000000000000000000000000db2fa4f76fd1cbb08b52e57387f2c73c8cc067f9000000000000000000000000a152e9b26df4eddd79115ef846bc74a1f2f853c70000000000000000000000003050eb14394f11e8cb74a1af6db83db05fabb12b000000000000000000000000264aa32dad1d8f0388b787eef55e8d5dff7d9d1a000000000000000000000000e599f271f0094c3fd5532648b7436eb68d4025ac00000000000000000000000033b9c695c88df21067855fcbcad5f7ee20d0b9820000000000000000000000008e27a7d508f292d709d7099662f42492bc8e599c0000000000000000000000006b27994021433c75b5115feca4fc31c9e1c3df21000000000000000000000000d299fa49a47faae8f7d1b69f0fb2588687eb4e5200000000000000000000000065efa2d869cffbe5558e4bb789041e32e75f785a00000000000000000000000044bfb7765841a406674f42cadcbf2bbfe69117bc000000000000000000000000c11c272f7c5f127dd911dcc3bfbe62872df77d8d000000000000000000000000f74662dc19c3d6480627e7e7542d28b30a3a1531000000000000000000000000743b0c678f819573d421c8d8f017281d11bf5056000000000000000000000000b8d86887bb379db33a17c11a7ff9cffb16e35131000000000000000000000000c6e678f796591bb22b58571f5db16fcf5a8bff3c000000000000000000000000ac531284c469b20ad3996dc04ceacbff3b4ca2f600000000000000000000000049cc90f95fb3795ada93bf86eb7a55ed93f347270000000000000000000000002cb98f1b05213be609d4f8c9099e5265760c2ab3000000000000000000000000966eb41f9215f74a76f15e101138eff62777231200000000000000000000000077daa2a89281c0936420f7ede2f0e68233501a54000000000000000000000000e05546e3eff21f2fb0fbec055ef6cba23f77304500000000000000000000000052b9d6664da609082e51f0e7a45a7a945de629710000000000000000000000009a6bed0f7f31ea12075593e8c3f7b3d7c16c6f8a0000000000000000000000005fd468903fad6436b61265101e10022292b0f3c0000000000000000000000000b07568a6853d97020a69f48c122c84d2aec76d92000000000000000000000000d0585568d1bfb1f626cf335c6c3e6cdcd84754f700000000000000000000000065cb1900ed9f1f7de66776ec75bd12bf7ab0163b0000000000000000000000008f7c8aa90ebdc51484a16e036b0797667c861912000000000000000000000000f6a6c660c0858007af06dff44050106e49227f57000000000000000000000000cb4e589c58b636291b73f36428a2901f9a901ced00000000000000000000000012f6c398833cbea0b56434d814ac107fba0c9bd8000000000000000000000000eccfc303f108e382b192cc9aab70e30a7aa7810f0000000000000000000000007be2d5150d7d7efdd0ffa8a957dec8895f8d1abb0000000000000000000000007c172e273d6feb01c041211140f39ea8be27b72500000000000000000000000054514197caaec3db94451f432c5092c3783763870000000000000000000000002516ffe75fdc84bf027ee14352839acf63cb041f000000000000000000000000c6508db99f4db213dd71c34784ec0505383789ce000000000000000000000000f8dfa50903d65fcc03da6348bf8fa88483965b160000000000000000000000002499a068f468f8896f2b7021cb04092c2476f56300000000000000000000000061ec0aef14f58a89df9749520a171eee30b4feb6000000000000000000000000b57935e3997173d8d2d471fbf0c5c8f9ae02b0c90000000000000000000000005e64578c4cf7abccb32a264cdaf0c3261975d94e000000000000000000000000c4bb855893df113f1f3616c942e683a440df52cc000000000000000000000000daf5025ec0844c9dadad3b8b3c4cad41b97d6596000000000000000000000000160339dc12dce4971ee95c545931c12bbf6c5287000000000000000000000000b59a8042ceb28de94b49601979d7910172156f03000000000000000000000000e8889b3afa1d2a37996cf3ac56c1e633b4f50dac0000000000000000000000004f001b31c83f6891c9ddd41dcfccee9986d2460c000000000000000000000000449362efaf626063228251f44f61770ed316ad5500000000000000000000000005da93afc48b874ccf93d3646b6a49cd4d0b321100000000000000000000000088b3adba67e1e3b16b11483fc630bb3f81c73b6d0000000000000000000000004d502f02b8da038aed30ab1b8524b6320c84ba1b000000000000000000000000c8d69f95a6e93981bb772f63cf4d61d7a905f9a1000000000000000000000000d4ec18973cf3af5779886907124e55085e91d6420000000000000000000000000de5344d8be36b90e7ca73301102981d3b2fda250000000000000000000000001174c8d3fe1ce6fa955c228760300e91500e4da1000000000000000000000000eead4f379456506a2451852ef1ddf1d8c563cbd7000000000000000000000000707cea598b987ec33371490b4675f3af9eb2b1dd0000000000000000000000000300248c6045adae61585f2e454da9c77b25b46c000000000000000000000000d1319e17a1bf454afdf55335a0dcceb6d72607f9000000000000000000000000f12d4e9585d3f11c9fdecd660a00717bd7e296de000000000000000000000000ef38a797c135876314189704f07725a785d062b8000000000000000000000000a5ded0412981543480a26de10d9575e680dcd7ae0000000000000000000000000be8c925e6d7bbe0ab308e77933bb04e05571082000000000000000000000000e007ee43f5effe8107efc3c5a80bdedcb5959c380000000000000000000000003f108f6b29f941943bd3c187b7dcc97337f0e28300000000000000000000000088efbadfe921329dfc18ae698a56e1917c62764700000000000000000000000032c14c8b0df2ade0d4deaefa67340bbe4b312a910000000000000000000000007058084b8ed1d2934b260b546aecea79ff3ddec5000000000000000000000000aadeaeae853d88b675cd126a02407a55190147980000000000000000000000003b5a09804421c0cc73ade78219e8009f611633c00000000000000000000000002b3241338e81947959246847e199ef164bebfcbf0000000000000000000000007226122e864d80295771965d3df6760ed8dc894c0000000000000000000000004153b278035d95cde210ee624444aaffdba85a4e000000000000000000000000a2ee16c8d256ff2b3390eb20e6481ae43d7a8081000000000000000000000000b8a41cfb33ec38012c542d28a2d2adf50c0335eb0000000000000000000000001e86ae93a09895ffdf65dbc30044a97266fa12c400000000000000000000000075f0c86c268ba51aff3a78cb8696ee350ba8bfde00000000000000000000000015e1c04e9c5ca82e345e9fe549d0e959f15695c70000000000000000000000006201f32c4a95c3b9c64248e76b8fceeb856458640000000000000000000000005b65b896918ebd74f843d0dcfd1e7bf658a9ccd0000000000000000000000000fb95a069bca28020ee26ee9249b8c8c3e39de19e0000000000000000000000002826e0a4cfe5fcec5279aec12c4c928d545f8e3b00000000000000000000000047ff9f6d8b60d7bf7b8b162fd15ff7466c030639000000000000000000000000347691621e017447144fdec87cdcc11a18e0c103000000000000000000000000c941fc5ef8613f0a6fc1928a1310f9f189b358830000000000000000000000004ee081fd9a51c1049164a047b0392b6eacf653bc0000000000000000000000008528520487cc55be4a0c1107245e64cc8c71ca930000000000000000000000000ee876b94c9b78f47a52ba13faf1717a0366d5f500000000000000000000000029bebb64d4ef42be9b84fdd220a3249d3af8af9a000000000000000000000000a3ed2f67fffe1bfd47acf19b2e87b642b6d9372500000000000000000000000057966d342f21a43159652e32ddf6daf55a21fbe20000000000000000000000002fe2d27315d598298bf2450da927ea0f5a8d280300000000000000000000000061364d8034eca6a67382873d6df7a3e2272fa95b0000000000000000000000007739d16c64bcd859b17a43086a037fa56f4c0c1500000000000000000000000032213e3cb433aa8387c8bf5352c8b6fb9a4721ae000000000000000000000000b7f4ed0af44cf6b136cae29d6438832ed51f26b600000000000000000000000068c9a24fc41881daf1a82c1d59048270cc510dac000000000000000000000000c61c871aca1ee6d97221f38192ce7a1e04716eed00000000000000000000000029a7344119c4fd3e582b5e1454a1448a35ef243a0000000000000000000000000b56d716e765c454f3065d7686495eef408371220000000000000000000000002e0f50ea52213cebc28706776a07a97f4796d88d000000000000000000000000bf2c8d2f98a4dbc05f51f475d81c48b3efb7dcba0000000000000000000000004d6954860a5291a60d64ac9f2c224b742dee40730000000000000000000000001ff00121e9f6f4839b08a6efe6348f0bcc9ecd5c0000000000000000000000009029c8a11e310f1abbcfcfdac4dae7fa3cf0f56b000000000000000000000000aa3cfe869ddb8c505ea32e7856aba47c511eef660000000000000000000000002725f5d4ff99d5cc8f20211dabb832c5fbd4f2d8000000000000000000000000a6c7ec7b289fdece521dbaafa274038bb95c5fcf000000000000000000000000e522124f4c57f0976ca00a7ab9125ce8ab6d1d09000000000000000000000000fa39b9dfc6f613217126c34206586d4bc6af3852000000000000000000000000ea2c0ae279e26079926886770b5710a95dc9d86c000000000000000000000000a20d921cec2a6c56d1ecc32e548aa713e2550fe30000000000000000000000006b090ee37ead372281fbedb5d8636796c6c89d35000000000000000000000000a2f266a1cc103b8ac1dcf564c5fae262c9fc8364000000000000000000000000e82fe8fd7beaecbaf02f0879f9cc7fb592e3d0c30000000000000000000000002e13ab492cc3001436ad48be416023ecaf0072c00000000000000000000000006c2e89dd9c3539f3c87f2e31244b664b92c85883000000000000000000000000aab8b753b05c90ec704e9dbe79a9e366f1a4775c000000000000000000000000eaf958a6d7bddfc4b1b5fb0b880b2a998df146850000000000000000000000009ef206fa56f673b57260654183398d1c7e210e2a000000000000000000000000cc9973acb08ee36ce1dd57eb94263449f722763000000000000000000000000019758c4247a22882fcc03f1c764c69774d56a610000000000000000000000000f177b67775e4b8b8b723d94a1e4b300c29c676d0000000000000000000000000be1558074111404da810fd48654941d522ee845d000000000000000000000000ba2d13ae3212781520f11d147fea508d8cf44056000000000000000000000000a5e09eba16c94ec69c24c2785dd7065289146cca00000000000000000000000080e88bb37de082057d587600a7efc1561d86d191000000000000000000000000be378a8d91a14e0cdaa61796509a75ee8255bfce0000000000000000000000005170e50ff672a6431293ce9c693b0ea7d1d2990d000000000000000000000000c98f8e16d9a40477c4053f887b828f1231cdab7a000000000000000000000000f2e67535aa6b4fafbd6e39ab65742542bd3d6780000000000000000000000000a6ef8ad6c76bb33b63f9619953b3584d30654c49000000000000000000000000a66c6cfb6b745a30ce0a18f9cca662a1a78d78d0000000000000000000000000ec287f89381b8fef25858e2854d3c6dfc2ee7ea1000000000000000000000000f50fee3a8196e49bfb6501e86411936ecb03e952000000000000000000000000644f41f35e07a3699df4366808ac08ca1037863b000000000000000000000000c5f5c91c036d2379f2e3ace8a3310246addd7554000000000000000000000000f068395f1adbe0d6f3b3e6c20d550b7a568be369000000000000000000000000018ad443331c1bc8af419e1ab8813e37dbf07b3e000000000000000000000000ca88eb1690dede759e2cbc0261a2601eb49352c9000000000000000000000000dceae123189d87008c87447fdd9466de64674baf000000000000000000000000ae880cbbc6739a153c5fc61aa2dcab963da2656b000000000000000000000000ab717ecbf05da67b55fda9fa23c330fb4afc93a2000000000000000000000000476ee4afa9112842c5f6d7014b6eb1c08f2f339100000000000000000000000003347f84d1a562fd978e05a0f6a37760b271d340000000000000000000000000b802553dc8afc397294c74af9ee04683bc1ae72d00000000000000000000000042d2ae06e68d447eaa5d5978b3ea8e66bf27ab53000000000000000000000000041609c83a2c8e1062f71ce15b4a1f2fff42ffba0000000000000000000000005e9d7face1d1369ac9069cea31336061992b267e000000000000000000000000088d52971c9802823e538a898847e5040c9c76190000000000000000000000008b27f703c1188aeb17d9f7f7d1f99ea7a26f8fb100000000000000000000000014cca55297ebf43fba0ab23613850a0c4d15376b000000000000000000000000c0f9cb7e611f6b9822654909bf6abbef0ccc255b000000000000000000000000d7a2dc21b878757a0cfc915f60a465fa595993f7000000000000000000000000a942d95aa06eb8dfaaf2e7d06f85f026e307810f00000000000000000000000021a415f52a87c12cb3163db935ea929d0843b48e0000000000000000000000000cf5deb1e629b8a0f33685babca4157b62a2336500000000000000000000000076befa597d53035ee0d43eab11e129f198a3b09400000000000000000000000000385aa52a301c030b166d8981c6917aa57a70020000000000000000000000006b668cb92c124dc26a9b4ba9fda27c0a8a60f8f60000000000000000000000009ae1d82fbb009e7bb2afe2547e4b3b6fe45342b3000000000000000000000000435bf4a1c73a382b92816c344ca2ac5e7e9975d200000000000000000000000033a4aa3cc0435ac3d247b62e03797c0a31d3229f0000000000000000000000002638db06e731c4b55afce63e1d933189acb431c70000000000000000000000007a947ac4e196397d91427d245c43e8f99a17c2ea000000000000000000000000b4fa647a23af5dfcc424475de99958063cb0e148000000000000000000000000e766bf1dc39ec15ca80267900d17c1442b544efc000000000000000000000000a860eefe8a03662c7348bb4529781dd21406244100000000000000000000000063cd05475910473ffccd41e729e4068b45dc8d510000000000000000000000002995f0a68fc4f51048f307bf081b8c09a0e4e99700000000000000000000000011222a31be36a6d853fb852a9a44ddb241290b08000000000000000000000000f58db63ae989b13a7ea02743417a1b58defd66740000000000000000000000001ab5971b8d837f4eb082e2c7d262292b9d82e1ff000000000000000000000000479cf15609cc892ecbeab83499a86c14c99c3bab000000000000000000000000ca90af695f8c87dbb080e0a73a6e1b97413a7b59000000000000000000000000329f234c378cd3e236fabb733f193d4008a5cd6d000000000000000000000000f260d61ed3c180539f921048983b3832a69d811a000000000000000000000000149d2fcc8eda76a098966622fc24f36cd74d538700000000000000000000000076cfffa7139903ba7233409eb2cd22c23d4c19fb000000000000000000000000ec65d4e35ca9f9ecaef3a2ffd64df1a0ac9d5be80000000000000000000000000f2556cabed056026f30fd55ee4b4e7c0c2f68b40000000000000000000000002cc1e26bbf1a53695110d78faa7b021aa2c8f20a00000000000000000000000086b6ec7076171530692cb325d94a1425148da97d0000000000000000000000004ac6dc39545947364b416937f6431ba1818f4fbb000000000000000000000000ad3dc8d2d8cf0abc333bcadcd2ffb7cafb912b910000000000000000000000001d6ff73049d71356ee20c5b403c641915faa4de2000000000000000000000000b7267f458b0d4f38744466590a8023a4a7e7ea56000000000000000000000000e56bcc23776410ab3b32ab0497b34154b73ff8b50000000000000000000000001bde8c21b47be99d58c43d283f7501703a64ab0b000000000000000000000000ced24b6a49c5217dd4f9cae19ac5cc2d7df38f61000000000000000000000000785bf7b0235908ba76207081aec8ba4ce361524e000000000000000000000000f0429528ded30121f9c2ab0d4de653d51becedf400000000000000000000000051b21605571c987ca5cb03f03ec7ee7206704af500000000000000000000000070a50c0e174a5e9f4a56e2ae849fcac97199dee9000000000000000000000000d7da71e7ddc95e464607efeac6c6c6d46a26f8610000000000000000000000000a7ffd571a8a2430d10095173dd94c96ed076abd00000000000000000000000031d18eed98e7d819644531252b8caae84b9e1771000000000000000000000000684e2a76cb3281bb3e9dc3e7d5a29151a1f4e160000000000000000000000000d9561a4546e0da11dc8ebe4ab5e02dc61fcd3e3a000000000000000000000000075773cdac7408e3e72919aefb543314c0015943000000000000000000000000ba0267881b1de1aec47ea0418d6c8ad20ef192290000000000000000000000003cff55c18176205ab656481921270b04939426b1000000000000000000000000f5691a5f8afb2c7bb618aa346085832bdd3d6a4a000000000000000000000000a8e5a934c85c4a07a8bf4b0d2320cabd33b59d5d000000000000000000000000f08b880a782d8c4083baa1073b0de22bf67c7d4c0000000000000000000000002776ca69dd33bce8b1cec3e6a51c7268313cb6d9000000000000000000000000847f1db65ce5e9232142c0ca5493c8df09035ef80000000000000000000000000ee44f027548fdcce4174e4471733346222cf446000000000000000000000000af87f44937eda988b6b1cce54d71fd2a6ae25ece000000000000000000000000a9293285ad996d39e187e265bf19ff9f6d58c2b3000000000000000000000000f2537e61d09eba3c0a1e3d2c5ee118deab383343000000000000000000000000974f61efc0dc1bfcfc86312caf88b7881c69fe9d00000000000000000000000076a38674d9bccab9400f06e01b8493cacaed54940000000000000000000000005bfe4e0780057bfb9553818febf3ae87424804fd000000000000000000000000209fdd51c58a081891c615adf5f1b4d59266e4e2000000000000000000000000ba324d55462b0822d9aab6cab88ab12bf7376913000000000000000000000000ad38221bede1e10715c406b89dddd263e85c22c1000000000000000000000000c67ddd52e383df02e3365d7f8569c8a439181d7a0000000000000000000000001261ef5836770e577acee2c1f235339b0639a24a000000000000000000000000d38910fe35774693713c748b98e59ca99acd028d00000000000000000000000072824bb44b35eab26e88e9e1f5e33e2ca66d761100000000000000000000000099ed616b50061b18acfd3aebcc40f6d5053c0afd00000000000000000000000019c1314ddb8089cf54ccfc9743ed4f6b4de16bc3000000000000000000000000af6e61096b8f2ec43f8bb8f74465df5ed0584189000000000000000000000000f55c26bc0da0a50677abb0329344d63e9791cc64000000000000000000000000a7790d6f5a9b708d77d2989ad7cb9403ba215d9d0000000000000000000000002f8c738d0dca8a63d5384ac9271c3b6c2f7b82e8000000000000000000000000735540a4e419242624e333401ed0cff11d8e394c000000000000000000000000fc61dde6fcd54d2d602298097a92810752ea2216000000000000000000000000a474f7df19c449350de2d8ed9530a96bd489e6180000000000000000000000007e1e3abd8dd50a480f1e94ace42024809a9976b00000000000000000000000007a5b3261d1e318b2b1d7cd8395fec2d114cf1e0700000000000000000000000022b8526cb9bf35870fb3b64d2830a342662fae1a000000000000000000000000273c1e818ad4323b261e6989adcd6ec292f39eb6000000000000000000000000b8bc2b71537cdbe0cbbebe3743d456751e48b437000000000000000000000000e5eb897aee540b3b61f9657ae6c7b3a8817e5bbc00000000000000000000000076bc03f1e645601bb46e1b913ad19f5cc68ead9e0000000000000000000000004e907e40d9e14d137ca9a4dc4e837526ab24a75a0000000000000000000000003e872672b1aa1928e2c595c792d3660bdb9bf009000000000000000000000000002c24046f6238c8a23d5e66af11e82b413a747300000000000000000000000016ed4972d859c4863f5aac4827ed35082fefe2dd00000000000000000000000093d7cd3e4b5b02a57ac94536c49e09b3b678298c0000000000000000000000002fe6d5d36fae80f36b49c41cc669495a15d61f1c000000000000000000000000ee336bb5175862703e2d121b2d62fe3230defa8d000000000000000000000000ca40f22df970cfac0fb548f4681544c0fb76d2260000000000000000000000004a0e6f7c6a68b009549f7022fc772f04f0b3d267000000000000000000000000500739e1734d4361a642e3dc1947eab3312cbe020000000000000000000000007f9777b9460f6f2e2fec0737b3ecb764135569cb00000000000000000000000054e8086deb08c761eaa0aa77b6b0846c9d2ffbb2000000000000000000000000c54885669813cabbf2809e1abfc8f64ff963081a000000000000000000000000cebe744ffe69ba5c903a648a9ee1e7951d06d35c000000000000000000000000c4b4c4c30ee327faf976fea34d91ada6dc65aae3000000000000000000000000cd52cd7fe07189bd16d236eb1da587bb9d2fbc690000000000000000000000009ac44065540cb31b69c9f18c22392c6eb6cbb7b30000000000000000000000000a791b8d6c823a58f59782bf901141d2e21d3867000000000000000000000000dc75d344dfcf394da9ef25500b83551809fd7c23000000000000000000000000d76a3139322bf6edcdd8e71cbe225a80c94a43d000000000000000000000000031a3135956b33f9936791e5f11546e7f9a11d91f00000000000000000000000067a045c776f4422d56d6f59c70fb42bba05be7c9000000000000000000000000f3e27a226c9c1c0169119dad4c6f8ce8b07342b2000000000000000000000000eaebad11a448fc6b3fdb40f53bbbcaefc9e12720000000000000000000000000715b03b71847e76ddef8ac19dc0bdf92f2fee7630000000000000000000000005032f427d9b911ebe63ad99f707e1fafa7b9a23b0000000000000000000000001758fe2c3492bed3f6e2a95247b6df04fbdc4cfc00000000000000000000000016aad9880d1504fef8b64ed16ee33e7d42bd9321000000000000000000000000edc03dda760e5793ecda69d9837956f246e2cc7c0000000000000000000000006a89f27f7dee87a4b69167ca795dbe07478de68900000000000000000000000091d762a023ee879222728725d1b4cfeca45aef1e00000000000000000000000046a3d887a93389e59c1081a96c23578a8a97e87100000000000000000000000072da397dfb39e9bdaf2e6e5852886a8e0f422b07000000000000000000000000e2c8c2258ab538d28501e23e938310946df84ac1000000000000000000000000bb022fa11c82a529f67d850029ae9ba111d6ae580000000000000000000000005dc5002fe446e7526aca4c30068f162ff806953b0000000000000000000000002e254caaf6662a65f28d0e6826e23305897686eb0000000000000000000000002da4b85b57ed5445eb9d92190f1cc31f70791cc7000000000000000000000000127f2b9ba900b3698589d66e22b457dcc2e988f00000000000000000000000001c5f2610033871625f91ef349ff5ad29168a5f4f000000000000000000000000381c1fbbf9821e2d3af022d9bfcd38a223d8d733000000000000000000000000af9ca6bf95489773edb5003abf3f96e137b0dce00000000000000000000000002187b84a100effa2c5c5bf6d31a959b9338a799d000000000000000000000000922875f3ac04bc4ee3f249216d83f5a3b30a1f83000000000000000000000000430d3d400469a788bba266a718916c886400c68e000000000000000000000000f2d811c2ac3e8019c18ff3530777c072cc8a580a000000000000000000000000dc5c5b4ef174c2c57fe6c712851729fa86496622000000000000000000000000e59e04766142949d054abfa7a9ae29147a2dbde5000000000000000000000000616e72cb01e23dfb60b412580fa26739185373ca0000000000000000000000005a1e7ef74e0fd75a2ee88cd6cec34ef3362efd0600000000000000000000000056f857ba48c51822a9ee3f0e658c608bc26732b10000000000000000000000008143cbd82f9329f112076510706639c58e7fee2f000000000000000000000000cb00f2d13d7840c4b60b995edc76d250e2bce4b600000000000000000000000016bdc0f3a107af309153772089298277891026d4000000000000000000000000d21fd880f77730e3fe563a3b884e2231426d423e0000000000000000000000000b2eed6a84dd1ba7d8ece83a1665bdb5a4428b94000000000000000000000000ab1ca7e2322b23c5a51c9704684efd8ce32a799e00000000000000000000000091e5e85ee93ca51b26137cb0bafd67ba9de477cc0000000000000000000000006ff0825bc7d48865078fe3413e4be66bd41677e0000000000000000000000000794cdc7d28cda9720186e8f81c57a084eef049b200000000000000000000000000000000000000000000000000000000000002bc0000000000000000000000000000000000000000000000000000005e627181b400000000000000000000000000000000000000000000000000002bc6d43b0bd200000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000e22aaf624700000000000000000000000000000000000000000000000000000843a372ad7e00000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000017ee175821700000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000ecda0e58cf00000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000001726a3162700000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000002bb6cd2c9a060000000000000000000000000000000000000000000000000000000e3f2948b50000000000000000000000000000000000000000000000000000001396d8c3fa0000000000000000000000000000000000000000000000000000001726a3162700000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000031dd107e7c00000000000000000000000000000000000000000000000000000011cef39ae3000000000000000000000000000000000000000000000000000001f7fa546c210000000000000000000000000000000000000000000000000000006ad9b5a153000000000000000000000000000000000000000000000000000000d05bbbc76300000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000903f82003100000000000000000000000000000000000000000000000000000051eb2d62150000000000000000000000000000000000000000000000000000019997e2ea6c0000000000000000000000000000000000000000000000000000001396d8c3fa000000000000000000000000000000000000000000000000000000f5c188264100000000000000000000000000000000000000000000000000000050234838fe000000000000000000000000000000000000000000000000000001597ba9233a00000000000000000000000000000000000000000000000000000010070e71cc0000000000000000000000000000000000000000000000000000004903b394a40000000000000000000000000000000000000000000000000000004e5b630fe8000000000000000000000000000000000000000000000000000000b215840ce10000000000000000000000000000000000000000000000000000015b438e4c5000000000000000000000000000000000000000000000000000000051eb2d621500000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000005742dcdd5900000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000590ac206700000000000000000000000000000000000000000000000000000007ca8a93c370000000000000000000000000000000000000000000000000000005c9a8c589e0000000000000000000000000000000000000000000000000000001396d8c3fa000000000000000000000000000000000000000000000000000000602a56aacb000000000000000000000000000000000000000000000000000000155ebded1000000000000000000000000000000000000000000000000000000073c12f6ec5000000000000000000000000000000000000000000000000000000557af7b443000000000000000000000000000000000000000000000000000000c93c2723080000000000000000000000000000000000000000000000000000006911d0783d000000000000000000000000000000000000000000000000000000f95152786e00000000000000000000000000000000000000000000000000000051eb2d621500000000000000000000000000000000000000000000000000000167bad26bf00000000000000000000000000000000000000000000000000000001ab66d685400000000000000000000000000000000000000000000000000000051eb2d6215000000000000000000000000000000000000000000000000000000356cdad0a9000000000000000000000000000000000000000000000000000000cccbf17536000000000000000000000000000000000000000000000000000000d3eb861991000000000000000000000000000000000000000000000000000000eb12292fb80000000000000000000000000000000000000000000000000000015ed3589e7e0000000000000000000000000000000000000000000000000000005ad2a72f8700000000000000000000000000000000000000000000000000002bca64055e000000000000000000000000000000000000000000000000000000003c8c6f750400000000000000000000000000000000000000000000000000000031dd107e7c0000000000000000000000000000000000000000000000000000002c856103380000000000000000000000000000000000000000000000000000004acb98bdba000000000000000000000000000000000000000000000000000000c77441f9f2000000000000000000000000000000000000000000000000000000155ebded100000000000000000000000000000000000000000000000000000001ab66d685400000000000000000000000000000000000000000000000000000107907bc124000000000000000000000000000000000000000000000000000000eea1f381e6000000000000000000000000000000000000000000000000000000155ebded1000000000000000000000000000000000000000000000000000000010070e71cc00000000000000000000000000000000000000000000000000000041e41ef049000000000000000000000000000000000000000000000000000000401c39c73200000000000000000000000000000000000000000000000000000043ac04195f0000000000000000000000000000000000000000000000000000004acb98bdba000000000000000000000000000000000000000000000000000000239de735c60000000000000000000000000000000000000000000000000000008e779cd71a000000000000000000000000000000000000000000000000000000d223a0f07a0000000000000000000000000000000000000000000000000000018990d4789f000000000000000000000000000000000000000000000000000000e7825edd8b0000000000000000000000000000000000000000000000000000004c937de6d1000000000000000000000000000000000000000000000000000000f069d8aafc000000000000000000000000000000000000000000000000000000e5ba79b47400000000000000000000000000000000000000000000000000000187c8ef4f890000000000000000000000000000000000000000000000000000003c8c6f750400000000000000000000000000000000000000000000000000000033a4f5a793000000000000000000000000000000000000000000000000000000de9ae51019000000000000000000000000000000000000000000000000000001a60f270a0b000000000000000000000000000000000000000000000000000000590ac206700000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000473bce6b8d00000000000000000000000000000000000000000000000000000155ebded10c00000000000000000000000000000000000000000000000000000087580832bf0000000000000000000000000000000000000000000000000000001ab66d6854000000000000000000000000000000000000000000000000000000473bce6b8d000000000000000000000000000000000000000000000000000001726a316278000000000000000000000000000000000000000000000000000000cb040c4c1f0000000000000000000000000000000000000000000000000000018b58b9a1b6000000000000000000000000000000000000000000000000000000f231bdd41300000000000000000000000000000000000000000000000000000165f2ed42d9000000000000000000000000000000000000000000000000000000473bce6b8d0000000000000000000000000000000000000000000000000000016b4a9cbe1d0000000000000000000000000000000000000000000000000000016982b7950600000000000000000000000000000000000000000000000000000053b3128b2c0000000000000000000000000000000000000000000000000000017432168b8f000000000000000000000000000000000000000000000000000000d3eb861991000000000000000000000000000000000000000000000000000000ecda0e58cf000000000000000000000000000000000000000000000000000000e94a4406a200000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000038fca522d7000000000000000000000000000000000000000000000000000000e94a4406a2000000000000000000000000000000000000000000000000000001726a316278000000000000000000000000000000000000000000000000000000eea1f381e6000000000000000000000000000000000000000000000000000000e062ca3930000000000000000000000000000000000000000000000000000000d9433594d50000000000000000000000000000000000000000000000000000010400b16ef7000000000000000000000000000000000000000000000000000000d5b36b42a70000000000000000000000000000000000000000000000000000019b5fc81383000000000000000000000000000000000000000000000000000000658206260f0000000000000000000000000000000000000000000000000000002abd7bda2100000000000000000000000000000000000000000000000000000157b3c3fa230000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000557af7b4430000000000000000000000000000000000000000000000000000018b58b9a1b60000000000000000000000000000000000000000000000000000018ee883f3e40000000000000000000000000000000000000000000000000000010070e71cc900000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000001ab66d685400000000000000000000000000000000000000000000000000000187c8ef4f8900000000000000000000000000000000000000000000000000000030152b556500000000000000000000000000000000000000000000000000000170a24c39610000000000000000000000000000000000000000000000000000009207672948000000000000000000000000000000000000000000000000000000d05bbbc7630000000000000000000000000000000000000000000000000000006749eb4f260000000000000000000000000000000000000000000000000000008cafb7ae030000000000000000000000000000000000000000000000000000017432168b8f000000000000000000000000000000000000000000000000000000038fca522d0000000000000000000000000000000000000000000000000000005742dcdd59000000000000000000000000000000000000000000000000000000db0b1abdec0000000000000000000000000000000000000000000000000000001396d8c3fa00000000000000000000000000000000000000000000000000000186010a2672000000000000000000000000000000000000000000000000000000e062ca3930000000000000000000000000000000000000000000000000000001609b3dc795000000000000000000000000000000000000000000000000000000e3f2948b5d000000000000000000000000000000000000000000000000000000c77441f9f2000000000000000000000000000000000000000000000000000000db0b1abdec0000000000000000000000000000000000000000000000000000005742dcdd59000000000000000000000000000000000000000000000000000000f95152786e0000000000000000000000000000000000000000000000000000002565cc5edd00000000000000000000000000000000000000000000000000000177c1e0ddbc000000000000000000000000000000000000000000000000000000557af7b4430000000000000000000000000000000000000000000000000000018990d4789f000000000000000000000000000000000000000000000000000000fb1937a1850000000000000000000000000000000000000000000000000000007750f9c0f300000000000000000000000000000000000000000000000000000050234838fe00000000000000000000000000000000000000000000000000000087580832bf0000000000000000000000000000000000000000000000000000005ad2a72f870000000000000000000000000000000000000000000000000000010238cc45e0000000000000000000000000000000000000000000000000000000d3eb861991000000000000000000000000000000000000000000000000000000fb1937a18500000000000000000000000000000000000000000000000000000038fca522d7000000000000000000000000000000000000000000000000000000e5ba79b4740000000000000000000000000000000000000000000000000000004e5b630fe8000000000000000000000000000000000000000000000000000000590ac20670000000000000000000000000000000000000000000000000000000be8cc82c80000000000000000000000000000000000000000000000000000000f231bdd4130000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000f069d8aafc000000000000000000000000000000000000000000000000000000557af7b443000000000000000000000000000000000000000000000000000000eea1f381e60000000000000000000000000000000000000000000000000000015b438e4c5000000000000000000000000000000000000000000000000000000182713fd4450000000000000000000000000000000000000000000000000000000c77441f9f0000000000000000000000000000000000000000000000000000017989c606d3000000000000000000000000000000000000000000000000000001324df79b4600000000000000000000000000000000000000000000000000000071f94a45ae00000000000000000000000000000000000000000000000000000075891497dc00000000000000000000000000000000000000000000000000000050234838fe000000000000000000000000000000000000000000000000000001726a3162780000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000cccbf17536000000000000000000000000000000000000000000000000000000d77b506bbe0000000000000000000000000000000000000000000000000000016982b79506000000000000000000000000000000000000000000000000000000f5c1882641000000000000000000000000000000000000000000000000000000f95152786e0000000000000000000000000000000000000000000000000000005ad2a72f87000000000000000000000000000000000000000000000000000000ecda0e58cf00000000000000000000000000000000000000000000000000000075891497dc000000000000000000000000000000000000000000000000000000f7896d4f5700000000000000000000000000000000000000000000000000000053b3128b2c000000000000000000000000000000000000000000000000000000fb1937a1850000000000000000000000000000000000000000000000000000006e697ff3810000000000000000000000000000000000000000000000000000016b4a9cbe1d00000000000000000000000000000000000000000000000000000053b3128b2c00000000000000000000000000000000000000000000000000000063ba20fcf9000000000000000000000000000000000000000000000000000000c93c27230800000000000000000000000000000000000000000000000000000063ba20fcf9000000000000000000000000000000000000000000000000000001843924fd5b000000000000000000000000000000000000000000000000000000557af7b4430000000000000000000000000000000000000000000000000000005ad2a72f8700000000000000000000000000000000000000000000000000000170a24c396100000000000000000000000000000000000000000000000000000175f9fbb4a5000000000000000000000000000000000000000000000000000000f069d8aafc0000000000000000000000000000000000000000000000000000005c9a8c589e000000000000000000000000000000000000000000000000000000ce93d69e4d00000000000000000000000000000000000000000000000000000043ac04195f000000000000000000000000000000000000000000000000000000ce93d69e4d000000000000000000000000000000000000000000000000000000038fca522d0000000000000000000000000000000000000000000000000000004e5b630fe80000000000000000000000000000000000000000000000000000008cafb7ae0300000000000000000000000000000000000000000000000000000011cef39ae3000000000000000000000000000000000000000000000000000000bcc4e303690000000000000000000000000000000000000000000000000000003ac48a4bee000000000000000000000000000000000000000000000000000000356cdad0a9000000000000000000000000000000000000000000000000000000d223a0f07a0000000000000000000000000000000000000000000000000000005742dcdd59000000000000000000000000000000000000000000000000000000d223a0f07a0000000000000000000000000000000000000000000000000000007031651c980000000000000000000000000000000000000000000000000000005c9a8c589e00000000000000000000000000000000000000000000000000000051eb2d62150000000000000000000000000000000000000000000000000000005c9a8c589e00000000000000000000000000000000000000000000000000000001c7e52916000000000000000000000000000000000000000000000000000000ce93d69e4d000000000000000000000000000000000000000000000000000000dcd2ffe7020000000000000000000000000000000000000000000000000000002abd7bda21000000000000000000000000000000000000000000000000000000d05bbbc763000000000000000000000000000000000000000000000000000000557af7b4430000000000000000000000000000000000000000000000000000001396d8c3fa0000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000001396d8c3fa0000000000000000000000000000000000000000000000000000004e5b630fe80000000000000000000000000000000000000000000000000000010238cc45e0000000000000000000000000000000000000000000000000000000d3eb861991000000000000000000000000000000000000000000000000000000fb1937a1850000000000000000000000000000000000000000000000000000008cafb7ae03000000000000000000000000000000000000000000000000000000f95152786e0000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000d9433594d50000000000000000000000000000000000000000000000000000005ad2a72f870000000000000000000000000000000000000000000000000000001726a3162700000000000000000000000000000000000000000000000000000187c8ef4f890000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000017432168b8f00000000000000000000000000000000000000000000000000000053b3128b2c000000000000000000000000000000000000000000000000000000de9ae510190000000000000000000000000000000000000000000000000000004e5b630fe8000000000000000000000000000000000000000000000000000000155ebded100000000000000000000000000000000000000000000000000000006e697ff381000000000000000000000000000000000000000000000000000000b04d9ee3ca000000000000000000000000000000000000000000000000000001642b0819c2000000000000000000000000000000000000000000000000000000ecda0e58cf00000000000000000000000000000000000000000000000000000092076729480000000000000000000000000000000000000000000000000000005742dcdd59000000000000000000000000000000000000000000000000000000c5ac5cd0db00000000000000000000000000000000000000000000000000000149749ab16d0000000000000000000000000000000000000000000000000000006ad9b5a153000000000000000000000000000000000000000000000000000000b5a54e5f0e00000000000000000000000000000000000000000000000000000051eb2d6215000000000000000000000000000000000000000000000000000000155ebded10000000000000000000000000000000000000000000000000000000f231bdd413000000000000000000000000000000000000000000000000000000eea1f381e60000000000000000000000000000000000000000000000000000002e4d462c4f0000000000000000000000000000000000000000000000000000006ca19aca6a00000000000000000000000000000000000000000000000000000051eb2d621500000000000000000000000000000000000000000000000000000197cffdc155000000000000000000000000000000000000000000000000000000e062ca39300000000000000000000000000000000000000000000000000000017ee175821700000000000000000000000000000000000000000000000000000063ba20fcf9000000000000000000000000000000000000000000000000000000cb040c4c1f000000000000000000000000000000000000000000000000000000155ebded100000000000000000000000000000000000000000000000000000001ab66d6854000000000000000000000000000000000000000000000000000000d77b506bbe00000000000000000000000000000000000000000000000000000105c896980d000000000000000000000000000000000000000000000000000000e3f2948b5d0000000000000000000000000000000000000000000000000000008ae7d284ed000000000000000000000000000000000000000000000000000000ecda0e58cf000000000000000000000000000000000000000000000000000000de9ae51019000000000000000000000000000000000000000000000000000000b5a54e5f0e0000000000000000000000000000000000000000000000000000016b4a9cbe1d0000000000000000000000000000000000000000000000000000016b4a9cbe1d00000000000000000000000000000000000000000000000000000033a4f5a7930000000000000000000000000000000000000000000000000000005c9a8c589e0000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000155ebded10000000000000000000000000000000000000000000000000000000590ac20670000000000000000000000000000000000000000000000000000000155ebded100000000000000000000000000000000000000000000000000000017d19905900000000000000000000000000000000000000000000000000000000d77b506bbe0000000000000000000000000000000000000000000000000000004903b394a4000000000000000000000000000000000000000000000000000000b215840ce100000000000000000000000000000000000000000000000000000031dd107e7c000000000000000000000000000000000000000000000000000000e94a4406a2000000000000000000000000000000000000000000000000000000557af7b44300000000000000000000000000000000000000000000000000000053b3128b2c0000000000000000000000000000000000000000000000000000008038738e64000000000000000000000000000000000000000000000000000000e7825edd8b0000000000000000000000000000000000000000000000000000017989c606d3000000000000000000000000000000000000000000000000000000de9ae51019000000000000000000000000000000000000000000000000000000f3f9a2fd2a0000000000000000000000000000000000000000000000000000004e5b630fe8000000000000000000000000000000000000000000000000000000be8cc82c80000000000000000000000000000000000000000000000000000000d9433594d50000000000000000000000000000000000000000000000000000005e627181b40000000000000000000000000000000000000000000000000000008038738e6400000000000000000000000000000000000000000000000000000050234838fe00000000000000000000000000000000000000000000000000000041e41ef0490000000000000000000000000000000000000000000000000000016b4a9cbe1d0000000000000000000000000000000000000000000000000000014b3c7fda840000000000000000000000000000000000000000000000000000005742dcdd59000000000000000000000000000000000000000000000000000000155ebded10000000000000000000000000000000000000000000000000000001609b3dc7950000000000000000000000000000000000000000000000000000001ab66d68540000000000000000000000000000000000000000000000000000008cafb7ae0300000000000000000000000000000000000000000000000000000041e41ef049000000000000000000000000000000000000000000000000000000891fed5bd60000000000000000000000000000000000000000000000000000015423f9a7f6000000000000000000000000000000000000000000000000000000c054ad5597000000000000000000000000000000000000000000000000000000ecda0e58cf000000000000000000000000000000000000000000000000000000590ac2067000000000000000000000000000000000000000000000000000000197cffdc1550000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000007ae0c41320000000000000000000000000000000000000000000000000000000d77b506bbe000000000000000000000000000000000000000000000000000000fb1937a18500000000000000000000000000000000000000000000000000000051eb2d6215000000000000000000000000000000000000000000000000000000590ac20670000000000000000000000000000000000000000000000000000000acbdd4919d0000000000000000000000000000000000000000000000000000017989c606d300000000000000000000000000000000000000000000000000000043ac04195f000000000000000000000000000000000000000000000000000000155ebded100000000000000000000000000000000000000000000000000000004e5b630fe80000000000000000000000000000000000000000000000000000007e708e654e000000000000000000000000000000000000000000000000000000155ebded1000000000000000000000000000000000000000000000000000000170a24c3961000000000000000000000000000000000000000000000000000000602a56aacb0000000000000000000000000000000000000000000000000000019d27ad3c9a000000000000000000000000000000000000000000000000000000e062ca393000000000000000000000000000000000000000000000000000000187c8ef4f89000000000000000000000000000000000000000000000000000000e5ba79b474000000000000000000000000000000000000000000000000000001195f6f5c0700000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000008038738e640000000000000000000000000000000000000000000000000000001396d8c3fa000000000000000000000000000000000000000000000000000000d5b36b42a70000000000000000000000000000000000000000000000000000005ad2a72f8700000000000000000000000000000000000000000000000000000170a24c39610000000000000000000000000000000000000000000000000000015b438e4c500000000000000000000000000000000000000000000000000000007031651c980000000000000000000000000000000000000000000000000000004c937de6d1000000000000000000000000000000000000000000000000000000d5b36b42a700000000000000000000000000000000000000000000000000000031dd107e7c000000000000000000000000000000000000000000000000000000fea901f3b2000000000000000000000000000000000000000000000000000000155ebded100000000000000000000000000000000000000000000000000000005742dcdd590000000000000000000000000000000000000000000000000000006749eb4f2600000000000000000000000000000000000000000000000000000050234838fe000000000000000000000000000000000000000000000000000000e7825edd8b0000000000000000000000000000000000000000000000000000006ad9b5a153000000000000000000000000000000000000000000000000000000e5ba79b47400000000000000000000000000000000000000000000000000000187c8ef4f890000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000004573e94276000000000000000000000000000000000000000000000000000000a0469071fd000000000000000000000000000000000000000000000000000000b76d338825000000000000000000000000000000000000000000000000000000602a56aacb0000000000000000000000000000000000000000000000000000006911d0783d0000000000000000000000000000000000000000000000000000005c9a8c589e000000000000000000000000000000000000000000000000000000a92e0a3f6f0000000000000000000000000000000000000000000000000000002e4d462c4f000000000000000000000000000000000000000000000000000000975f16a48c0000000000000000000000000000000000000000000000000000001ab66d685400000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000007750f9c0f300000000000000000000000000000000000000000000000000000051eb2d62150000000000000000000000000000000000000000000000000000016b4a9cbe1d000000000000000000000000000000000000000000000000000000dcd2ffe7020000000000000000000000000000000000000000000000000000005742dcdd59000000000000000000000000000000000000000000000000000000e94a4406a2000000000000000000000000000000000000000000000000000000e94a4406a2000000000000000000000000000000000000000000000000000000d3eb8619910000000000000000000000000000000000000000000000000000005c9a8c589e0000000000000000000000000000000000000000000000000000006ca19aca6a0000000000000000000000000000000000000000000000000000005e627181b400000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000073c12f6ec50000000000000000000000000000000000000000000000000000010ce82b3c6800000000000000000000000000000000000000000000000000000073c12f6ec500000000000000000000000000000000000000000000000000000105c896980d00000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000001396d8c3fa000000000000000000000000000000000000000000000000000000d223a0f07a00000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000be8cc82c80000000000000000000000000000000000000000000000000000000975f16a48c000000000000000000000000000000000000000000000000000000e5ba79b474000000000000000000000000000000000000000000000000000000d5b36b42a7000000000000000000000000000000000000000000000000000000e3f2948b5d0000000000000000000000000000000000000000000000000000007918deea09000000000000000000000000000000000000000000000000000000f5c18826410000000000000000000000000000000000000000000000000000003ac48a4bee000000000000000000000000000000000000000000000000000000b3dd6935f80000000000000000000000000000000000000000000000000000002565cc5edd000000000000000000000000000000000000000000000000000000eea1f381e600000000000000000000000000000000000000000000000000000050234838fe000000000000000000000000000000000000000000000000000000cccbf17536000000000000000000000000000000000000000000000000000000a59e3fed42000000000000000000000000000000000000000000000000000000b04d9ee3ca000000000000000000000000000000000000000000000000000000de9ae51019000000000000000000000000000000000000000000000000000000e3f2948b5d0000000000000000000000000000000000000000000000000000017d1990590000000000000000000000000000000000000000000000000000000053b3128b2c000000000000000000000000000000000000000000000000000000eea1f381e6000000000000000000000000000000000000000000000000000000e3f2948b5d0000000000000000000000000000000000000000000000000000005742dcdd59000000000000000000000000000000000000000000000000000000d5b36b42a70000000000000000000000000000000000000000000000000000019d27ad3c9a000000000000000000000000000000000000000000000000000000cccbf17536000000000000000000000000000000000000000000000000000001408d20e3fb000000000000000000000000000000000000000000000000000000891fed5bd6000000000000000000000000000000000000000000000000000000155ebded100000000000000000000000000000000000000000000000000000000e3f2948b5000000000000000000000000000000000000000000000000000000239de735c6000000000000000000000000000000000000000000000000000000c93c2723080000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000e22aaf62470000000000000000000000000000000000000000000000000000007918deea09000000000000000000000000000000000000000000000000000000d5b36b42a700000000000000000000000000000000000000000000000000000011cef39ae300000000000000000000000000000000000000000000000000000093cf4c525e0000000000000000000000000000000000000000000000000000006911d0783d000000000000000000000000000000000000000000000000000000356cdad0a900000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000019d27ad3c9a0000000000000000000000000000000000000000000000000000004573e942760000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000de9ae5101900000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000e7825edd8b0000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000001396d8c3fa00000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000087580832bf00000000000000000000000000000000000000000000000000000165f2ed42d9000000000000000000000000000000000000000000000000000000d5b36b42a70000000000000000000000000000000000000000000000000000005ad2a72f87000000000000000000000000000000000000000000000000000000d77b506bbe0000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000001ab66d685400000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000d9433594d5000000000000000000000000000000000000000000000000000000f95152786e000000000000000000000000000000000000000000000000000000de9ae5101900000000000000000000000000000000000000000000000000000050234838fe0000000000000000000000000000000000000000000000000000004e5b630fe8000000000000000000000000000000000000000000000000000000a3d65ac42b000000000000000000000000000000000000000000000000000000c5ac5cd0db00000000000000000000000000000000000000000000000000000063ba20fcf900000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000004c937de6d1000000000000000000000000000000000000000000000000000000602a56aacb00000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000061f23bd3e2000000000000000000000000000000000000000000000000000000356cdad0a900000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000557af7b443000000000000000000000000000000000000000000000000000000eb12292fb8000000000000000000000000000000000000000000000000000000f3f9a2fd2a00000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000cb040c4c1f000000000000000000000000000000000000000000000000000000c77441f9f200000000000000000000000000000000000000000000000000000186010a267200000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000006749eb4f26000000000000000000000000000000000000000000000000000000dcd2ffe702000000000000000000000000000000000000000000000000000000cb040c4c1f000000000000000000000000000000000000000000000000000000bcc4e30369000000000000000000000000000000000000000000000000000000658206260f00000000000000000000000000000000000000000000000000000150942f55c800000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000003e54549e1b00000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000ce93d69e4d00000000000000000000000000000000000000000000000000000186010a267200000000000000000000000000000000000000000000000000000149749ab16d000000000000000000000000000000000000000000000000000000e5ba79b474000000000000000000000000000000000000000000000000000000658206260f000000000000000000000000000000000000000000000000000000f3f9a2fd2a00000000000000000000000000000000000000000000000000000011cef39ae30000000000000000000000000000000000000000000000000000017b51ab2fea0000000000000000000000000000000000000000000000000000017989c606d3000000000000000000000000000000000000000000000000000001843924fd5b00000000000000000000000000000000000000000000000000000167bad26bf0000000000000000000000000000000000000000000000000000000c93c272308000000000000000000000000000000000000000000000000000000200e1ce3990000000000000000000000000000000000000000000000000000005ad2a72f870000000000000000000000000000000000000000000000000000004573e942760000000000000000000000000000000000000000000000000000008ae7d284ed0000000000000000000000000000000000000000000000000000009e7eab48e700000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000bcc4e3036900000000000000000000000000000000000000000000000000000031dd107e7c0000000000000000000000000000000000000000000000000000009926fbcda3000000000000000000000000000000000000000000000000000000602a56aacb0000000000000000000000000000000000000000000000000000001726a3162700000000000000000000000000000000000000000000000000000050234838fe0000000000000000000000000000000000000000000000000000005ad2a72f870000000000000000000000000000000000000000000000000000008cafb7ae03000000000000000000000000000000000000000000000000000000c93c27230800000000000000000000000000000000000000000000000000000053b3128b2c000000000000000000000000000000000000000000000000000000557af7b44300000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000be8cc82c80000000000000000000000000000000000000000000000000000000fce11cca9c000000000000000000000000000000000000000000000000000000d05bbbc7630000000000000000000000000000000000000000000000000000007031651c980000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000cccbf17536000000000000000000000000000000000000000000000000000000e94a4406a20000000000000000000000000000000000000000000000000000009926fbcda300000000000000000000000000000000000000000000000000000038fca522d70000000000000000000000000000000000000000000000000000004c937de6d100000000000000000000000000000000000000000000000000000043ac04195f0000000000000000000000000000000000000000000000000000008ae7d284ed0000000000000000000000000000000000000000000000000000004c937de6d10000000000000000000000000000000000000000000000000000001ab66d6854000000000000000000000000000000000000000000000000000000602a56aacb000000000000000000000000000000000000000000000000000000e062ca3930000000000000000000000000000000000000000000000000000000de9ae5101900000000000000000000000000000000000000000000000000000033a4f5a7930000000000000000000000000000000000000000000000000000015b438e4c50000000000000000000000000000000000000000000000000000000e062ca393000000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000fce11cca9c000000000000000000000000000000000000000000000000000000c3e477a7c400000000000000000000000000000000000000000000000000000028f596b10a000000000000000000000000000000000000000000000000000000eea1f381e600000000000000000000000000000000000000000000000000000085902309a800000000000000000000000000000000000000000000000000000050234838fe00000000000000000000000000000000000000000000000000000050234838fe000000000000000000000000000000000000000000000000000000272db187f40000000000000000000000000000000000000000000000000000003e54549e1b0000000000000000000000000000000000000000000000000000001726a3162700000000000000000000000000000000000000000000000000000030152b55650000000000000000000000000000000000000000000000000000004acb98bdba00000000000000000000000000000000000000000000000000000043ac04195f0000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000c054ad55970000000000000000000000000000000000000000000000000000003734bff9c00000000000000000000000000000000000000000000000000000017d1990590000000000000000000000000000000000000000000000000000000093cf4c525e000000000000000000000000000000000000000000000000000000356cdad0a900000000000000000000000000000000000000000000000000000050234838fe00000000000000000000000000000000000000000000000000000073c12f6ec500000000000000000000000000000000000000000000000000000051eb2d6215000000000000000000000000000000000000000000000000000000dcd2ffe70200000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000aaf5ef68860000000000000000000000000000000000000000000000000000008e779cd71a0000000000000000000000000000000000000000000000000000008ae7d284ed0000000000000000000000000000000000000000000000000000010238cc45e000000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000009aeee0f6b90000000000000000000000000000000000000000000000000000007750f9c0f30000000000000000000000000000000000000000000000000000009207672948000000000000000000000000000000000000000000000000000000ecda0e58cf000000000000000000000000000000000000000000000000000000d9433594d5000000000000000000000000000000000000000000000000000000f231bdd4130000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000007918deea09000000000000000000000000000000000000000000000000000000e7825edd8b000000000000000000000000000000000000000000000000000000602a56aacb0000000000000000000000000000000000000000000000000000004903b394a40000000000000000000000000000000000000000000000000000006749eb4f26000000000000000000000000000000000000000000000000000000cb040c4c1f00000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000590ac2067000000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000d223a0f07a00000000000000000000000000000000000000000000000000000021d6020caf0000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000001396d8c3fa00000000000000000000000000000000000000000000000000000087580832bf0000000000000000000000000000000000000000000000000000010ce82b3c680000000000000000000000000000000000000000000000000000005c9a8c589e000000000000000000000000000000000000000000000000000000590ac206700000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000fea901f3b200000000000000000000000000000000000000000000000000000050234838fe000000000000000000000000000000000000000000000000000000d223a0f07a0000000000000000000000000000000000000000000000000000018ee883f3e4000000000000000000000000000000000000000000000000000000356cdad0a9000000000000000000000000000000000000000000000000000000eea1f381e60000000000000000000000000000000000000000000000000000008ae7d284ed000000000000000000000000000000000000000000000000000000155ebded10000000000000000000000000000000000000000000000000000000b215840ce1000000000000000000000000000000000000000000000000000000b93518b13c0000000000000000000000000000000000000000000000000000017432168b8f00000000000000000000000000000000000000000000000000000085902309a8000000000000000000000000000000000000000000000000000000473bce6b8d0000000000000000000000000000000000000000000000000000002abd7bda210000000000000000000000000000000000000000000000000000007031651c9800000000000000000000000000000000000000000000000000000053b3128b2c000000000000000000000000000000000000000000000000000000eb12292fb800000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000007031651c9800000000000000000000000000000000000000000000000000000041e41ef049000000000000000000000000000000000000000000000000000000e062ca39300000000000000000000000000000000000000000000000000000005e627181b4000000000000000000000000000000000000000000000000000000d77b506bbe000000000000000000000000000000000000000000000000000000658206260f000000000000000000000000000000000000000000000000000000272db187f4000000000000000000000000000000000000000000000000000000e5ba79b47400000000000000000000000000000000000000000000000000000050234838fe000000000000000000000000000000000000000000000000000000e7825edd8b00000000000000000000000000000000000000000000000000000155ebded10c0000000000000000000000000000000000000000000000000000005742dcdd590000000000000000000000000000000000000000000000000000001ab66d6854000000000000000000000000000000000000000000000000000000155ebded10000000000000000000000000000000000000000000000000000000d9433594d5000000000000000000000000000000000000000000000000000000272db187f4000000000000000000000000000000000000000000000000000000c93c2723080000000000000000000000000000000000000000000000000000006ad9b5a15300000000000000000000000000000000000000000000000000000010070e71cc0000000000000000000000000000000000000000000000000000009597317b75000000000000000000000000000000000000000000000000000000f3f9a2fd2a00000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000c77441f9f200000000000000000000000000000000000000000000000000000051eb2d621500000000000000000000000000000000000000000000000000000145e4d05f4000000000000000000000000000000000000000000000000000000010070e71cc00000000000000000000000000000000000000000000000000000083c83de0920000000000000000000000000000000000000000000000000000002565cc5edd00000000000000000000000000000000000000000000000000000083c83de092000000000000000000000000000000000000000000000000000000557af7b44300000000000000000000000000000000000000000000000000000030152b55650000000000000000000000000000000000000000000000000000005c9a8c589e0000000000000000000000000000000000000000000000000000010b20461351000000000000000000000000000000000000000000000000000000cccbf1753600000000000000000000000000000000000000000000000000000165f2ed42d90000000000000000000000000000000000000000000000000000005742dcdd5900000000000000000000000000000000000000000000000000000053b3128b2c000000000000000000000000000000000000000000000000000000155ebded1000000000000000000000000000000000000000000000000000000043ac04195f000000000000000000000000000000000000000000000000000000f95152786e000000000000000000000000000000000000000000000000000000a92e0a3f6f00000000000000000000000000000000000000000000000000000187c8ef4f89000000000000000000000000000000000000000000000000000000155ebded100000000000000000000000000000000000000000000000000000008cafb7ae03000000000000000000000000000000000000000000000000000000bcc4e303690000000000000000000000000000000000000000000000000000002e4d462c4f0000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000dcd2ffe70200000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000602a56aacb0000000000000000000000000000000000000000000000000000003734bff9c000000000000000000000000000000000000000000000000000000053b3128b2c0000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000005742dcdd5900000000000000000000000000000000000000000000000000000075891497dc000000000000000000000000000000000000000000000000000000c5ac5cd0db0000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000155ebded10000000000000000000000000000000000000000000000000000000de9ae5101900000000000000000000000000000000000000000000000000000165f2ed42d90000000000000000000000000000000000000000000000000000001ab66d68540000000000000000000000000000000000000000000000000000001ab66d68540000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000401c39c73200000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000155ebded1000000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000007ca8a93c3700000000000000000000000000000000000000000000000000000011cef39ae3000000000000000000000000000000000000000000000000000000be8cc82c800000000000000000000000000000000000000000000000000000006749eb4f260000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000002e4d462c4f000000000000000000000000000000000000000000000000000000d05bbbc7630000000000000000000000000000000000000000000000000000004acb98bdba0000000000000000000000000000000000000000000000000000004acb98bdba0000000000000000000000000000000000000000000000000000001726a31627" + }, + "result": { + "gasUsed": "0x24f678b1", + "output": "0x" + }, + "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber": 3663377, + "transactionHash": "0xdbbc58127cc21ef53ae41e63d815853a748ca6ecee21264466fb8960e0feefe3", + "transactionPosition": 107 + } + ] """ require Logger @@ -165,7 +5666,6 @@ defmodule EthereumJSONRPC.Filecoin do end defp parse_trace_block_calls(calls) - defp parse_trace_block_calls(%{"type" => 0} = res), do: res defp parse_trace_block_calls(%{"Type" => type} = call) do sanitized_call = @@ -189,7 +5689,7 @@ defmodule EthereumJSONRPC.Filecoin do "callType" => type, "from" => from, "to" => to, - "createdContractAddressHash" => to, + "createdContractAddressHash" => Map.get(result, "address", "0x"), "value" => Map.get(action, "value", "0x0"), "gas" => Map.get(action, "gas", "0x0"), "gasUsed" => Map.get(result, "gasUsed", "0x0"), diff --git a/cspell.json b/cspell.json index 1d791a23e111..691c40233b58 100644 --- a/cspell.json +++ b/cspell.json @@ -170,6 +170,7 @@ "exvcr", "Faileddi", "falala", + "FEVM", "Filesize", "Filecoin", "fkey", From 858f626f81dfbbacd64b8b5c01984419ab2b0848 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 18:54:05 +0000 Subject: [PATCH 476/607] Bump core-js from 3.35.1 to 3.36.0 in /apps/block_scout_web/assets Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.35.1 to 3.36.0. - [Release notes](https://github.com/zloirock/core-js/releases) - [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/zloirock/core-js/commits/v3.36.0/packages/core-js) --- updated-dependencies: - dependency-name: core-js dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 389069632ed0..f54a4242b4a9 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -17,7 +17,7 @@ "chart.js": "^4.4.1", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", - "core-js": "^3.35.1", + "core-js": "^3.36.0", "crypto-browserify": "^3.12.0", "dropzone": "^5.9.3", "eth-net-props": "^1.0.41", @@ -5949,9 +5949,9 @@ } }, "node_modules/core-js": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.1.tgz", - "integrity": "sha512-IgdsbxNyMskrTFxa9lWHyMwAJU5gXOPP+1yO+K59d50VLVAIDAbs7gIv705KzALModfK3ZrSZTPNpC0PQgIZuw==", + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.0.tgz", + "integrity": "sha512-mt7+TUBbTFg5+GngsAxeKBTl5/VS0guFeJacYge9OmHb+m058UwwIm41SE9T4Den7ClatV57B6TYTuJ0CX1MAw==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -22278,9 +22278,9 @@ } }, "core-js": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.1.tgz", - "integrity": "sha512-IgdsbxNyMskrTFxa9lWHyMwAJU5gXOPP+1yO+K59d50VLVAIDAbs7gIv705KzALModfK3ZrSZTPNpC0PQgIZuw==" + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.0.tgz", + "integrity": "sha512-mt7+TUBbTFg5+GngsAxeKBTl5/VS0guFeJacYge9OmHb+m058UwwIm41SE9T4Den7ClatV57B6TYTuJ0CX1MAw==" }, "core-js-compat": { "version": "3.35.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 698f6c739c5f..61d41ef18d9c 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -29,7 +29,7 @@ "chart.js": "^4.4.1", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", - "core-js": "^3.35.1", + "core-js": "^3.36.0", "crypto-browserify": "^3.12.0", "dropzone": "^5.9.3", "eth-net-props": "^1.0.41", From bea2fbf67191066b841c5a8bfdaf730fad67e6e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 18:55:13 +0000 Subject: [PATCH 477/607] Bump @amplitude/analytics-browser in /apps/block_scout_web/assets Bumps [@amplitude/analytics-browser](https://github.com/amplitude/Amplitude-TypeScript) from 2.4.0 to 2.4.1. - [Release notes](https://github.com/amplitude/Amplitude-TypeScript/releases) - [Commits](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@2.4.0...@amplitude/analytics-browser@2.4.1) --- updated-dependencies: - dependency-name: "@amplitude/analytics-browser" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 46 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 389069632ed0..10a53b0564e3 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -7,7 +7,7 @@ "name": "blockscout", "license": "GPL-3.0", "dependencies": { - "@amplitude/analytics-browser": "^2.4.0", + "@amplitude/analytics-browser": "^2.4.1", "@fortawesome/fontawesome-free": "^6.5.1", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", @@ -116,15 +116,15 @@ } }, "node_modules/@amplitude/analytics-browser": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.4.0.tgz", - "integrity": "sha512-QHEHCxGiEYeYv052MCnRWmPVD56micO65kODsR5hqWMkJQcemql8gvZfwYPt0rDGxxmvTxEN95uxHbbV2p3bpw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.4.1.tgz", + "integrity": "sha512-sxudfDAOZcyTJOHUc66uAHgZbZY9PORh4CAX4V66ozW+0vneeYPJm3ECCJqIrdJ/JWceOe/ohCT8G06X9s/4yg==", "dependencies": { "@amplitude/analytics-client-common": "^2.0.11", "@amplitude/analytics-core": "^2.2.0", "@amplitude/analytics-types": "^2.4.0", - "@amplitude/plugin-page-view-tracking-browser": "^2.1.0", - "@amplitude/plugin-web-attribution-browser": "^2.1.0", + "@amplitude/plugin-page-view-tracking-browser": "^2.1.1", + "@amplitude/plugin-web-attribution-browser": "^2.1.1", "tslib": "^2.4.1" } }, @@ -174,9 +174,9 @@ "integrity": "sha512-GVj9W4X3XMVyGfqXdES2vFU8pqTIHvihj/vNOjOrwYHVdia3GjlcGl77GXuERCIwr52MoiUVTGXmXn3adf+A+Q==" }, "node_modules/@amplitude/plugin-page-view-tracking-browser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.1.0.tgz", - "integrity": "sha512-iAZzLcmk6RGFf0emihBLtVBX2mZwmW6pyQOHQT7dLxCE8+L/Sgj2zhMyoRomL3RBoUUnXfu03ET3L9fv30LMGA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.1.1.tgz", + "integrity": "sha512-dZwA0kb/dvFTokF2yVklfjwl0Ben6g6Sukpr84tocpurJ7Ojhh1egISCZ4QWt2VgKKh7X4pyK73f5Fcp/1JcNw==", "dependencies": { "@amplitude/analytics-client-common": "^2.0.11", "@amplitude/analytics-types": "^2.4.0", @@ -189,9 +189,9 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@amplitude/plugin-web-attribution-browser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.1.0.tgz", - "integrity": "sha512-2b/LTZifOLvx8XTVPfyaxh39uX5ANNbvypByk9zywprucbKUj+1hzycgP57lN8Xw2iuqRfypQz7V2YGdR9w4MQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.1.1.tgz", + "integrity": "sha512-paWFk+uJeVlUF/HYNoczIxP3IJX/KR573B/E4Wp5bLB3XeJIhRbj3RQdJKhnKN5keg8+Vei/8uGdGQK4d3DYDg==", "dependencies": { "@amplitude/analytics-client-common": "^2.0.11", "@amplitude/analytics-core": "^2.2.0", @@ -17907,15 +17907,15 @@ "dev": true }, "@amplitude/analytics-browser": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.4.0.tgz", - "integrity": "sha512-QHEHCxGiEYeYv052MCnRWmPVD56micO65kODsR5hqWMkJQcemql8gvZfwYPt0rDGxxmvTxEN95uxHbbV2p3bpw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.4.1.tgz", + "integrity": "sha512-sxudfDAOZcyTJOHUc66uAHgZbZY9PORh4CAX4V66ozW+0vneeYPJm3ECCJqIrdJ/JWceOe/ohCT8G06X9s/4yg==", "requires": { "@amplitude/analytics-client-common": "^2.0.11", "@amplitude/analytics-core": "^2.2.0", "@amplitude/analytics-types": "^2.4.0", - "@amplitude/plugin-page-view-tracking-browser": "^2.1.0", - "@amplitude/plugin-web-attribution-browser": "^2.1.0", + "@amplitude/plugin-page-view-tracking-browser": "^2.1.1", + "@amplitude/plugin-web-attribution-browser": "^2.1.1", "tslib": "^2.4.1" }, "dependencies": { @@ -17971,9 +17971,9 @@ "integrity": "sha512-GVj9W4X3XMVyGfqXdES2vFU8pqTIHvihj/vNOjOrwYHVdia3GjlcGl77GXuERCIwr52MoiUVTGXmXn3adf+A+Q==" }, "@amplitude/plugin-page-view-tracking-browser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.1.0.tgz", - "integrity": "sha512-iAZzLcmk6RGFf0emihBLtVBX2mZwmW6pyQOHQT7dLxCE8+L/Sgj2zhMyoRomL3RBoUUnXfu03ET3L9fv30LMGA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.1.1.tgz", + "integrity": "sha512-dZwA0kb/dvFTokF2yVklfjwl0Ben6g6Sukpr84tocpurJ7Ojhh1egISCZ4QWt2VgKKh7X4pyK73f5Fcp/1JcNw==", "requires": { "@amplitude/analytics-client-common": "^2.0.11", "@amplitude/analytics-types": "^2.4.0", @@ -17988,9 +17988,9 @@ } }, "@amplitude/plugin-web-attribution-browser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.1.0.tgz", - "integrity": "sha512-2b/LTZifOLvx8XTVPfyaxh39uX5ANNbvypByk9zywprucbKUj+1hzycgP57lN8Xw2iuqRfypQz7V2YGdR9w4MQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.1.1.tgz", + "integrity": "sha512-paWFk+uJeVlUF/HYNoczIxP3IJX/KR573B/E4Wp5bLB3XeJIhRbj3RQdJKhnKN5keg8+Vei/8uGdGQK4d3DYDg==", "requires": { "@amplitude/analytics-client-common": "^2.0.11", "@amplitude/analytics-core": "^2.2.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 698f6c739c5f..887b2dfeeb52 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^6.5.1", - "@amplitude/analytics-browser": "^2.4.0", + "@amplitude/analytics-browser": "^2.4.1", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", "assert": "^2.1.0", From dd85b6ccfe5834b349902b53c9040d09246493d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 18:56:17 +0000 Subject: [PATCH 478/607] Bump webpack from 5.90.1 to 5.90.3 in /apps/block_scout_web/assets Bumps [webpack](https://github.com/webpack/webpack) from 5.90.1 to 5.90.3. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.90.1...v5.90.3) --- updated-dependencies: - dependency-name: webpack dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 389069632ed0..edc306b71ce7 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -92,7 +92,7 @@ "sass": "^1.70.0", "sass-loader": "^14.1.0", "style-loader": "^3.3.4", - "webpack": "^5.90.1", + "webpack": "^5.90.3", "webpack-cli": "^5.1.4" }, "engines": { @@ -17369,9 +17369,9 @@ } }, "node_modules/webpack": { - "version": "5.90.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz", - "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==", + "version": "5.90.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", + "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -30935,9 +30935,9 @@ "dev": true }, "webpack": { - "version": "5.90.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz", - "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==", + "version": "5.90.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", + "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 698f6c739c5f..c68918dff314 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -104,7 +104,7 @@ "sass": "^1.70.0", "sass-loader": "^14.1.0", "style-loader": "^3.3.4", - "webpack": "^5.90.1", + "webpack": "^5.90.3", "webpack-cli": "^5.1.4" }, "jest": { From 9bc63a5ef8c4b26f9822db88775d73232e1b748d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 18:57:14 +0000 Subject: [PATCH 479/607] Bump sass-loader from 14.1.0 to 14.1.1 in /apps/block_scout_web/assets Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 14.1.0 to 14.1.1. - [Release notes](https://github.com/webpack-contrib/sass-loader/releases) - [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/sass-loader/compare/v14.1.0...v14.1.1) --- updated-dependencies: - dependency-name: sass-loader dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 389069632ed0..6d12c9e8147a 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -90,7 +90,7 @@ "postcss": "^8.4.35", "postcss-loader": "^8.1.0", "sass": "^1.70.0", - "sass-loader": "^14.1.0", + "sass-loader": "^14.1.1", "style-loader": "^3.3.4", "webpack": "^5.90.1", "webpack-cli": "^5.1.4" @@ -15241,9 +15241,9 @@ } }, "node_modules/sass-loader": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.0.tgz", - "integrity": "sha512-LS2mLeFWA+orYxHNu+O18Xe4jR0kyamNOOUsE3NyBP4DvIL+8stHpNX0arYTItdPe80kluIiJ7Wfe/9iHSRO0Q==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.1.tgz", + "integrity": "sha512-QX8AasDg75monlybel38BZ49JP5Z+uSKfKwF2rO7S74BywaRmGQMUBw9dtkS+ekyM/QnP+NOrRYq8ABMZ9G8jw==", "dev": true, "dependencies": { "neo-async": "^2.6.2" @@ -29305,9 +29305,9 @@ } }, "sass-loader": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.0.tgz", - "integrity": "sha512-LS2mLeFWA+orYxHNu+O18Xe4jR0kyamNOOUsE3NyBP4DvIL+8stHpNX0arYTItdPe80kluIiJ7Wfe/9iHSRO0Q==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.1.tgz", + "integrity": "sha512-QX8AasDg75monlybel38BZ49JP5Z+uSKfKwF2rO7S74BywaRmGQMUBw9dtkS+ekyM/QnP+NOrRYq8ABMZ9G8jw==", "dev": true, "requires": { "neo-async": "^2.6.2" diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 698f6c739c5f..bd3397f5fbb9 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -102,7 +102,7 @@ "postcss": "^8.4.35", "postcss-loader": "^8.1.0", "sass": "^1.70.0", - "sass-loader": "^14.1.0", + "sass-loader": "^14.1.1", "style-loader": "^3.3.4", "webpack": "^5.90.1", "webpack-cli": "^5.1.4" From c9be63fcb40ab6ca96b3c559bbfe1b822a304efa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 08:44:08 +0000 Subject: [PATCH 480/607] Bump sass from 1.70.0 to 1.71.0 in /apps/block_scout_web/assets Bumps [sass](https://github.com/sass/dart-sass) from 1.70.0 to 1.71.0. - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.70.0...1.71.0) --- updated-dependencies: - dependency-name: sass dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 9c70e81eb70b..615db5ef19c3 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -89,7 +89,7 @@ "mini-css-extract-plugin": "^2.8.0", "postcss": "^8.4.35", "postcss-loader": "^8.1.0", - "sass": "^1.70.0", + "sass": "^1.71.0", "sass-loader": "^14.1.1", "style-loader": "^3.3.4", "webpack": "^5.90.3", @@ -15224,9 +15224,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.70.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.70.0.tgz", - "integrity": "sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==", + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.0.tgz", + "integrity": "sha512-HKKIKf49Vkxlrav3F/w6qRuPcmImGVbIXJ2I3Kg0VMA+3Bav+8yE9G5XmP5lMj6nl4OlqbPftGAscNaNu28b8w==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -29294,9 +29294,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass": { - "version": "1.70.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.70.0.tgz", - "integrity": "sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==", + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.0.tgz", + "integrity": "sha512-HKKIKf49Vkxlrav3F/w6qRuPcmImGVbIXJ2I3Kg0VMA+3Bav+8yE9G5XmP5lMj6nl4OlqbPftGAscNaNu28b8w==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 0c7cae8575cd..e7edf532c30c 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -101,7 +101,7 @@ "mini-css-extract-plugin": "^2.8.0", "postcss": "^8.4.35", "postcss-loader": "^8.1.0", - "sass": "^1.70.0", + "sass": "^1.71.0", "sass-loader": "^14.1.1", "style-loader": "^3.3.4", "webpack": "^5.90.3", From 09b269bf72dd9b7cc8f60fe3b1d3dcfd0329b90b Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Thu, 15 Feb 2024 15:02:08 +0400 Subject: [PATCH 481/607] Eliminate incorrect token transfers with empty token_ids --- CHANGELOG.md | 1 + apps/explorer/config/config.exs | 1 + apps/explorer/config/runtime/test.exs | 1 + apps/explorer/lib/explorer/application.ex | 3 +- .../migrator/token_transfer_token_ids.ex | 156 ++++++++++++++++++ .../token_transfer_token_ids_test.exs | 63 +++++++ .../lib/indexer/transform/token_transfers.ex | 38 ++++- 7 files changed, 260 insertions(+), 3 deletions(-) create mode 100644 apps/explorer/lib/explorer/migrator/token_transfer_token_ids.ex create mode 100644 apps/explorer/test/explorer/migrator/token_transfer_token_ids_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 53bb3211dde4..4506e0dc75d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - [#9403](https://github.com/blockscout/blockscout/pull/9403) - Null round handling +- [#9401](https://github.com/blockscout/blockscout/pull/9401) - Eliminate incorrect token transfers with empty token_ids - [#9396](https://github.com/blockscout/blockscout/pull/9396) - More-Minimal Proxy support - [#9379](https://github.com/blockscout/blockscout/pull/9379) - Filter non-traceable transactions for zetachain - [#9364](https://github.com/blockscout/blockscout/pull/9364) - Fix using of startblock/endblock in API v1 list endpoints: txlist, txlistinternal, tokentx diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 9e75a6926ea8..47d6f0d87eb6 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -115,6 +115,7 @@ config :explorer, Explorer.Migrator.TransactionsDenormalization, enabled: true config :explorer, Explorer.Migrator.AddressCurrentTokenBalanceTokenType, enabled: true config :explorer, Explorer.Migrator.AddressTokenBalanceTokenType, enabled: true config :explorer, Explorer.Migrator.SanitizeMissingBlockRanges, enabled: true +config :explorer, Explorer.Migrator.TokenTransferTokenIds, enabled: true config :explorer, Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand, enabled: true diff --git a/apps/explorer/config/runtime/test.exs b/apps/explorer/config/runtime/test.exs index da3989383653..91950b4421be 100644 --- a/apps/explorer/config/runtime/test.exs +++ b/apps/explorer/config/runtime/test.exs @@ -39,6 +39,7 @@ config :explorer, Explorer.Migrator.TransactionsDenormalization, enabled: false config :explorer, Explorer.Migrator.AddressCurrentTokenBalanceTokenType, enabled: false config :explorer, Explorer.Migrator.AddressTokenBalanceTokenType, enabled: false config :explorer, Explorer.Migrator.SanitizeMissingBlockRanges, enabled: false +config :explorer, Explorer.Migrator.TokenTransferTokenIds, enabled: false config :explorer, realtime_events_sender: Explorer.Chain.Events.SimpleSender diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 1477205e3c4c..cd21e443a5f8 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -129,7 +129,8 @@ defmodule Explorer.Application do configure(Explorer.Migrator.TransactionsDenormalization), configure(Explorer.Migrator.AddressCurrentTokenBalanceTokenType), configure(Explorer.Migrator.AddressTokenBalanceTokenType), - configure(Explorer.Migrator.SanitizeMissingBlockRanges) + configure(Explorer.Migrator.SanitizeMissingBlockRanges), + configure(Explorer.Migrator.TokenTransferTokenIds) ] |> List.flatten() diff --git a/apps/explorer/lib/explorer/migrator/token_transfer_token_ids.ex b/apps/explorer/lib/explorer/migrator/token_transfer_token_ids.ex new file mode 100644 index 000000000000..509c2d208e53 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/token_transfer_token_ids.ex @@ -0,0 +1,156 @@ +defmodule Explorer.Migrator.TokenTransferTokenIds do + @moduledoc """ + Delete all token transfers of ERC-721 tokens with deposit/withdrawal signatures + Delete all token transfers of ERC-1155 tokens with empty amount, amounts and token_ids + Send blocks containing token transfers of ERC-721 tokens with empty token_ids to re-fetch + """ + + use GenServer, restart: :transient + + import Ecto.Query + + require Logger + + alias Explorer.Chain.Import.Runner.Blocks + alias Explorer.Chain.{Log, TokenTransfer} + alias Explorer.Migrator.MigrationStatus + alias Explorer.Repo + + @migration_name "token_transfers_token_ids" + @default_batch_size 500 + + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + @impl true + def init(_) do + case MigrationStatus.get_status(@migration_name) do + "completed" -> + :ignore + + _ -> + MigrationStatus.set_status(@migration_name, "started") + schedule_batch_migration() + {:ok, %{step: :delete}} + end + end + + @impl true + def handle_info(:migrate_batch, %{step: step} = state) do + case last_unprocessed_identifiers(step) do + [] -> + case step do + :delete -> + Logger.info("TokenTransferTokenIds deletion finished, continuing with blocks re-fetch") + schedule_batch_migration() + {:noreply, %{step: :refetch}} + + :refetch -> + Logger.info("TokenTransferTokenIds migration finished") + MigrationStatus.set_status(@migration_name, "completed") + {:stop, :normal, state} + end + + identifiers -> + identifiers + |> Enum.chunk_every(batch_size()) + |> Enum.map(&run_task(&1, step)) + |> Task.await_many(:infinity) + + schedule_batch_migration() + + {:noreply, state} + end + end + + defp last_unprocessed_identifiers(step) do + limit = batch_size() * concurrency() + + step + |> unprocessed_identifiers() + |> limit(^limit) + |> Repo.all(timeout: :infinity) + end + + defp unprocessed_identifiers(:delete) do + from( + tt in TokenTransfer, + left_join: l in Log, + on: tt.block_hash == l.block_hash and tt.transaction_hash == l.transaction_hash and tt.log_index == l.index, + left_join: t in assoc(tt, :token), + where: + t.type == ^"ERC-721" and + (l.first_topic == ^TokenTransfer.weth_deposit_signature() or + l.first_topic == ^TokenTransfer.weth_withdrawal_signature()), + or_where: t.type == ^"ERC-1155" and is_nil(tt.amount) and is_nil(tt.amounts) and is_nil(tt.token_ids), + select: {tt.transaction_hash, tt.block_hash, tt.log_index} + ) + end + + defp unprocessed_identifiers(:refetch) do + from( + tt in TokenTransfer, + join: t in assoc(tt, :token), + join: b in assoc(tt, :block), + where: t.type == ^"ERC-721" and is_nil(tt.token_ids), + where: b.consensus == true, + select: tt.block_number, + distinct: tt.block_number + ) + end + + defp run_task(batch, step), do: Task.async(fn -> handle_batch(batch, step) end) + + defp handle_batch(token_transfer_ids, :delete) do + token_transfer_ids + |> build_delete_query() + |> Repo.query!([], timeout: :infinity) + end + + defp handle_batch(block_numbers, :refetch) do + Blocks.invalidate_consensus_blocks(block_numbers) + 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 + + defp build_delete_query(token_transfer_ids) do + """ + DELETE + FROM token_transfers tt + WHERE (tt.transaction_hash, tt.block_hash, tt.log_index) IN #{encode_token_transfer_ids(token_transfer_ids)} + """ + end + + defp encode_token_transfer_ids(ids) do + encoded_values = + ids + |> Enum.reduce("", fn {t_hash, b_hash, log_index}, acc -> + acc <> "('#{hash_to_query_string(t_hash)}', '#{hash_to_query_string(b_hash)}', #{log_index})," + end) + |> String.trim_trailing(",") + + "(#{encoded_values})" + end + + defp hash_to_query_string(hash) do + s_hash = + hash + |> to_string() + |> String.trim_leading("0") + + "\\#{s_hash}" + end +end diff --git a/apps/explorer/test/explorer/migrator/token_transfer_token_ids_test.exs b/apps/explorer/test/explorer/migrator/token_transfer_token_ids_test.exs new file mode 100644 index 000000000000..891edd4b5322 --- /dev/null +++ b/apps/explorer/test/explorer/migrator/token_transfer_token_ids_test.exs @@ -0,0 +1,63 @@ +defmodule Explorer.Migrator.TokenTransferTokenIdsTest do + use Explorer.DataCase, async: false + + alias Explorer.Chain.{Block, TokenTransfer} + alias Explorer.Migrator.{TokenTransferTokenIds, MigrationStatus} + alias Explorer.Repo + + describe "Migrate token transfers" do + test "Handles delete and re-fetch" do + %{contract_address: token_address} = insert(:token, type: "ERC-721") + block = insert(:block, consensus: true) + + insert(:token_transfer, + from_address: insert(:address), + block: block, + block_number: block.number, + token_contract_address: token_address, + token_ids: nil + ) + + deposit_log = insert(:log, first_topic: TokenTransfer.weth_deposit_signature()) + + insert(:token_transfer, + from_address: insert(:address), + token_contract_address: token_address, + block: deposit_log.block, + transaction: deposit_log.transaction, + log_index: deposit_log.index + ) + + withdrawal_log = insert(:log, first_topic: TokenTransfer.weth_withdrawal_signature()) + + insert(:token_transfer, + from_address: insert(:address), + token_contract_address: token_address, + block: withdrawal_log.block, + transaction: withdrawal_log.transaction, + log_index: withdrawal_log.index + ) + + erc1155_token = insert(:token, type: "ERC-1155") + + insert(:token_transfer, + from_address: insert(:address), + token_contract_address: erc1155_token.contract_address, + amount: nil, + amounts: nil, + token_ids: nil + ) + + assert MigrationStatus.get_status("token_transfers_token_ids") == nil + + TokenTransferTokenIds.start_link([]) + Process.sleep(100) + + assert MigrationStatus.get_status("token_transfers_token_ids") == "completed" + + token_address_hash = token_address.hash + assert %{token_contract_address_hash: ^token_address_hash, token_ids: nil} = Repo.one(TokenTransfer) + assert %{consensus: false} = Repo.get_by(Block, hash: block.hash) + end + end +end diff --git a/apps/indexer/lib/indexer/transform/token_transfers.ex b/apps/indexer/lib/indexer/transform/token_transfers.ex index 7b10e701b9a4..7564bbff6c29 100644 --- a/apps/indexer/lib/indexer/transform/token_transfers.ex +++ b/apps/indexer/lib/indexer/transform/token_transfers.ex @@ -47,7 +47,8 @@ defmodule Indexer.Transform.TokenTransfers do erc1155_token_transfers.token_transfers ++ erc20_and_erc721_token_transfers.token_transfers ++ weth_transfers.token_transfers - {tokens, token_transfers} = sanitize_token_types(rough_tokens, rough_token_transfers) + {tokens, sanitized_token_transfers} = sanitize_token_types(rough_tokens, rough_token_transfers) + token_transfers = sanitize_weth_transfers(tokens, sanitized_token_transfers, weth_transfers.token_transfers) token_transfers |> Enum.filter(fn token_transfer -> @@ -70,6 +71,39 @@ defmodule Indexer.Transform.TokenTransfers do token_transfers_from_logs_uniq end + defp sanitize_weth_transfers(total_tokens, total_transfers, weth_transfers) do + existing_token_types_map = + total_tokens + |> Enum.map(&{&1.contract_address_hash, &1.type}) + |> Map.new() + + invalid_weth_transfers = + Enum.reduce(weth_transfers, [], fn token_transfer, acc -> + if existing_token_types_map[token_transfer.token_contract_address_hash] == "ERC-721" do + [token_transfer | acc] + else + acc + end + end) + + total_transfers + |> subtract_token_transfers(invalid_weth_transfers) + |> Enum.reverse() + end + + defp subtract_token_transfers(tt_from, tt_to_subtract) do + Enum.reduce(tt_from, [], fn tt, acc -> + case Enum.find( + tt_to_subtract, + &(&1.block_hash == tt.block_hash and &1.transaction_hash == tt.transaction_hash and + &1.log_index == tt.log_index) + ) do + nil -> [tt | acc] + _ -> acc + end + end) + end + defp sanitize_token_types(tokens, token_transfers) do existing_token_types_map = tokens @@ -273,7 +307,7 @@ defmodule Indexer.Transform.TokenTransfers do ) do [token_ids, values] = decode_data(data, [{:array, {:uint, 256}}, {:array, {:uint, 256}}]) - if token_ids == [] || values == [] do + if is_nil(token_ids) or token_ids == [] or is_nil(values) or values == [] do nil else token_transfer = %{ From 6907d870ebfa06a4c29baa3de6efe147f0a3f604 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop <105209995+Qwerty5Uiop@users.noreply.github.com> Date: Tue, 20 Feb 2024 20:33:26 +0400 Subject: [PATCH 482/607] Refactor weth transfers sanitizing Co-authored-by: nikitosing <32202610+nikitosing@users.noreply.github.com> --- .../lib/indexer/transform/token_transfers.ex | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/indexer/lib/indexer/transform/token_transfers.ex b/apps/indexer/lib/indexer/transform/token_transfers.ex index 7564bbff6c29..364f4af701e1 100644 --- a/apps/indexer/lib/indexer/transform/token_transfers.ex +++ b/apps/indexer/lib/indexer/transform/token_transfers.ex @@ -78,9 +78,9 @@ defmodule Indexer.Transform.TokenTransfers do |> Map.new() invalid_weth_transfers = - Enum.reduce(weth_transfers, [], fn token_transfer, acc -> + Enum.reduce(weth_transfers, %{}, fn token_transfer, acc -> if existing_token_types_map[token_transfer.token_contract_address_hash] == "ERC-721" do - [token_transfer | acc] + Map.put(acc, token_transfer_to_key(token_transfer), true) else acc end @@ -91,13 +91,13 @@ defmodule Indexer.Transform.TokenTransfers do |> Enum.reverse() end + defp token_transfer_to_key(token_transfer) do + {token_transfer.block_hash, token_transfer.transaction_hash, token_transfer.log_index} + end + defp subtract_token_transfers(tt_from, tt_to_subtract) do Enum.reduce(tt_from, [], fn tt, acc -> - case Enum.find( - tt_to_subtract, - &(&1.block_hash == tt.block_hash and &1.transaction_hash == tt.transaction_hash and - &1.log_index == tt.log_index) - ) do + case tt_to_subtract[token_transfer_to_key(tt)] do nil -> [tt | acc] _ -> acc end From c118d877c7e38ea769a1f570afb0dff691d30a8f Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Tue, 20 Feb 2024 20:56:54 +0400 Subject: [PATCH 483/607] Refactor token transfers migration name --- apps/explorer/config/config.exs | 2 +- apps/explorer/config/runtime/test.exs | 2 +- apps/explorer/lib/explorer/application.ex | 2 +- ...ds.ex => sanitize_incorrect_nft_token_transfers.ex} | 8 ++++---- ...=> sanitize_incorrect_nft_token_transfers_test.exs} | 10 +++++----- 5 files changed, 12 insertions(+), 12 deletions(-) rename apps/explorer/lib/explorer/migrator/{token_transfer_token_ids.ex => sanitize_incorrect_nft_token_transfers.ex} (93%) rename apps/explorer/test/explorer/migrator/{token_transfer_token_ids_test.exs => sanitize_incorrect_nft_token_transfers_test.exs} (82%) diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 47d6f0d87eb6..414cb38de6ca 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -115,7 +115,7 @@ config :explorer, Explorer.Migrator.TransactionsDenormalization, enabled: true config :explorer, Explorer.Migrator.AddressCurrentTokenBalanceTokenType, enabled: true config :explorer, Explorer.Migrator.AddressTokenBalanceTokenType, enabled: true config :explorer, Explorer.Migrator.SanitizeMissingBlockRanges, enabled: true -config :explorer, Explorer.Migrator.TokenTransferTokenIds, enabled: true +config :explorer, Explorer.Migrator.SanitizeIncorrectNFTTokenTransfers, enabled: true config :explorer, Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand, enabled: true diff --git a/apps/explorer/config/runtime/test.exs b/apps/explorer/config/runtime/test.exs index 91950b4421be..db5d2eaee689 100644 --- a/apps/explorer/config/runtime/test.exs +++ b/apps/explorer/config/runtime/test.exs @@ -39,7 +39,7 @@ config :explorer, Explorer.Migrator.TransactionsDenormalization, enabled: false config :explorer, Explorer.Migrator.AddressCurrentTokenBalanceTokenType, enabled: false config :explorer, Explorer.Migrator.AddressTokenBalanceTokenType, enabled: false config :explorer, Explorer.Migrator.SanitizeMissingBlockRanges, enabled: false -config :explorer, Explorer.Migrator.TokenTransferTokenIds, enabled: false +config :explorer, Explorer.Migrator.SanitizeIncorrectNFTTokenTransfers, enabled: false config :explorer, realtime_events_sender: Explorer.Chain.Events.SimpleSender diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index cd21e443a5f8..961662975551 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -130,7 +130,7 @@ defmodule Explorer.Application do configure(Explorer.Migrator.AddressCurrentTokenBalanceTokenType), configure(Explorer.Migrator.AddressTokenBalanceTokenType), configure(Explorer.Migrator.SanitizeMissingBlockRanges), - configure(Explorer.Migrator.TokenTransferTokenIds) + configure(Explorer.Migrator.SanitizeIncorrectNFTTokenTransfers) ] |> List.flatten() diff --git a/apps/explorer/lib/explorer/migrator/token_transfer_token_ids.ex b/apps/explorer/lib/explorer/migrator/sanitize_incorrect_nft_token_transfers.ex similarity index 93% rename from apps/explorer/lib/explorer/migrator/token_transfer_token_ids.ex rename to apps/explorer/lib/explorer/migrator/sanitize_incorrect_nft_token_transfers.ex index 509c2d208e53..5e4fd49a2627 100644 --- a/apps/explorer/lib/explorer/migrator/token_transfer_token_ids.ex +++ b/apps/explorer/lib/explorer/migrator/sanitize_incorrect_nft_token_transfers.ex @@ -1,4 +1,4 @@ -defmodule Explorer.Migrator.TokenTransferTokenIds do +defmodule Explorer.Migrator.SanitizeIncorrectNFTTokenTransfers do @moduledoc """ Delete all token transfers of ERC-721 tokens with deposit/withdrawal signatures Delete all token transfers of ERC-1155 tokens with empty amount, amounts and token_ids @@ -16,7 +16,7 @@ defmodule Explorer.Migrator.TokenTransferTokenIds do alias Explorer.Migrator.MigrationStatus alias Explorer.Repo - @migration_name "token_transfers_token_ids" + @migration_name "sanitize_incorrect_nft" @default_batch_size 500 def start_link(_) do @@ -42,12 +42,12 @@ defmodule Explorer.Migrator.TokenTransferTokenIds do [] -> case step do :delete -> - Logger.info("TokenTransferTokenIds deletion finished, continuing with blocks re-fetch") + Logger.info("SanitizeIncorrectNFTTokenTransfers deletion finished, continuing with blocks re-fetch") schedule_batch_migration() {:noreply, %{step: :refetch}} :refetch -> - Logger.info("TokenTransferTokenIds migration finished") + Logger.info("SanitizeIncorrectNFTTokenTransfers migration finished") MigrationStatus.set_status(@migration_name, "completed") {:stop, :normal, state} end diff --git a/apps/explorer/test/explorer/migrator/token_transfer_token_ids_test.exs b/apps/explorer/test/explorer/migrator/sanitize_incorrect_nft_token_transfers_test.exs similarity index 82% rename from apps/explorer/test/explorer/migrator/token_transfer_token_ids_test.exs rename to apps/explorer/test/explorer/migrator/sanitize_incorrect_nft_token_transfers_test.exs index 891edd4b5322..cd019e245bd7 100644 --- a/apps/explorer/test/explorer/migrator/token_transfer_token_ids_test.exs +++ b/apps/explorer/test/explorer/migrator/sanitize_incorrect_nft_token_transfers_test.exs @@ -1,8 +1,8 @@ -defmodule Explorer.Migrator.TokenTransferTokenIdsTest do +defmodule Explorer.Migrator.SanitizeIncorrectNFTTokenTransfersTest do use Explorer.DataCase, async: false alias Explorer.Chain.{Block, TokenTransfer} - alias Explorer.Migrator.{TokenTransferTokenIds, MigrationStatus} + alias Explorer.Migrator.{SanitizeIncorrectNFTTokenTransfers, MigrationStatus} alias Explorer.Repo describe "Migrate token transfers" do @@ -48,12 +48,12 @@ defmodule Explorer.Migrator.TokenTransferTokenIdsTest do token_ids: nil ) - assert MigrationStatus.get_status("token_transfers_token_ids") == nil + assert MigrationStatus.get_status("sanitize_incorrect_nft") == nil - TokenTransferTokenIds.start_link([]) + SanitizeIncorrectNFTTokenTransfers.start_link([]) Process.sleep(100) - assert MigrationStatus.get_status("token_transfers_token_ids") == "completed" + assert MigrationStatus.get_status("sanitize_incorrect_nft") == "completed" token_address_hash = token_address.hash assert %{token_contract_address_hash: ^token_address_hash, token_ids: nil} = Repo.one(TokenTransfer) From f997b67be8ccb8fde8c3535178a158f6018d2294 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Wed, 21 Feb 2024 00:14:32 +0400 Subject: [PATCH 484/607] Add Enum.uniq before sanitizing token transfers --- CHANGELOG.md | 1 + apps/indexer/lib/indexer/transform/token_transfers.ex | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4506e0dc75d4..577bfdc2a89c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#9437](https://github.com/blockscout/blockscout/pull/9437) - Add Enum.uniq before sanitizing token transfers - [#9403](https://github.com/blockscout/blockscout/pull/9403) - Null round handling - [#9401](https://github.com/blockscout/blockscout/pull/9401) - Eliminate incorrect token transfers with empty token_ids - [#9396](https://github.com/blockscout/blockscout/pull/9396) - More-Minimal Proxy support diff --git a/apps/indexer/lib/indexer/transform/token_transfers.ex b/apps/indexer/lib/indexer/transform/token_transfers.ex index 364f4af701e1..09a4b1fea641 100644 --- a/apps/indexer/lib/indexer/transform/token_transfers.ex +++ b/apps/indexer/lib/indexer/transform/token_transfers.ex @@ -107,6 +107,7 @@ defmodule Indexer.Transform.TokenTransfers do defp sanitize_token_types(tokens, token_transfers) do existing_token_types_map = tokens + |> Enum.uniq() |> Enum.reduce([], fn %{contract_address_hash: address_hash}, acc -> case Repo.get_by(Token, contract_address_hash: address_hash) do %{type: type} -> [{address_hash, type} | acc] From c066a68b5c262b63a79816a0d9c9c2e40f52ac30 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Mon, 5 Feb 2024 23:34:50 +0400 Subject: [PATCH 485/607] Token transfers denormalization --- .../block_scout_web/views/tokens/helper.ex | 106 +++++++++++++++--- .../block_scout_web/views/transaction_view.ex | 3 + apps/block_scout_web/priv/gettext/default.pot | 48 ++++---- .../priv/gettext/en/LC_MESSAGES/default.po | 48 ++++---- .../channels/websocket_v2_test.exs | 3 + .../api/v2/address_controller_test.exs | 25 ++++- .../api/v2/token_controller_test.exs | 26 ++++- .../api/v2/transaction_controller_test.exs | 21 +++- .../views/tokens/helper_test.exs | 4 +- apps/explorer/config/config.exs | 1 + apps/explorer/config/runtime/test.exs | 1 + apps/explorer/lib/explorer/application.ex | 3 +- .../chain/cache/background_migrations.ex | 12 +- .../explorer/chain/denormalization_helper.ex | 2 + .../explorer/chain/import/runner/blocks.ex | 68 +++++++---- .../chain/import/runner/token_transfers.ex | 6 +- .../lib/explorer/chain/token_transfer.ex | 9 +- .../migrator/token_transfer_token_type.ex | 76 +++++++++++++ ...2141_add_token_type_to_token_transfers.exs | 11 ++ .../test/explorer/chain/import_test.exs | 1 + apps/explorer/test/explorer/chain_test.exs | 1 + apps/explorer/test/support/factory.ex | 3 +- 22 files changed, 368 insertions(+), 110 deletions(-) create mode 100644 apps/explorer/lib/explorer/migrator/token_transfer_token_type.ex create mode 100644 apps/explorer/priv/repo/migrations/20240122102141_add_token_type_to_token_transfers.exs diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/helper.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/helper.ex index 51119e8d7a98..309732bd85f2 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/helper.ex @@ -16,31 +16,66 @@ defmodule BlockScoutWeb.Tokens.Helper do When the token's type is ERC-721, the function will return a string with the token_id that represents the ERC-721 token since this kind of token doesn't have amount and decimals. """ - def token_transfer_amount(%{token: token, amount: amount, amounts: amounts, token_ids: token_ids}) do - do_token_transfer_amount(token, amount, amounts, token_ids) + def token_transfer_amount(%{ + token: token, + token_type: token_type, + amount: amount, + amounts: amounts, + token_ids: token_ids + }) do + do_token_transfer_amount(token, token_type, amount, amounts, token_ids) end - def token_transfer_amount(%{token: token, amount: amount, token_ids: token_ids}) do - do_token_transfer_amount(token, amount, nil, token_ids) + def token_transfer_amount(%{token: token, token_type: token_type, amount: amount, token_ids: token_ids}) do + do_token_transfer_amount(token, token_type, amount, nil, token_ids) end - defp do_token_transfer_amount(%Token{type: "ERC-20"}, nil, nil, _token_ids) do + # TODO: remove this clause along with token transfer denormalization + defp do_token_transfer_amount(%Token{type: "ERC-20"}, nil, nil, nil, _token_ids) do {:ok, "--"} end - defp do_token_transfer_amount(%Token{type: "ERC-20", decimals: nil}, amount, _amounts, _token_ids) do + defp do_token_transfer_amount(_token, "ERC-20", nil, nil, _token_ids) do + {:ok, "--"} + end + + # TODO: remove this clause along with token transfer denormalization + defp do_token_transfer_amount(%Token{type: "ERC-20", decimals: nil}, nil, amount, _amounts, _token_ids) do + {:ok, CurrencyHelper.format_according_to_decimals(amount, Decimal.new(0))} + end + + defp do_token_transfer_amount(%Token{decimals: nil}, "ERC-20", amount, _amounts, _token_ids) do {:ok, CurrencyHelper.format_according_to_decimals(amount, Decimal.new(0))} end - defp do_token_transfer_amount(%Token{type: "ERC-20", decimals: decimals}, amount, _amounts, _token_ids) do + # TODO: remove this clause along with token transfer denormalization + defp do_token_transfer_amount(%Token{type: "ERC-20", decimals: decimals}, nil, amount, _amounts, _token_ids) do {:ok, CurrencyHelper.format_according_to_decimals(amount, decimals)} end - defp do_token_transfer_amount(%Token{type: "ERC-721"}, _amount, _amounts, _token_ids) do + defp do_token_transfer_amount(%Token{decimals: decimals}, "ERC-20", amount, _amounts, _token_ids) do + {:ok, CurrencyHelper.format_according_to_decimals(amount, decimals)} + end + + # TODO: remove this clause along with token transfer denormalization + defp do_token_transfer_amount(%Token{type: "ERC-721"}, nil, _amount, _amounts, _token_ids) do + {:ok, :erc721_instance} + end + + defp do_token_transfer_amount(_token, "ERC-721", _amount, _amounts, _token_ids) do {:ok, :erc721_instance} end - defp do_token_transfer_amount(%Token{type: "ERC-1155", decimals: decimals}, amount, amounts, token_ids) do + # TODO: remove this clause along with token transfer denormalization + defp do_token_transfer_amount(%Token{type: "ERC-1155", decimals: decimals}, nil, amount, amounts, token_ids) do + if amount do + {:ok, :erc1155_instance, CurrencyHelper.format_according_to_decimals(amount, decimals)} + else + {:ok, :erc1155_instance, amounts, token_ids, decimals} + end + end + + defp do_token_transfer_amount(%Token{decimals: decimals}, "ERC-1155", amount, amounts, token_ids) do if amount do {:ok, :erc1155_instance, CurrencyHelper.format_according_to_decimals(amount, decimals)} else @@ -48,29 +83,37 @@ defmodule BlockScoutWeb.Tokens.Helper do end end - defp do_token_transfer_amount(_token, _amount, _amounts, _token_ids) do + defp do_token_transfer_amount(_token, _token_type, _amount, _amounts, _token_ids) do nil end def token_transfer_amount_for_api(%{ token: token, + token_type: token_type, amount: amount, amounts: amounts, token_ids: token_ids }) do - do_token_transfer_amount_for_api(token, amount, amounts, token_ids) + do_token_transfer_amount_for_api(token, token_type, amount, amounts, token_ids) + end + + def token_transfer_amount_for_api(%{token: token, token_type: token_type, amount: amount, token_ids: token_ids}) do + do_token_transfer_amount_for_api(token, token_type, amount, nil, token_ids) end - def token_transfer_amount_for_api(%{token: token, amount: amount, token_ids: token_ids}) do - do_token_transfer_amount_for_api(token, amount, nil, token_ids) + # TODO: remove this clause along with token transfer denormalization + defp do_token_transfer_amount_for_api(%Token{type: "ERC-20"}, nil, nil, nil, _token_ids) do + {:ok, nil} end - defp do_token_transfer_amount_for_api(%Token{type: "ERC-20"}, nil, nil, _token_ids) do + defp do_token_transfer_amount_for_api(_token, "ERC-20", nil, nil, _token_ids) do {:ok, nil} end + # TODO: remove this clause along with token transfer denormalization defp do_token_transfer_amount_for_api( %Token{type: "ERC-20", decimals: decimals}, + nil, amount, _amounts, _token_ids @@ -78,12 +121,43 @@ defmodule BlockScoutWeb.Tokens.Helper do {:ok, amount, decimals} end - defp do_token_transfer_amount_for_api(%Token{type: "ERC-721"}, _amount, _amounts, _token_ids) do + defp do_token_transfer_amount_for_api( + %Token{decimals: decimals}, + "ERC-20", + amount, + _amounts, + _token_ids + ) do + {:ok, amount, decimals} + end + + # TODO: remove this clause along with token transfer denormalization + defp do_token_transfer_amount_for_api(%Token{type: "ERC-721"}, nil, _amount, _amounts, _token_ids) do + {:ok, :erc721_instance} + end + + defp do_token_transfer_amount_for_api(_token, "ERC-721", _amount, _amounts, _token_ids) do {:ok, :erc721_instance} end + # TODO: remove this clause along with token transfer denormalization defp do_token_transfer_amount_for_api( %Token{type: "ERC-1155", decimals: decimals}, + nil, + amount, + amounts, + token_ids + ) do + if amount do + {:ok, :erc1155_instance, amount, decimals} + else + {:ok, :erc1155_instance, amounts, token_ids, decimals} + end + end + + defp do_token_transfer_amount_for_api( + %Token{decimals: decimals}, + "ERC-1155", amount, amounts, token_ids @@ -95,7 +169,7 @@ defmodule BlockScoutWeb.Tokens.Helper do end end - defp do_token_transfer_amount_for_api(_token, _amount, _amounts, _token_ids) do + defp do_token_transfer_amount_for_api(_token, _token_type, _amount, _amounts, _token_ids) do nil end diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex index dac7fc0bd57d..4d94264b5ed9 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex @@ -148,6 +148,7 @@ defmodule BlockScoutWeb.TransactionView do amount: nil, amounts: [], token_ids: token_transfer.token_ids, + token_type: token_transfer.token_type, to_address_hash: token_transfer.to_address_hash, from_address_hash: token_transfer.from_address_hash } @@ -162,6 +163,7 @@ defmodule BlockScoutWeb.TransactionView do amount: nil, amounts: amounts, token_ids: token_transfer.token_ids, + token_type: token_transfer.token_type, to_address_hash: token_transfer.to_address_hash, from_address_hash: token_transfer.from_address_hash } @@ -175,6 +177,7 @@ defmodule BlockScoutWeb.TransactionView do amount: token_transfer.amount, amounts: [], token_ids: token_transfer.token_ids, + token_type: token_transfer.token_type, to_address_hash: token_transfer.to_address_hash, from_address_hash: token_transfer.from_address_hash } diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 38e44c6d5c9d..3912a1c094b7 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -68,7 +68,7 @@ msgstr "" msgid "%{subnetwork} Explorer - BlockScout" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:371 +#: lib/block_scout_web/views/transaction_view.ex:374 #, 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:364 +#: lib/block_scout_web/views/transaction_view.ex:367 #, 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:480 +#: lib/block_scout_web/views/transaction_view.ex:483 #, elixir-autogen, elixir-format msgid "Contract Call" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:477 +#: lib/block_scout_web/views/transaction_view.ex:480 #, 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:222 +#: lib/block_scout_web/views/transaction_view.ex:225 #, elixir-autogen, elixir-format msgid "ERC-1155 " msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:220 +#: lib/block_scout_web/views/transaction_view.ex:223 #, 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:221 +#: lib/block_scout_web/views/transaction_view.ex:224 #, 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:375 +#: lib/block_scout_web/views/transaction_view.ex:378 #, elixir-autogen, elixir-format msgid "Error: %{reason}" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:373 +#: lib/block_scout_web/views/transaction_view.ex:376 #, 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:535 +#: lib/block_scout_web/views/transaction_view.ex:538 #, 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:536 +#: lib/block_scout_web/views/transaction_view.ex:539 #, 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:327 +#: lib/block_scout_web/views/transaction_view.ex:330 #, 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:370 -#: lib/block_scout_web/views/transaction_view.ex:409 +#: lib/block_scout_web/views/transaction_view.ex:373 +#: lib/block_scout_web/views/transaction_view.ex:412 #, 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:537 +#: lib/block_scout_web/views/transaction_view.ex:540 #, 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:538 +#: lib/block_scout_web/views/transaction_view.ex:541 #, 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:372 +#: lib/block_scout_web/views/transaction_view.ex:375 #, 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:471 +#: lib/block_scout_web/views/transaction_view.ex:474 #, 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:472 +#: lib/block_scout_web/views/transaction_view.ex:475 #, 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:470 +#: lib/block_scout_web/views/transaction_view.ex:473 #, 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:473 +#: lib/block_scout_web/views/transaction_view.ex:476 #, 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:534 +#: lib/block_scout_web/views/transaction_view.ex:537 #, 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:483 +#: lib/block_scout_web/views/transaction_view.ex:486 #, elixir-autogen, elixir-format msgid "Transaction" msgstr "" @@ -3177,7 +3177,7 @@ msgstr "" msgid "Uncles" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:363 +#: lib/block_scout_web/views/transaction_view.ex:366 #, elixir-autogen, elixir-format msgid "Unconfirmed" msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 328f17c0900d..c36ed9283cf0 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -68,7 +68,7 @@ msgstr "" msgid "%{subnetwork} Explorer - BlockScout" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:371 +#: lib/block_scout_web/views/transaction_view.ex:374 #, 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:364 +#: lib/block_scout_web/views/transaction_view.ex:367 #, 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:480 +#: lib/block_scout_web/views/transaction_view.ex:483 #, elixir-autogen, elixir-format msgid "Contract Call" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:477 +#: lib/block_scout_web/views/transaction_view.ex:480 #, 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:222 +#: lib/block_scout_web/views/transaction_view.ex:225 #, elixir-autogen, elixir-format msgid "ERC-1155 " msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:220 +#: lib/block_scout_web/views/transaction_view.ex:223 #, 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:221 +#: lib/block_scout_web/views/transaction_view.ex:224 #, 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:375 +#: lib/block_scout_web/views/transaction_view.ex:378 #, elixir-autogen, elixir-format msgid "Error: %{reason}" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:373 +#: lib/block_scout_web/views/transaction_view.ex:376 #, 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:535 +#: lib/block_scout_web/views/transaction_view.ex:538 #, 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:536 +#: lib/block_scout_web/views/transaction_view.ex:539 #, 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:327 +#: lib/block_scout_web/views/transaction_view.ex:330 #, 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:370 -#: lib/block_scout_web/views/transaction_view.ex:409 +#: lib/block_scout_web/views/transaction_view.ex:373 +#: lib/block_scout_web/views/transaction_view.ex:412 #, 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:537 +#: lib/block_scout_web/views/transaction_view.ex:540 #, 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:538 +#: lib/block_scout_web/views/transaction_view.ex:541 #, 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:372 +#: lib/block_scout_web/views/transaction_view.ex:375 #, 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:471 +#: lib/block_scout_web/views/transaction_view.ex:474 #, 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:472 +#: lib/block_scout_web/views/transaction_view.ex:475 #, 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:470 +#: lib/block_scout_web/views/transaction_view.ex:473 #, 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:473 +#: lib/block_scout_web/views/transaction_view.ex:476 #, 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:534 +#: lib/block_scout_web/views/transaction_view.ex:537 #, 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:483 +#: lib/block_scout_web/views/transaction_view.ex:486 #, elixir-autogen, elixir-format msgid "Transaction" msgstr "" @@ -3177,7 +3177,7 @@ msgstr "" msgid "Uncles" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:363 +#: lib/block_scout_web/views/transaction_view.ex:366 #, elixir-autogen, elixir-format msgid "Unconfirmed" msgstr "" diff --git a/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs b/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs index 6d7aad632f90..e4ff798de0a1 100644 --- a/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs +++ b/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs @@ -190,6 +190,7 @@ defmodule BlockScoutWeb.WebsocketV2Test do from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", to_address_hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", token_contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + token_type: "ERC-20", transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" }, %{ @@ -200,6 +201,7 @@ defmodule BlockScoutWeb.WebsocketV2Test do from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", to_address_hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", token_contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + token_type: "ERC-20", transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" }, %{ @@ -210,6 +212,7 @@ defmodule BlockScoutWeb.WebsocketV2Test do from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", to_address_hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", token_contract_address_hash: "0x00f38d4764929064f2d4d3a56520a76ab3df4151", + token_type: "ERC-20", transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" } ], diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index 0abc6ae7d48b..4189d1a1129f 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -961,7 +961,8 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do block: tx.block, block_number: tx.block_number, from_address: address, - token_contract_address: erc_20_token.contract_address + token_contract_address: erc_20_token.contract_address, + token_type: "ERC-20" ) end @@ -977,7 +978,8 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do block_number: tx.block_number, from_address: address, token_contract_address: erc_721_token.contract_address, - token_ids: [x] + token_ids: [x], + token_type: "ERC-721" ) end @@ -993,7 +995,8 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do block_number: tx.block_number, from_address: address, token_contract_address: erc_1155_token.contract_address, - token_ids: [x] + token_ids: [x], + token_type: "ERC-1155" ) end @@ -1086,7 +1089,8 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do block: tx.block, block_number: tx.block_number, from_address: address, - token_contract_address: erc_20_token.contract_address + token_contract_address: erc_20_token.contract_address, + token_type: "ERC-20" ) end @@ -1102,7 +1106,8 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do block_number: tx.block_number, to_address: address, token_contract_address: erc_721_token.contract_address, - token_ids: [x] + token_ids: [x], + token_type: "ERC-721" ) end @@ -1166,6 +1171,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do block_number: tx.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn _x -> id end), + token_type: "ERC-1155", amounts: Enum.map(0..50, fn x -> x end) ) end @@ -1200,7 +1206,8 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do block: tx.block, block_number: tx.block_number, token_contract_address: token.contract_address, - token_ids: [i] + token_ids: [i], + token_type: "ERC-721" ) end @@ -1228,6 +1235,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do block_number: tx.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn x -> x end), + token_type: "ERC-1155", amounts: Enum.map(0..50, fn x -> x end) ) @@ -1270,6 +1278,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do block_number: tx_1.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..24, fn x -> x end), + token_type: "ERC-1155", amounts: Enum.map(0..24, fn x -> x end) ) @@ -1288,6 +1297,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do block_number: tx_2.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(25..49, fn x -> x end), + token_type: "ERC-1155", amounts: Enum.map(25..49, fn x -> x end) ) @@ -1304,6 +1314,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do block_number: tx_2.block_number, token_contract_address: token.contract_address, token_ids: [50], + token_type: "ERC-1155", amounts: [50] ) @@ -1332,6 +1343,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do block_number: tx_1.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..24, fn x -> x end), + token_type: "ERC-1155", amounts: Enum.map(0..24, fn x -> x end) ) @@ -1350,6 +1362,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do block_number: tx_2.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(25..50, fn x -> x end), + token_type: "ERC-1155", amounts: Enum.map(25..50, fn x -> x end) ) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs index 0cc36ad595f6..18c30bfd19e6 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs @@ -160,6 +160,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do block_number: tx.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn _x -> id end), + token_type: "ERC-1155", amounts: Enum.map(0..50, fn x -> x end) ) end @@ -192,7 +193,8 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do block: tx.block, block_number: tx.block_number, token_contract_address: token.contract_address, - token_ids: [i] + token_ids: [i], + token_type: "ERC-721" ) end @@ -218,6 +220,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do block_number: tx.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn x -> x end), + token_type: "ERC-1155", amounts: Enum.map(0..50, fn x -> x end) ) @@ -250,6 +253,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do block_number: tx_1.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..24, fn x -> x end), + token_type: "ERC-1155", amounts: Enum.map(0..24, fn x -> x end) ) @@ -267,6 +271,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do block_number: tx_2.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(25..49, fn x -> x end), + token_type: "ERC-1155", amounts: Enum.map(25..49, fn x -> x end) ) @@ -282,6 +287,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do block_number: tx_2.block_number, token_contract_address: token.contract_address, token_ids: [50], + token_type: "ERC-1155", amounts: [50] ) @@ -308,6 +314,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do block_number: tx_1.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..24, fn x -> x end), + token_type: "ERC-1155", amounts: Enum.map(0..24, fn x -> x end) ) @@ -325,6 +332,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do block_number: tx_2.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(25..50, fn x -> x end), + token_type: "ERC-1155", amounts: Enum.map(25..50, fn x -> x end) ) @@ -997,7 +1005,8 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do insert(:token_transfer, token_contract_address: token.contract_address, transaction: transaction, - token_ids: [0] + token_ids: [0], + token_type: "ERC-721" ) for _ <- 1..50 do @@ -1067,6 +1076,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do token_contract_address: token.contract_address, transaction: transaction, token_ids: [id + 1], + token_type: "ERC-1155", amounts: [1] ) @@ -1075,6 +1085,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do token_contract_address: token.contract_address, transaction: transaction, token_ids: [id, id + 1], + token_type: "ERC-1155", amounts: [1, 2] ) @@ -1088,7 +1099,8 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do insert(:token_transfer, token_contract_address: token.contract_address, transaction: transaction, - token_ids: [id] + token_ids: [id], + token_type: "ERC-1155" ) end @@ -1120,7 +1132,8 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do block: tx.block, block_number: tx.block_number, token_contract_address: token.contract_address, - token_ids: [id] + token_ids: [id], + token_type: "ERC-721" ) end @@ -1155,6 +1168,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do block_number: tx.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn _x -> id end), + token_type: "ERC-1155", amounts: Enum.map(0..50, fn x -> x end) ) @@ -1183,6 +1197,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do block_number: tx.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn x -> x end) ++ [id], + token_type: "ERC-1155", amounts: Enum.map(1..51, fn x -> x end) ++ [amount] ) end @@ -1312,7 +1327,8 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do insert_list(count, :token_transfer, token_contract_address: token.contract_address, transaction: transaction, - token_ids: [0] + token_ids: [0], + token_type: "ERC-721" ) request = get(conn, "/api/v2/tokens/#{token.contract_address.hash}/instances/0/transfers-count") diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs index 9fdad1782da8..1f0e671812ff 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs @@ -245,6 +245,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do block_number: tx.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn x -> x end), + token_type: "ERC-1155", amounts: Enum.map(0..50, fn x -> x end) ) @@ -271,6 +272,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do block_number: tx.block_number, token_contract_address: token.contract_address, token_ids: [1], + token_type: "ERC-1155", amounts: [2], amount: nil ) @@ -581,7 +583,8 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do block: tx.block, block_number: tx.block_number, token_contract_address: erc_1155_token.contract_address, - token_ids: [x] + token_ids: [x], + token_type: "ERC-1155" ) end |> Enum.reverse() @@ -595,7 +598,8 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do block: tx.block, block_number: tx.block_number, token_contract_address: erc_721_token.contract_address, - token_ids: [x] + token_ids: [x], + token_type: "ERC-721" ) end |> Enum.reverse() @@ -608,7 +612,8 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do transaction: tx, block: tx.block, block_number: tx.block_number, - token_contract_address: erc_20_token.contract_address + token_contract_address: erc_20_token.contract_address, + token_type: "ERC-20" ) end |> Enum.reverse() @@ -723,6 +728,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do block_number: tx.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn _x -> id end), + token_type: "ERC-1155", amounts: Enum.map(0..50, fn x -> x end) ) end @@ -758,7 +764,8 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do block: tx.block, block_number: tx.block_number, token_contract_address: token.contract_address, - token_ids: [i] + token_ids: [i], + token_type: "ERC-721" ) end @@ -788,6 +795,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do block_number: tx.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn x -> x end), + token_type: "ERC-1155", amounts: Enum.map(0..50, fn x -> x end) ) @@ -823,6 +831,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do block_number: tx.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..24, fn x -> x end), + token_type: "ERC-1155", amounts: Enum.map(0..24, fn x -> x end) ) @@ -838,6 +847,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do block_number: tx.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(25..49, fn x -> x end), + token_type: "ERC-1155", amounts: Enum.map(25..49, fn x -> x end) ) @@ -853,6 +863,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do block_number: tx.block_number, token_contract_address: token.contract_address, token_ids: [50], + token_type: "ERC-1155", amounts: [50] ) @@ -879,6 +890,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do block_number: tx.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..24, fn x -> x end), + token_type: "ERC-1155", amounts: Enum.map(0..24, fn x -> x end) ) @@ -894,6 +906,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do block_number: tx.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(25..50, fn x -> x end), + token_type: "ERC-1155", amounts: Enum.map(25..50, fn x -> x end) ) diff --git a/apps/block_scout_web/test/block_scout_web/views/tokens/helper_test.exs b/apps/block_scout_web/test/block_scout_web/views/tokens/helper_test.exs index a8815379cedb..bcf41d9d4660 100644 --- a/apps/block_scout_web/test/block_scout_web/views/tokens/helper_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/tokens/helper_test.exs @@ -27,14 +27,14 @@ 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_ids: [1]) + token_transfer = build(:token_transfer, token: token, amount: nil, token_ids: [1], token_type: "ERC-721") assert Helper.token_transfer_amount(token_transfer) == {:ok, :erc721_instance} end test "returns nothing for unknown token's type" do token = build(:token, type: "unknown") - token_transfer = build(:token_transfer, token: token) + token_transfer = build(:token_transfer, token: token, token_type: "unknown") assert Helper.token_transfer_amount(token_transfer) == nil end diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 414cb38de6ca..dcc12a69bde8 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -116,6 +116,7 @@ config :explorer, Explorer.Migrator.AddressCurrentTokenBalanceTokenType, enabled config :explorer, Explorer.Migrator.AddressTokenBalanceTokenType, enabled: true config :explorer, Explorer.Migrator.SanitizeMissingBlockRanges, enabled: true config :explorer, Explorer.Migrator.SanitizeIncorrectNFTTokenTransfers, enabled: true +config :explorer, Explorer.Migrator.TokenTransferTokenType, enabled: true config :explorer, Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand, enabled: true diff --git a/apps/explorer/config/runtime/test.exs b/apps/explorer/config/runtime/test.exs index db5d2eaee689..27e34b729c45 100644 --- a/apps/explorer/config/runtime/test.exs +++ b/apps/explorer/config/runtime/test.exs @@ -40,6 +40,7 @@ config :explorer, Explorer.Migrator.AddressCurrentTokenBalanceTokenType, enabled config :explorer, Explorer.Migrator.AddressTokenBalanceTokenType, enabled: false config :explorer, Explorer.Migrator.SanitizeMissingBlockRanges, enabled: false config :explorer, Explorer.Migrator.SanitizeIncorrectNFTTokenTransfers, enabled: false +config :explorer, Explorer.Migrator.TokenTransferTokenType, enabled: false config :explorer, realtime_events_sender: Explorer.Chain.Events.SimpleSender diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 961662975551..713b1f51d98b 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -130,7 +130,8 @@ defmodule Explorer.Application do configure(Explorer.Migrator.AddressCurrentTokenBalanceTokenType), configure(Explorer.Migrator.AddressTokenBalanceTokenType), configure(Explorer.Migrator.SanitizeMissingBlockRanges), - configure(Explorer.Migrator.SanitizeIncorrectNFTTokenTransfers) + configure(Explorer.Migrator.SanitizeIncorrectNFTTokenTransfers), + configure(Explorer.Migrator.TokenTransferTokenType) ] |> List.flatten() diff --git a/apps/explorer/lib/explorer/chain/cache/background_migrations.ex b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex index 3470f48c08b7..f452078b6425 100644 --- a/apps/explorer/lib/explorer/chain/cache/background_migrations.ex +++ b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex @@ -9,13 +9,15 @@ defmodule Explorer.Chain.Cache.BackgroundMigrations do name: :background_migrations_status, key: :denormalization_finished, key: :tb_token_type_finished, - key: :ctb_token_type_finished + key: :ctb_token_type_finished, + key: :tt_denormalization_finished @dialyzer :no_match alias Explorer.Migrator.{ AddressCurrentTokenBalanceTokenType, AddressTokenBalanceTokenType, + TokenTransferTokenType, TransactionsDenormalization } @@ -42,4 +44,12 @@ defmodule Explorer.Chain.Cache.BackgroundMigrations do {:return, false} end + + defp handle_fallback(:tt_denormalization_finished) do + Task.start(fn -> + set_tt_denormalization_finished(TokenTransferTokenType.migration_finished?()) + end) + + {:return, false} + end end diff --git a/apps/explorer/lib/explorer/chain/denormalization_helper.ex b/apps/explorer/lib/explorer/chain/denormalization_helper.ex index 0199fc7359e1..308a6483fc5d 100644 --- a/apps/explorer/lib/explorer/chain/denormalization_helper.ex +++ b/apps/explorer/lib/explorer/chain/denormalization_helper.ex @@ -47,4 +47,6 @@ defmodule Explorer.Chain.DenormalizationHelper do end def denormalization_finished?, do: BackgroundMigrations.get_denormalization_finished() + + def tt_denormalization_finished?, do: BackgroundMigrations.get_tt_denormalization_finished() end diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex index fb66db434b91..8aaad171c4c2 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex @@ -15,6 +15,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do Address, Block, BlockNumberHelper, + DenormalizationHelper, Import, PendingBlockOperation, Token, @@ -710,28 +711,51 @@ defmodule Explorer.Chain.Import.Runner.Blocks do end defp forked_token_transfers_query(forked_transaction_hashes) do - from(token_transfer in TokenTransfer, - where: token_transfer.transaction_hash in ^forked_transaction_hashes, - inner_join: token in Token, - on: token.contract_address_hash == token_transfer.token_contract_address_hash, - where: token.type == "ERC-721", - inner_join: instance in Instance, - on: - fragment("? @> ARRAY[?::decimal]", token_transfer.token_ids, instance.token_id) and - instance.token_contract_address_hash == token_transfer.token_contract_address_hash, - # per one token instance we will have only one token transfer - where: - token_transfer.block_number == instance.owner_updated_at_block and - token_transfer.log_index == instance.owner_updated_at_log_index, - select: %{ - from: token_transfer.from_address_hash, - to: token_transfer.to_address_hash, - token_id: instance.token_id, - token_contract_address_hash: token_transfer.token_contract_address_hash, - block_number: token_transfer.block_number, - log_index: token_transfer.log_index - } - ) + if DenormalizationHelper.tt_denormalization_finished?() do + from(token_transfer in TokenTransfer, + where: token_transfer.transaction_hash in ^forked_transaction_hashes, + where: token_transfer.token_type == "ERC-721", + inner_join: instance in Instance, + on: + fragment("? @> ARRAY[?::decimal]", token_transfer.token_ids, instance.token_id) and + instance.token_contract_address_hash == token_transfer.token_contract_address_hash, + # per one token instance we will have only one token transfer + where: + token_transfer.block_number == instance.owner_updated_at_block and + token_transfer.log_index == instance.owner_updated_at_log_index, + select: %{ + from: token_transfer.from_address_hash, + to: token_transfer.to_address_hash, + token_id: instance.token_id, + token_contract_address_hash: token_transfer.token_contract_address_hash, + block_number: token_transfer.block_number, + log_index: token_transfer.log_index + } + ) + else + from(token_transfer in TokenTransfer, + where: token_transfer.transaction_hash in ^forked_transaction_hashes, + inner_join: token in Token, + on: token.contract_address_hash == token_transfer.token_contract_address_hash, + where: token.type == "ERC-721", + inner_join: instance in Instance, + on: + fragment("? @> ARRAY[?::decimal]", token_transfer.token_ids, instance.token_id) and + instance.token_contract_address_hash == token_transfer.token_contract_address_hash, + # per one token instance we will have only one token transfer + where: + token_transfer.block_number == instance.owner_updated_at_block and + token_transfer.log_index == instance.owner_updated_at_log_index, + select: %{ + from: token_transfer.from_address_hash, + to: token_transfer.to_address_hash, + token_id: instance.token_id, + token_contract_address_hash: token_transfer.token_contract_address_hash, + block_number: token_transfer.block_number, + log_index: token_transfer.log_index + } + ) + end end defp token_instances_on_conflict do diff --git a/apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex b/apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex index f50e98691e61..ad6681fcaaba 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex @@ -90,18 +90,20 @@ defmodule Explorer.Chain.Import.Runner.TokenTransfers do to_address_hash: fragment("EXCLUDED.to_address_hash"), token_contract_address_hash: fragment("EXCLUDED.token_contract_address_hash"), token_ids: fragment("EXCLUDED.token_ids"), + token_type: fragment("EXCLUDED.token_type"), inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token_transfer.inserted_at), updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token_transfer.updated_at) ] ], where: fragment( - "(EXCLUDED.amount, EXCLUDED.from_address_hash, EXCLUDED.to_address_hash, EXCLUDED.token_contract_address_hash, EXCLUDED.token_ids) IS DISTINCT FROM (?, ? ,? , ?, ?)", + "(EXCLUDED.amount, EXCLUDED.from_address_hash, EXCLUDED.to_address_hash, EXCLUDED.token_contract_address_hash, EXCLUDED.token_ids, EXCLUDED.token_type) IS DISTINCT FROM (?, ?, ?, ?, ?, ?)", token_transfer.amount, token_transfer.from_address_hash, token_transfer.to_address_hash, token_transfer.token_contract_address_hash, - token_transfer.token_ids + token_transfer.token_ids, + token_transfer.token_type ) ) end diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index 13e656365798..b1ad716af388 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -68,6 +68,7 @@ defmodule Explorer.Chain.TokenTransfer do field(:amounts, {:array, :decimal}) field(:token_ids, {:array, :decimal}) field(:index_in_batch, :integer, virtual: true) + field(:token_type, :string) belongs_to(:from_address, Address, foreign_key: :from_address_hash, @@ -115,7 +116,7 @@ defmodule Explorer.Chain.TokenTransfer do timestamps() end - @required_attrs ~w(block_number log_index from_address_hash to_address_hash token_contract_address_hash transaction_hash block_hash)a + @required_attrs ~w(block_number log_index from_address_hash to_address_hash token_contract_address_hash transaction_hash block_hash token_type)a @optional_attrs ~w(amount amounts token_ids)a @doc false @@ -345,7 +346,11 @@ defmodule Explorer.Chain.TokenTransfer do def filter_by_type(query, []), do: query def filter_by_type(query, token_types) when is_list(token_types) do - where(query, [token: token], token.type in ^token_types) + if DenormalizationHelper.tt_denormalization_finished?() do + where(query, [tt], tt.token_type in ^token_types) + else + where(query, [token: token], token.type in ^token_types) + end end def filter_by_type(query, _), do: query diff --git a/apps/explorer/lib/explorer/migrator/token_transfer_token_type.ex b/apps/explorer/lib/explorer/migrator/token_transfer_token_type.ex new file mode 100644 index 000000000000..c4511ad363fc --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/token_transfer_token_type.ex @@ -0,0 +1,76 @@ +defmodule Explorer.Migrator.TokenTransferTokenType do + @moduledoc """ + Migrates all token_transfers to have set token_type + """ + + use Explorer.Migrator.FillingMigration + + import Ecto.Query + + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Chain.TokenTransfer + alias Explorer.Migrator.FillingMigration + alias Explorer.Repo + + @migration_name "tt_denormalization" + + @impl FillingMigration + def migration_name, do: @migration_name + + @impl FillingMigration + def last_unprocessed_identifiers do + limit = batch_size() * concurrency() + + unprocessed_data_query() + |> select([tt], {tt.transaction_hash, tt.block_hash, tt.log_index}) + |> limit(^limit) + |> Repo.all(timeout: :infinity) + end + + @impl FillingMigration + def unprocessed_data_query do + from(tt in TokenTransfer, where: is_nil(tt.token_type)) + end + + @impl FillingMigration + def update_batch(token_transfer_ids) do + token_transfer_ids + |> build_update_query() + |> Repo.query!([], timeout: :infinity) + end + + @impl FillingMigration + def update_cache do + BackgroundMigrations.set_tb_token_type_finished(true) + end + + defp build_update_query(token_transfer_ids) do + """ + UPDATE token_transfers tt + SET token_type = t.type + FROM tokens t + WHERE tt.token_contract_address_hash = t.contract_address_hash + AND (tt.transaction_hash, tt.block_hash, tt.log_index) IN #{encode_token_transfer_ids(token_transfer_ids)}; + """ + end + + defp encode_token_transfer_ids(ids) do + encoded_values = + ids + |> Enum.reduce("", fn {t_hash, b_hash, log_index}, acc -> + acc <> "('#{hash_to_query_string(t_hash)}', '#{hash_to_query_string(b_hash)}', #{log_index})," + end) + |> String.trim_trailing(",") + + "(#{encoded_values})" + end + + defp hash_to_query_string(hash) do + s_hash = + hash + |> to_string() + |> String.trim_leading("0") + + "\\#{s_hash}" + end +end diff --git a/apps/explorer/priv/repo/migrations/20240122102141_add_token_type_to_token_transfers.exs b/apps/explorer/priv/repo/migrations/20240122102141_add_token_type_to_token_transfers.exs new file mode 100644 index 000000000000..b99fa7b58f39 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240122102141_add_token_type_to_token_transfers.exs @@ -0,0 +1,11 @@ +defmodule Explorer.Repo.Migrations.AddTokenTypeToTokenTransfers do + use Ecto.Migration + + def change do + alter table(:token_transfers) do + add_if_not_exists(:token_type, :string) + end + + create_if_not_exists(index(:token_transfers, :token_type)) + end +end diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs index 0d2fbfa02de3..0cbfa186f5ba 100644 --- a/apps/explorer/test/explorer/chain/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -163,6 +163,7 @@ defmodule Explorer.Chain.ImportTest do from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", to_address_hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", token_contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + token_type: "ERC-20", transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" } ], diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index e57ebb13a0bc..ec9f58f0d8f6 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -1376,6 +1376,7 @@ defmodule Explorer.ChainTest do from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", to_address_hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", token_contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + token_type: "ERC-20", transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" } ] diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index fbb9808f27e1..33670478d74e 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -748,7 +748,7 @@ defmodule Explorer.Factory do contract_code = Map.fetch!(contract_code_info(), :bytecode) token_address = insert(:contract_address, contract_code: contract_code) - insert(:token, contract_address: token_address) + token = insert(:token, contract_address: token_address) %TokenTransfer{ block: build(:block), @@ -757,6 +757,7 @@ defmodule Explorer.Factory do from_address: from_address, to_address: to_address, token_contract_address: token_address, + token_type: token.type, transaction: log.transaction, log_index: log.index } From 5fa357e29db873ff24787662d2964aa9b31d7a21 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Thu, 15 Feb 2024 11:49:37 +0400 Subject: [PATCH 486/607] Denormalization names refactoring + small token transfer type fix --- apps/explorer/lib/explorer/chain.ex | 4 ++-- .../lib/explorer/chain/beacon/reader.ex | 4 ++-- .../chain/cache/background_migrations.ex | 6 +++--- .../explorer/chain/cache/gas_price_oracle.ex | 2 +- .../explorer/chain/denormalization_helper.ex | 10 +++++----- apps/explorer/lib/explorer/chain/search.ex | 2 +- .../chain/transaction/history/historian.ex | 6 +++--- apps/explorer/lib/explorer/etherscan.ex | 16 ++++++++-------- apps/explorer/lib/explorer/etherscan/logs.ex | 4 ++-- .../migrator/transactions_denormalization.ex | 2 +- .../lib/indexer/transform/token_transfers.ex | 18 +++++------------- .../indexer/transform/token_transfers_test.exs | 4 ++-- 12 files changed, 35 insertions(+), 43 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 0033679fedbd..c18ecd787f55 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -352,7 +352,7 @@ defmodule Explorer.Chain do to_block = to_block(options) base = - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do from(log in Log, order_by: [desc: log.block_number, desc: log.index], where: log.address_hash == ^address_hash, @@ -482,7 +482,7 @@ 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 = - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do from( transaction in Transaction, where: transaction.block_hash in ^block_hashes and transaction.block_consensus == true, diff --git a/apps/explorer/lib/explorer/chain/beacon/reader.ex b/apps/explorer/lib/explorer/chain/beacon/reader.ex index 9dd3623b35f8..5fb8189ec585 100644 --- a/apps/explorer/lib/explorer/chain/beacon/reader.ex +++ b/apps/explorer/lib/explorer/chain/beacon/reader.ex @@ -126,7 +126,7 @@ defmodule Explorer.Chain.Beacon.Reader do |> limit(10) query_with_denormalization = - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do query |> order_by([bt, transaction], desc: transaction.block_consensus, desc: transaction.block_number) |> select([bt, transaction], %{ @@ -181,7 +181,7 @@ defmodule Explorer.Chain.Beacon.Reader do ) query_with_denormalization = - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do query |> distinct([transaction_blob, transaction, blob], transaction.block_timestamp) |> select([transaction_blob, transaction, blob], transaction.block_timestamp) diff --git a/apps/explorer/lib/explorer/chain/cache/background_migrations.ex b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex index f452078b6425..c4449853b290 100644 --- a/apps/explorer/lib/explorer/chain/cache/background_migrations.ex +++ b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex @@ -7,7 +7,7 @@ defmodule Explorer.Chain.Cache.BackgroundMigrations do use Explorer.Chain.MapCache, name: :background_migrations_status, - key: :denormalization_finished, + key: :transactions_denormalization_finished, key: :tb_token_type_finished, key: :ctb_token_type_finished, key: :tt_denormalization_finished @@ -21,9 +21,9 @@ defmodule Explorer.Chain.Cache.BackgroundMigrations do TransactionsDenormalization } - defp handle_fallback(:denormalization_finished) do + defp handle_fallback(:transactions_denormalization_finished) do Task.start(fn -> - set_denormalization_finished(TransactionsDenormalization.migration_finished?()) + set_transactions_denormalization_finished(TransactionsDenormalization.migration_finished?()) end) {:return, false} diff --git a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex index ea335fecf786..05471ac2887e 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex @@ -99,7 +99,7 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do end fee_query = - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do from( transaction in Transaction, where: transaction.block_consensus == true, diff --git a/apps/explorer/lib/explorer/chain/denormalization_helper.ex b/apps/explorer/lib/explorer/chain/denormalization_helper.ex index 308a6483fc5d..0839080ad23a 100644 --- a/apps/explorer/lib/explorer/chain/denormalization_helper.ex +++ b/apps/explorer/lib/explorer/chain/denormalization_helper.ex @@ -7,7 +7,7 @@ defmodule Explorer.Chain.DenormalizationHelper do @spec extend_block_necessity(keyword(), :optional | :required) :: keyword() def extend_block_necessity(opts, necessity \\ :optional) do - if denormalization_finished?() do + if transactions_denormalization_finished?() do opts else Keyword.update(opts, :necessity_by_association, %{:block => necessity}, &Map.put(&1, :block, necessity)) @@ -16,7 +16,7 @@ defmodule Explorer.Chain.DenormalizationHelper do @spec extend_transaction_block_necessity(keyword(), :optional | :required) :: keyword() def extend_transaction_block_necessity(opts, necessity \\ :optional) do - if denormalization_finished?() do + if transactions_denormalization_finished?() do opts else Keyword.update( @@ -30,7 +30,7 @@ defmodule Explorer.Chain.DenormalizationHelper do @spec extend_transaction_preload(list()) :: list() def extend_transaction_preload(preloads) do - if denormalization_finished?() do + if transactions_denormalization_finished?() do preloads else [transaction: :block] ++ (preloads -- [:transaction]) @@ -39,14 +39,14 @@ defmodule Explorer.Chain.DenormalizationHelper do @spec extend_block_preload(list()) :: list() def extend_block_preload(preloads) do - if denormalization_finished?() do + if transactions_denormalization_finished?() do preloads else [:block | preloads] end end - def denormalization_finished?, do: BackgroundMigrations.get_denormalization_finished() + def transactions_denormalization_finished?, do: BackgroundMigrations.get_transactions_denormalization_finished() def tt_denormalization_finished?, do: BackgroundMigrations.get_tt_denormalization_finished() end diff --git a/apps/explorer/lib/explorer/chain/search.ex b/apps/explorer/lib/explorer/chain/search.ex index 653418299627..4e2a50ef8c74 100644 --- a/apps/explorer/lib/explorer/chain/search.ex +++ b/apps/explorer/lib/explorer/chain/search.ex @@ -403,7 +403,7 @@ defmodule Explorer.Chain.Search do end defp search_tx_query(term) do - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do transaction_search_fields = search_fields() |> Map.put(:tx_hash, dynamic([transaction], transaction.hash)) diff --git a/apps/explorer/lib/explorer/chain/transaction/history/historian.ex b/apps/explorer/lib/explorer/chain/transaction/history/historian.ex index 2c8e4479cf6d..de731e61c872 100644 --- a/apps/explorer/lib/explorer/chain/transaction/history/historian.ex +++ b/apps/explorer/lib/explorer/chain/transaction/history/historian.ex @@ -89,7 +89,7 @@ defmodule Explorer.Chain.Transaction.History.Historian do Logger.info("tx/per day chart: min/max block numbers [#{min_block}, #{max_block}]") all_transactions_query = - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do from( transaction in Transaction, where: transaction.block_number >= ^min_block and transaction.block_number <= ^max_block, @@ -112,7 +112,7 @@ defmodule Explorer.Chain.Transaction.History.Historian do ) query = - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do all_transactions_query else from(transaction in subquery(all_transactions_query), @@ -128,7 +128,7 @@ defmodule Explorer.Chain.Transaction.History.Historian do Logger.info("tx/per day chart: total gas used #{gas_used}") total_fee_query = - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do from(transaction in subquery(all_transactions_query), select: fragment("SUM(? * ?)", transaction.gas_price, transaction.gas_used) ) diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index 67e51f75e9a4..9c1268891252 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -103,7 +103,7 @@ 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 = - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do from( it in InternalTransaction, inner_join: transaction in assoc(it, :transaction), @@ -229,7 +229,7 @@ defmodule Explorer.Etherscan do |> Repo.replica().all() else query = - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do from( it in InternalTransaction, inner_join: transaction in assoc(it, :transaction), @@ -472,7 +472,7 @@ defmodule Explorer.Etherscan do defp list_transactions(address_hash, max_block_number, options) do query = - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do from( t in Transaction, where: not is_nil(t.block_hash), @@ -566,7 +566,7 @@ defmodule Explorer.Etherscan do |> where_contract_address_match(contract_address_hash) wrapped_query = - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do from( tt in subquery(tt_specific_token_query), inner_join: t in Transaction, @@ -655,7 +655,7 @@ defmodule Explorer.Etherscan do defp where_start_transaction_block_match(query, %{startblock: nil}), do: query defp where_start_transaction_block_match(query, %{startblock: start_block} = params) do - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do where(query, [transaction], transaction.block_number >= ^start_block) else where_start_block_match(query, params) @@ -665,7 +665,7 @@ defmodule Explorer.Etherscan do defp where_end_transaction_block_match(query, %{endblock: nil}), do: query defp where_end_transaction_block_match(query, %{endblock: end_block} = params) do - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do where(query, [transaction], transaction.block_number <= ^end_block) else where_end_block_match(query, params) @@ -687,7 +687,7 @@ defmodule Explorer.Etherscan do defp where_start_timestamp_match(query, %{start_timestamp: nil}), do: query defp where_start_timestamp_match(query, %{start_timestamp: start_timestamp}) do - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do where(query, [transaction], ^start_timestamp <= transaction.block_timestamp) else where(query, [..., block], ^start_timestamp <= block.timestamp) @@ -697,7 +697,7 @@ defmodule Explorer.Etherscan do defp where_end_timestamp_match(query, %{end_timestamp: nil}), do: query defp where_end_timestamp_match(query, %{end_timestamp: end_timestamp}) do - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do where(query, [transaction], transaction.block_timestamp <= ^end_timestamp) else where(query, [..., block], block.timestamp <= ^end_timestamp) diff --git a/apps/explorer/lib/explorer/etherscan/logs.ex b/apps/explorer/lib/explorer/etherscan/logs.ex index a0c432ce4432..c3b79a439fc8 100644 --- a/apps/explorer/lib/explorer/etherscan/logs.ex +++ b/apps/explorer/lib/explorer/etherscan/logs.ex @@ -75,7 +75,7 @@ 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) - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do logs_query = Log |> where_topic_match(prepared_filter) @@ -206,7 +206,7 @@ defmodule Explorer.Etherscan.Logs do prepared_filter = Map.merge(@base_filter, filter) logs_query = where_topic_match(Log, prepared_filter) - if DenormalizationHelper.denormalization_finished?() do + if DenormalizationHelper.transactions_denormalization_finished?() do block_transaction_query = from(transaction in Transaction, where: transaction.block_number >= ^prepared_filter.from_block, diff --git a/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex b/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex index fe66548aba14..4e8493ad0a0c 100644 --- a/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex +++ b/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex @@ -48,6 +48,6 @@ defmodule Explorer.Migrator.TransactionsDenormalization do @impl FillingMigration def update_cache do - BackgroundMigrations.set_denormalization_finished(true) + BackgroundMigrations.set_transactions_denormalization_finished(true) end end diff --git a/apps/indexer/lib/indexer/transform/token_transfers.ex b/apps/indexer/lib/indexer/transform/token_transfers.ex index 09a4b1fea641..5163c5f7f458 100644 --- a/apps/indexer/lib/indexer/transform/token_transfers.ex +++ b/apps/indexer/lib/indexer/transform/token_transfers.ex @@ -47,8 +47,8 @@ defmodule Indexer.Transform.TokenTransfers do erc1155_token_transfers.token_transfers ++ erc20_and_erc721_token_transfers.token_transfers ++ weth_transfers.token_transfers - {tokens, sanitized_token_transfers} = sanitize_token_types(rough_tokens, rough_token_transfers) - token_transfers = sanitize_weth_transfers(tokens, sanitized_token_transfers, weth_transfers.token_transfers) + tokens = sanitize_token_types(rough_tokens, rough_token_transfers) + token_transfers = sanitize_weth_transfers(tokens, rough_token_transfers, weth_transfers.token_transfers) token_transfers |> Enum.filter(fn token_transfer -> @@ -129,17 +129,9 @@ defmodule Indexer.Transform.TokenTransfers do if token_type_priority(old_type) > token_type_priority(new_type), do: old_type, else: new_type end) - actual_tokens = - Enum.map(tokens, fn %{contract_address_hash: hash} = token -> - Map.put(token, :type, actual_token_types_map[hash]) - end) - - actual_token_transfers = - Enum.map(token_transfers, fn %{token_contract_address_hash: hash} = tt -> - Map.put(tt, :token_type, actual_token_types_map[hash]) - end) - - {actual_tokens, actual_token_transfers} + Enum.map(tokens, fn %{contract_address_hash: hash} = token -> + Map.put(token, :type, actual_token_types_map[hash]) + end) end defp define_token_type(token_transfers) do diff --git a/apps/indexer/test/indexer/transform/token_transfers_test.exs b/apps/indexer/test/indexer/transform/token_transfers_test.exs index 0c670035cc35..3ff6e5e52fc0 100644 --- a/apps/indexer/test/indexer/transform/token_transfers_test.exs +++ b/apps/indexer/test/indexer/transform/token_transfers_test.exs @@ -313,7 +313,7 @@ defmodule Indexer.Transform.TokenTransfersTest do } assert %{ - token_transfers: [%{token_contract_address_hash: ^contract_address_hash, token_type: "ERC-1155"}], + token_transfers: [%{token_contract_address_hash: ^contract_address_hash, token_type: "ERC-20"}], tokens: [%{contract_address_hash: ^contract_address_hash, type: "ERC-1155"}] } = TokenTransfers.parse([log]) end @@ -352,7 +352,7 @@ defmodule Indexer.Transform.TokenTransfersTest do assert %{ token_transfers: [ %{token_contract_address_hash: ^contract_address_hash, token_type: "ERC-1155"}, - %{token_contract_address_hash: ^contract_address_hash, token_type: "ERC-1155"} + %{token_contract_address_hash: ^contract_address_hash, token_type: "ERC-20"} ], tokens: [%{contract_address_hash: ^contract_address_hash, type: "ERC-1155"}] } = TokenTransfers.parse(logs) From dbaedbca2cbcf50a700836bd59ee915222790343 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Thu, 15 Feb 2024 16:27:44 +0400 Subject: [PATCH 487/607] Create token_transfers token_type index concurrently --- .../20240122102141_add_token_type_to_token_transfers.exs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/explorer/priv/repo/migrations/20240122102141_add_token_type_to_token_transfers.exs b/apps/explorer/priv/repo/migrations/20240122102141_add_token_type_to_token_transfers.exs index b99fa7b58f39..7ba7ddff029b 100644 --- a/apps/explorer/priv/repo/migrations/20240122102141_add_token_type_to_token_transfers.exs +++ b/apps/explorer/priv/repo/migrations/20240122102141_add_token_type_to_token_transfers.exs @@ -1,11 +1,13 @@ defmodule Explorer.Repo.Migrations.AddTokenTypeToTokenTransfers do use Ecto.Migration + @disable_ddl_transaction true + @disable_migration_lock true def change do alter table(:token_transfers) do add_if_not_exists(:token_type, :string) end - create_if_not_exists(index(:token_transfers, :token_type)) + create_if_not_exists(index(:token_transfers, :token_type, concurrently: true)) end end From ac736defdc1b53c8e000c7fe1b358da21ea1db38 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Tue, 20 Feb 2024 17:32:22 +0400 Subject: [PATCH 488/607] Add block_consensus to token_transfers --- .../explorer/chain/import/runner/blocks.ex | 12 +++ .../import/runner/internal_transactions.ex | 20 +++-- .../chain/import/runner/token_transfers.ex | 6 +- .../chain/import/runner/transactions.ex | 12 ++- .../lib/explorer/chain/token_transfer.ex | 52 ++++++------ apps/explorer/lib/explorer/etherscan.ex | 1 + .../migrator/token_transfer_token_type.ex | 12 ++- ...add_block_consensus_to_token_transfers.exs | 13 +++ .../chain/import/runner/blocks_test.exs | 5 ++ .../token_transfer_token_type_test.exs | 82 +++++++++++++++++++ apps/explorer/test/support/factory.ex | 3 +- 11 files changed, 177 insertions(+), 41 deletions(-) create mode 100644 apps/explorer/priv/repo/migrations/20240219152810_add_block_consensus_to_token_transfers.exs create mode 100644 apps/explorer/test/explorer/migrator/token_transfer_token_type_test.exs diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex index 8aaad171c4c2..f2324ffad442 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex @@ -409,6 +409,18 @@ defmodule Explorer.Chain.Import.Runner.Blocks do timeout: timeout ) + repo.update_all( + from( + token_transfer in TokenTransfer, + join: s in subquery(acquire_query), + on: token_transfer.block_hash == s.hash, + # we don't want to remove consensus from blocks that will be upserted + where: token_transfer.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)) diff --git a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex index a423d75ff7af..9f555c67abc3 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex @@ -9,7 +9,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do alias Ecto.Adapters.SQL alias Ecto.{Changeset, Multi, Repo} alias EthereumJSONRPC.Utility.RangesHelper - alias Explorer.Chain.{Block, Hash, Import, InternalTransaction, PendingBlockOperation, Transaction} + alias Explorer.Chain.{Block, Hash, Import, InternalTransaction, PendingBlockOperation, TokenTransfer, Transaction} alias Explorer.Chain.Events.Publisher alias Explorer.Chain.Import.Runner alias Explorer.Prometheus.Instrumenter @@ -721,7 +721,16 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do from( transaction in Transaction, where: transaction.block_number in ^invalid_block_numbers and transaction.block_consensus, - where: ^traceable_transactions_dynamic_query(), + where: ^traceable_block_number_dynamic_query(), + # ShareLocks order already enforced by `acquire_blocks` (see docs: sharelocks.md) + update: [set: [block_consensus: false]] + ) + + update_token_transfers_query = + from( + token_transfer in TokenTransfer, + where: token_transfer.block_number in ^invalid_block_numbers and token_transfer.block_consensus, + where: ^traceable_block_number_dynamic_query(), # ShareLocks order already enforced by `acquire_blocks` (see docs: sharelocks.md) update: [set: [block_consensus: false]] ) @@ -729,6 +738,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do try do {_num, result} = repo.update_all(update_block_query, []) {_num, _result} = repo.update_all(update_transaction_query, []) + {_num, _result} = repo.update_all(update_token_transfers_query, []) MissingRangesManipulator.add_ranges_by_block_numbers(invalid_block_numbers) @@ -787,13 +797,13 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do end end - defp traceable_transactions_dynamic_query do + defp traceable_block_number_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) + _from.._to = range, acc -> dynamic([transaction_or_tt], ^acc or transaction_or_tt.block_number in ^range) + num_to_latest, acc -> dynamic([transaction_or_tt], ^acc or transaction_or_tt.block_number >= ^num_to_latest) end) else dynamic([_], true) diff --git a/apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex b/apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex index ad6681fcaaba..2dac07465ebc 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex @@ -91,19 +91,21 @@ defmodule Explorer.Chain.Import.Runner.TokenTransfers do token_contract_address_hash: fragment("EXCLUDED.token_contract_address_hash"), token_ids: fragment("EXCLUDED.token_ids"), token_type: fragment("EXCLUDED.token_type"), + block_consensus: fragment("EXCLUDED.block_consensus"), inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token_transfer.inserted_at), updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token_transfer.updated_at) ] ], where: fragment( - "(EXCLUDED.amount, EXCLUDED.from_address_hash, EXCLUDED.to_address_hash, EXCLUDED.token_contract_address_hash, EXCLUDED.token_ids, EXCLUDED.token_type) IS DISTINCT FROM (?, ?, ?, ?, ?, ?)", + "(EXCLUDED.amount, EXCLUDED.from_address_hash, EXCLUDED.to_address_hash, EXCLUDED.token_contract_address_hash, EXCLUDED.token_ids, EXCLUDED.token_type, EXCLUDED.block_consensus) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?)", token_transfer.amount, token_transfer.from_address_hash, token_transfer.to_address_hash, token_transfer.token_contract_address_hash, token_transfer.token_ids, - token_transfer.token_type + token_transfer.token_type, + token_transfer.block_consensus ) ) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex index 62578969a783..5e8e124e9173 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex @@ -8,7 +8,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do import Ecto.Query, only: [from: 2] alias Ecto.{Multi, Repo} - alias Explorer.Chain.{Block, Hash, Import, Transaction} + alias Explorer.Chain.{Block, Hash, Import, TokenTransfer, Transaction} alias Explorer.Chain.Import.Runner.TokenTransfers alias Explorer.Prometheus.Instrumenter alias Explorer.Utility.MissingRangesManipulator @@ -392,6 +392,16 @@ defmodule Explorer.Chain.Import.Runner.Transactions do timeout: timeout ) + {_, _result} = + repo.update_all( + from(token_transfer in TokenTransfer, + join: s in subquery(query), + on: token_transfer.transaction_hash == s.hash + ), + [set: [block_consensus: false, updated_at: updated_at]], + timeout: timeout + ) + {:ok, result} rescue postgrex_error in Postgrex.Error -> diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index b1ad716af388..294b0e4b9b93 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -59,6 +59,7 @@ defmodule Explorer.Chain.TokenTransfer do * `:log_index` - Index of the corresponding `t:Explorer.Chain.Log.t/0` in the block. * `:amounts` - Tokens transferred amounts in case of batched transfer in ERC-1155 * `:token_ids` - IDs of the tokens (applicable to ERC-1155 tokens) + * `:block_consensus` - Consensus of the block that the transfer took place """ @primary_key false typed_schema "token_transfers" do @@ -69,6 +70,7 @@ defmodule Explorer.Chain.TokenTransfer do field(:token_ids, {:array, :decimal}) field(:index_in_batch, :integer, virtual: true) field(:token_type, :string) + field(:block_consensus, :boolean) belongs_to(:from_address, Address, foreign_key: :from_address_hash, @@ -117,7 +119,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 token_type)a - @optional_attrs ~w(amount amounts token_ids)a + @optional_attrs ~w(amount amounts token_ids block_consensus)a @doc false def changeset(%TokenTransfer{} = struct, params \\ %{}) do @@ -151,15 +153,10 @@ defmodule Explorer.Chain.TokenTransfer 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: ^preloads, - order_by: [desc: tt.block_number, desc: tt.log_index] - ) - - query + only_consensus_transfers_query() + |> where([tt], tt.token_contract_address_hash == ^token_address_hash and not is_nil(tt.block_number)) + |> preload(^preloads) + |> order_by([tt], desc: tt.block_number, desc: tt.log_index) |> page_token_transfer(paging_options) |> limit(^paging_options.page_size) |> Chain.select_repo(options).all() @@ -170,17 +167,12 @@ defmodule Explorer.Chain.TokenTransfer 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, - where: fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^Decimal.new(token_id)), - where: not is_nil(tt.block_number), - preload: ^preloads, - order_by: [desc: tt.block_number, desc: tt.log_index] - ) - - query + only_consensus_transfers_query() + |> where([tt], tt.token_contract_address_hash == ^token_address_hash) + |> where([tt], fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^Decimal.new(token_id))) + |> where([tt], not is_nil(tt.block_number)) + |> preload(^preloads) + |> order_by([tt], desc: tt.block_number, desc: tt.log_index) |> page_token_transfer(paging_options) |> limit(^paging_options.page_size) |> Chain.select_repo(options).all() @@ -313,14 +305,14 @@ defmodule Explorer.Chain.TokenTransfer do end def token_transfers_by_address_hash_and_token_address_hash(address_hash, token_address_hash) do - TokenTransfer + only_consensus_transfers_query() |> where([tt], tt.from_address_hash == ^address_hash or tt.to_address_hash == ^address_hash) |> where([tt], tt.token_contract_address_hash == ^token_address_hash) |> order_by([tt], desc: tt.block_number, desc: tt.log_index) end def token_transfers_by_address_hash(direction, address_hash, token_types) do - TokenTransfer + only_consensus_transfers_query() |> filter_by_direction(direction, address_hash) |> order_by([tt], desc: tt.block_number, desc: tt.log_index) |> join(:inner, [tt], token in assoc(tt, :token), as: :token) @@ -360,11 +352,15 @@ defmodule Explorer.Chain.TokenTransfer do """ @spec only_consensus_transfers_query() :: Ecto.Query.t() def only_consensus_transfers_query do - from(token_transfer in __MODULE__, - inner_join: block in assoc(token_transfer, :block), - as: :block, - where: block.consensus == true - ) + if DenormalizationHelper.tt_denormalization_finished?() do + from(token_transfer in __MODULE__, where: token_transfer.block_consensus == true) + else + from(token_transfer in __MODULE__, + inner_join: block in assoc(token_transfer, :block), + as: :block, + where: block.consensus == true + ) + end end @doc """ diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index 9c1268891252..29ed0425a92f 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -530,6 +530,7 @@ defmodule Explorer.Etherscan do @token_transfer_fields ~w( block_number block_hash + block_consensus token_contract_address_hash transaction_hash from_address_hash diff --git a/apps/explorer/lib/explorer/migrator/token_transfer_token_type.ex b/apps/explorer/lib/explorer/migrator/token_transfer_token_type.ex index c4511ad363fc..07c4edc80714 100644 --- a/apps/explorer/lib/explorer/migrator/token_transfer_token_type.ex +++ b/apps/explorer/lib/explorer/migrator/token_transfer_token_type.ex @@ -41,15 +41,19 @@ defmodule Explorer.Migrator.TokenTransferTokenType do @impl FillingMigration def update_cache do - BackgroundMigrations.set_tb_token_type_finished(true) + BackgroundMigrations.set_tt_denormalization_finished(true) end defp build_update_query(token_transfer_ids) do """ UPDATE token_transfers tt - SET token_type = t.type - FROM tokens t - WHERE tt.token_contract_address_hash = t.contract_address_hash + SET token_type = CASE WHEN t.type = 'ERC-1155' AND token_ids IS NULL THEN 'ERC-20' + ELSE t.type + END, + block_consensus = b.consensus + FROM tokens t, blocks b + WHERE tt.block_hash = b.hash + AND tt.token_contract_address_hash = t.contract_address_hash AND (tt.transaction_hash, tt.block_hash, tt.log_index) IN #{encode_token_transfer_ids(token_transfer_ids)}; """ end diff --git a/apps/explorer/priv/repo/migrations/20240219152810_add_block_consensus_to_token_transfers.exs b/apps/explorer/priv/repo/migrations/20240219152810_add_block_consensus_to_token_transfers.exs new file mode 100644 index 000000000000..253c952ba4ea --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240219152810_add_block_consensus_to_token_transfers.exs @@ -0,0 +1,13 @@ +defmodule Explorer.Repo.Migrations.AddBlockConsensusToTokenTransfers do + use Ecto.Migration + @disable_ddl_transaction true + @disable_migration_lock true + + def change do + alter table(:token_transfers) do + add_if_not_exists(:block_consensus, :boolean, default: true) + end + + create_if_not_exists(index(:token_transfers, :block_consensus, concurrently: true)) + end +end diff --git a/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs b/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs index 7e79c20916f3..0b78b53942d2 100644 --- a/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs @@ -386,6 +386,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do tt = insert(:token_transfer, token_ids: [id], + token_type: "ERC-721", transaction: transaction, token_contract_address: token_address, block_number: block_number, @@ -404,6 +405,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do for _ <- 0..10 do insert(:token_transfer, token_ids: [id], + token_type: "ERC-721", transaction: transaction, token_contract_address: tt.token_contract_address, block_number: consensus_block_1.number, @@ -414,6 +416,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do tt_1 = insert(:token_transfer, token_ids: [id], + token_type: "ERC-721", transaction: transaction, token_contract_address: tt.token_contract_address, block_number: consensus_block_1.number, @@ -431,6 +434,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do insert(:token_transfer, token_ids: [id], + token_type: "ERC-721", transaction: tx, token_contract_address: tt.token_contract_address, block_number: consensus_block_2.number, @@ -490,6 +494,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do tt = insert(:token_transfer, token_ids: [id], + token_type: "ERC-721", transaction: transaction, token_contract_address: token_address, block_number: block_number, diff --git a/apps/explorer/test/explorer/migrator/token_transfer_token_type_test.exs b/apps/explorer/test/explorer/migrator/token_transfer_token_type_test.exs new file mode 100644 index 000000000000..b6cc0ee1c372 --- /dev/null +++ b/apps/explorer/test/explorer/migrator/token_transfer_token_type_test.exs @@ -0,0 +1,82 @@ +defmodule Explorer.Migrator.TokenTransferTokenTypeTest do + use Explorer.DataCase, async: false + + import Ecto.Query + + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Chain.TokenTransfer + alias Explorer.Migrator.{TokenTransferTokenType, MigrationStatus} + alias Explorer.Repo + + describe "Migrate token transfers" do + test "Set token_type and block_consensus for not processed token transfers" do + %{contract_address_hash: regular_token_hash} = regular_token = insert(:token) + + Enum.each(0..4, fn _x -> + token_transfer = + insert(:token_transfer, + from_address: insert(:address), + token_contract_address: regular_token.contract_address, + token_type: nil, + block_consensus: nil + ) + + assert %{token_type: nil, block_consensus: nil} = token_transfer + end) + + %{contract_address_hash: erc1155_token_hash} = erc1155_token = insert(:token, type: "ERC-1155") + + Enum.each(0..4, fn _x -> + token_transfer = + insert(:token_transfer, + from_address: insert(:address), + token_contract_address: erc1155_token.contract_address, + token_type: nil, + block_consensus: nil, + token_ids: nil + ) + + assert %{token_type: nil, block_consensus: nil, token_ids: nil} = token_transfer + end) + + assert MigrationStatus.get_status("tt_denormalization") == nil + + TokenTransferTokenType.start_link([]) + Process.sleep(100) + + TokenTransfer + |> where([tt], tt.token_contract_address_hash == ^regular_token_hash) + |> Repo.all() + |> Repo.preload([:token, :block]) + |> Enum.each(fn tt -> + assert %{ + token_type: token_type, + token: %{type: token_type}, + block_consensus: consensus, + block: %{consensus: consensus} + } = tt + + assert not is_nil(token_type) + assert not is_nil(consensus) + end) + + TokenTransfer + |> where([tt], tt.token_contract_address_hash == ^erc1155_token_hash) + |> Repo.all() + |> Repo.preload([:token, :block]) + |> Enum.each(fn tt -> + assert %{ + token_type: "ERC-20", + token: %{type: "ERC-1155"}, + block_consensus: consensus, + block: %{consensus: consensus} + } = tt + + assert not is_nil(consensus) + end) + + assert MigrationStatus.get_status("tt_denormalization") == "completed" + assert BackgroundMigrations.get_tt_denormalization_finished() == true + end + end +end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 33670478d74e..7474c1c40e07 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -759,7 +759,8 @@ defmodule Explorer.Factory do token_contract_address: token_address, token_type: token.type, transaction: log.transaction, - log_index: log.index + log_index: log.index, + block_consensus: true } end From 20060526a5c9a8863781bb7b0d10f1e11d7de849 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Wed, 21 Feb 2024 10:33:40 +0400 Subject: [PATCH 489/607] Update updated_at along with block consensus in internal transactions runner --- .../chain/import/runner/internal_transactions.ex | 10 +++++----- apps/explorer/test/explorer/chain/import_test.exs | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex index 9f555c67abc3..f8983d82cc15 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex @@ -147,7 +147,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do end) |> Multi.run(:remove_consensus_of_invalid_blocks, fn repo, %{invalid_block_numbers: invalid_block_numbers} -> Instrumenter.block_import_stage_runner( - fn -> remove_consensus_of_invalid_blocks(repo, invalid_block_numbers) end, + fn -> remove_consensus_of_invalid_blocks(repo, invalid_block_numbers, timestamps) end, :block_pending, :internal_transactions, :remove_consensus_of_invalid_blocks @@ -705,7 +705,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do end end - defp remove_consensus_of_invalid_blocks(repo, invalid_block_numbers) do + defp remove_consensus_of_invalid_blocks(repo, invalid_block_numbers, %{updated_at: updated_at}) do if Enum.count(invalid_block_numbers) > 0 do update_block_query = from( @@ -714,7 +714,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do where: ^traceable_blocks_dynamic_query(), select: block.hash, # ShareLocks order already enforced by `acquire_blocks` (see docs: sharelocks.md) - update: [set: [consensus: false]] + update: [set: [consensus: false, updated_at: ^updated_at]] ) update_transaction_query = @@ -723,7 +723,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do where: transaction.block_number in ^invalid_block_numbers and transaction.block_consensus, where: ^traceable_block_number_dynamic_query(), # ShareLocks order already enforced by `acquire_blocks` (see docs: sharelocks.md) - update: [set: [block_consensus: false]] + update: [set: [block_consensus: false, updated_at: ^updated_at]] ) update_token_transfers_query = @@ -732,7 +732,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do where: token_transfer.block_number in ^invalid_block_numbers and token_transfer.block_consensus, where: ^traceable_block_number_dynamic_query(), # ShareLocks order already enforced by `acquire_blocks` (see docs: sharelocks.md) - update: [set: [block_consensus: false]] + update: [set: [block_consensus: false, updated_at: ^updated_at]] ) try do diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs index 0cbfa186f5ba..f3567418a9e1 100644 --- a/apps/explorer/test/explorer/chain/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -353,7 +353,8 @@ defmodule Explorer.Chain.ImportTest do 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> }, inserted_at: %{}, - updated_at: %{} + updated_at: %{}, + token_type: "ERC-20" } ] }} = Import.all(@import_data) From 41f8f1cbd2d9ad512ca6d84fc90603a82e9816ae Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 20 Feb 2024 19:00:20 +0300 Subject: [PATCH 490/607] Refactoring, cover with tests --- .../lib/ethereum_jsonrpc/filecoin.ex | 155 ++++++-------- .../lib/ethereum_jsonrpc/variant.ex | 1 + .../test/ethereum_jsonrpc/filecoin_test.exs | 200 ++++++++++++++++++ .../ethereum_jsonrpc/case/filecoin/mox.ex | 17 ++ .../import/runner/internal_transactions.ex | 4 +- .../runner/internal_transactions_test.exs | 56 +++-- .../test/explorer/chain/import_test.exs | 17 +- apps/explorer/test/explorer/chain_test.exs | 44 +++- 8 files changed, 369 insertions(+), 125 deletions(-) create mode 100644 apps/ethereum_jsonrpc/test/ethereum_jsonrpc/filecoin_test.exs create mode 100644 apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/filecoin/mox.ex diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex index db7d55ecf7fc..4afbd33c6cee 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex @@ -5509,12 +5509,12 @@ defmodule EthereumJSONRPC.Filecoin do import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2, request: 1] alias EthereumJSONRPC.Geth - alias EthereumJSONRPC.Geth.Calls + alias EthereumJSONRPC.Geth.Call @behaviour EthereumJSONRPC.Variant @doc """ - Block reward contract beneficiary fetching is not supported currently for Geth. + Block reward contract beneficiary fetching is not supported currently for FEVM. To signal to the caller that fetching is not supported, `:ignore` is returned. """ @@ -5534,7 +5534,7 @@ defmodule EthereumJSONRPC.Filecoin do def fetch_first_trace(_transactions_params, _json_rpc_named_arguments), do: :ignore @doc """ - Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Geth trace URL. + Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the FEVM `trace_block` URL. """ @impl EthereumJSONRPC.Variant def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) do @@ -5547,15 +5547,7 @@ defmodule EthereumJSONRPC.Filecoin do :ok <- Geth.check_errors_exist(blocks_responses, id_to_params) do transactions_params = to_transactions_params(blocks_responses, id_to_params) - {transactions_id_to_params, transactions_responses} = - Enum.reduce(transactions_params, {%{}, []}, fn {params, calls}, {id_to_params_acc, calls_acc} -> - {Map.put(id_to_params_acc, params[:id], params), [calls | calls_acc]} - end) - - debug_trace_transaction_responses_to_internal_transactions_params( - transactions_responses, - transactions_id_to_params - ) + debug_trace_transaction_responses_to_internal_transactions_params(transactions_params) end end @@ -5567,23 +5559,47 @@ defmodule EthereumJSONRPC.Filecoin do defp extract_transactions_params(block_number, tx_result) do tx_result - |> Enum.reduce({[], 0}, fn %{"transactionHash" => tx_hash, "transactionPosition" => transaction_index} = - calls_result, - {tx_acc, counter} -> - { - [ - {%{block_number: block_number, hash_data: tx_hash, transaction_index: transaction_index, id: counter}, - %{id: counter, result: calls_result}} - | tx_acc - ], - counter + 1 - } - end) + |> Enum.reduce( + {[], 0}, + # counter is the index of the internal transaction in transaction + fn %{"transactionHash" => tx_hash, "transactionPosition" => transaction_index} = calls_result, + {tx_acc, counter} -> + last_tx_response_from_accumulator = List.first(tx_acc) + + next_counter = + with {:empty_accumulator, false} <- {:empty_accumulator, is_nil(last_tx_response_from_accumulator)}, + true <- tx_hash !== last_tx_response_from_accumulator["transactionHash"] do + 0 + else + {:empty_accumulator, true} -> + 0 + + _ -> + counter + 1 + end + + { + [ + Map.merge( + %{ + "blockNumber" => block_number, + "transactionHash" => tx_hash, + "transactionIndex" => transaction_index, + "index" => next_counter + }, + calls_result + ) + | tx_acc + ], + next_counter + } + end + ) |> elem(0) end @doc """ - Fetches the pending transactions from the Geth node. + Fetches the pending transactions from the FEVM node. """ @impl EthereumJSONRPC.Variant def fetch_pending_transactions(_json_rpc_named_arguments), do: :ignore @@ -5600,103 +5616,53 @@ defmodule EthereumJSONRPC.Filecoin do }) end - defp debug_trace_transaction_responses_to_internal_transactions_params( - responses, - id_to_params - ) - when is_list(responses) and is_map(id_to_params) do + defp debug_trace_transaction_responses_to_internal_transactions_params(responses) + when is_list(responses) do responses - |> EthereumJSONRPC.sanitize_responses(id_to_params) - |> Enum.map(&debug_trace_transaction_response_to_internal_transactions_params(&1, id_to_params)) + |> Enum.map(&debug_trace_transaction_response_to_internal_transactions_params(&1)) |> Geth.reduce_internal_transactions_params() end - defp debug_trace_transaction_response_to_internal_transactions_params(%{id: id, result: calls}, id_to_params) - when is_map(id_to_params) do - %{block_number: block_number, hash_data: transaction_hash, transaction_index: transaction_index, id: id} = - Map.fetch!(id_to_params, id) - + defp debug_trace_transaction_response_to_internal_transactions_params(call) do internal_transaction_params = - calls - |> parse_trace_block_calls() - |> (&if(is_list(&1), do: &1, else: [&1])).() - |> Enum.map(fn trace -> - Map.merge(trace, %{ - "blockNumber" => block_number, - "index" => id, - "transactionIndex" => transaction_index, - "transactionHash" => transaction_hash - }) - end) - |> Calls.to_internal_transactions_params() + call + |> parse_trace_block_call() + |> Call.to_internal_transaction_params() {:ok, internal_transaction_params} end - defp debug_trace_transaction_response_to_internal_transactions_params(%{id: id, error: error}, id_to_params) - when is_map(id_to_params) do - %{ - block_number: block_number, - hash_data: "0x" <> transaction_hash_digits = transaction_hash, - transaction_index: transaction_index - } = Map.fetch!(id_to_params, id) - - not_found_message = "transaction " <> transaction_hash_digits <> " not found" - - normalized_error = - case error do - %{code: -32_000, message: ^not_found_message} -> - %{message: :not_found} - - %{code: -32_000, message: "execution timeout"} -> - %{message: :timeout} - - _ -> - error - end - - annotated_error = - Map.put(normalized_error, :data, %{ - block_number: block_number, - transaction_index: transaction_index, - transaction_hash: transaction_hash - }) - - {:error, annotated_error} - end - - defp parse_trace_block_calls(calls) - - defp parse_trace_block_calls(%{"Type" => type} = call) do + defp parse_trace_block_call(%{"Type" => type} = call) do sanitized_call = call |> Map.put("type", type) |> Map.drop(["Type"]) - parse_trace_block_calls(sanitized_call) + parse_trace_block_call(sanitized_call) end - defp parse_trace_block_calls( + defp parse_trace_block_call( %{"type" => upcase_type, "action" => %{"from" => from} = action, "result" => result} = call ) do type = String.downcase(upcase_type) - to = Map.get(action, "to", "0x") - input = Map.get(action, "input", "0x") - %{ "type" => if(type in ~w(call callcode delegatecall staticcall), do: "call", else: type), "callType" => type, "from" => from, - "to" => to, + "to" => Map.get(action, "to", "0x"), "createdContractAddressHash" => Map.get(result, "address", "0x"), "value" => Map.get(action, "value", "0x0"), "gas" => Map.get(action, "gas", "0x0"), "gasUsed" => Map.get(result, "gasUsed", "0x0"), - "input" => input, - "init" => input, - "createdContractCode" => Map.get(result, "output", "0x"), + "input" => Map.get(action, "input", "0x"), + "init" => Map.get(action, "init", "0x"), + "createdContractCode" => Map.get(result, "code", "0x"), "traceAddress" => Map.get(call, "traceAddress", []), + "blockNumber" => Map.get(call, "blockNumber"), + "index" => Map.get(call, "index"), + "transactionIndex" => Map.get(call, "transactionIndex"), + "transactionHash" => Map.get(call, "transactionHash"), # : check, that error is returned in the root of the call "error" => call["error"] } @@ -5704,8 +5670,7 @@ defmodule EthereumJSONRPC.Filecoin do %{"error" => nil} = ok_call -> ok_call |> Map.delete("error") - # to handle staticcall, all other cases handled by EthereumJSONRPC.Geth.Call.elixir_to_internal_transaction_params/1 - |> Map.put("output", Map.get(call, "output", "0x")) + |> Map.put("output", Map.get(result, "output", "0x")) error_call -> error_call diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex index 4d94b94e6909..a88216876cda 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex @@ -115,6 +115,7 @@ defmodule EthereumJSONRPC.Variant do end end + # credo:disable-for-next-line defp get_default_variant do case Application.get_env(:explorer, :chain_type) do "polygon_zkevm" -> "geth" diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/filecoin_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/filecoin_test.exs new file mode 100644 index 000000000000..3e6d46dd8d5c --- /dev/null +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/filecoin_test.exs @@ -0,0 +1,200 @@ +defmodule EthereumJSONRPC.FilecoinTest do + use EthereumJSONRPC.Case, async: false + + import Mox + + alias EthereumJSONRPC.Filecoin + + setup :verify_on_exit! + + describe "fetch_block_internal_transactions/2" do + setup do + initial_env = Application.get_all_env(:ethereum_jsonrpc) + old_env = Application.get_env(:explorer, :chain_type) + + Application.put_env(:explorer, :chain_type, "filecoin") + + on_exit(fn -> + Application.put_all_env([{:ethereum_jsonrpc, initial_env}]) + Application.put_env(:explorer, :chain_type, old_env) + end) + + EthereumJSONRPC.Case.Filecoin.Mox.setup() + end + + setup :verify_on_exit! + + test "is supported", %{json_rpc_named_arguments: json_rpc_named_arguments} do + block_number = 3_663_376 + block_quantity = EthereumJSONRPC.integer_to_quantity(block_number) + + expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id, params: [^block_quantity]}], _ -> + {:ok, + [ + %{ + id: id, + result: [ + %{ + "type" => "call", + "subtraces" => 0, + "traceAddress" => [], + "action" => %{ + "callType" => "call", + "from" => "0xff0000000000000000000000000000000021cc23", + "to" => "0xff000000000000000000000000000000001a34e5", + "gas" => "0x1891a7d", + "value" => "0x0", + "input" => + "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f2850d8182004081820d58c0960ee115a7a4b6f2fd36a83da26c608d49e4160a3737655d0f637b81be81b018539809d35519b0b75ca06304b3b4d40c810e50b954e82c5119a8b4a64c3e762a7ae8a2d465d1cd5bf096c87c56ab0da879568378e5a2368c902eea9898cf1e2a1974ddb479ec6257b69aca7734d3b3e1e70428c77f9e528ffcb3dc3f050f0193c2cc005927a765c39a4931d67fb29aaba6e99f2c7d2566b98fdbf30d6e15a2bbd63b8fa059cfad231ccba1d8964542b50419eaad4bc442d3a1dc1f41941944c11a0037e5f45820d41114bb6abbf966c2528f5705447a53ee37b7055cd4478503ea5eaf1fe165c60000000000000000000000000000" + }, + "result" => %{ + "gasUsed" => "0x14696c1", + "output" => + "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "blockHash" => "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber" => 3_663_376, + "transactionHash" => "0xf37d8b8bf67df3ddaa264e22322d2b092e390ed33f1ab14c8a136b2767979254", + "transactionPosition" => 1 + }, + %{ + "type" => "call", + "subtraces" => 0, + "traceAddress" => [ + 1 + ], + "action" => %{ + "callType" => "call", + "from" => "0xff000000000000000000000000000000002c2c61", + "to" => "0xff00000000000000000000000000000000000004", + "gas" => "0x2c6aae6", + "value" => "0x0", + "input" => + "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + }, + "result" => %{ + "gasUsed" => "0x105fb2", + "output" => + "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" + }, + "blockHash" => "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber" => 3_663_376, + "transactionHash" => "0xbc62a61e0be0e8f6ae09e21ad10f6d79c9a8b8ebc46f8ce076dc0dbe1d6ed4a9", + "transactionPosition" => 21 + } + ] + } + ]} + end) + + assert {:ok, + [ + %{ + block_number: ^block_number, + transaction_index: 21, + transaction_hash: "0xbc62a61e0be0e8f6ae09e21ad10f6d79c9a8b8ebc46f8ce076dc0dbe1d6ed4a9", + index: 0, + trace_address: [1], + type: "call", + call_type: "call", + from_address_hash: "0xff000000000000000000000000000000002c2c61", + to_address_hash: "0xff00000000000000000000000000000000000004", + gas: 46_574_310, + gas_used: 1_073_074, + input: + "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + output: + "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000", + value: 0 + }, + %{ + block_number: ^block_number, + transaction_index: 1, + transaction_hash: "0xf37d8b8bf67df3ddaa264e22322d2b092e390ed33f1ab14c8a136b2767979254", + index: 0, + trace_address: [], + type: "call", + call_type: "call", + from_address_hash: "0xff0000000000000000000000000000000021cc23", + to_address_hash: "0xff000000000000000000000000000000001a34e5", + gas: 25_762_429, + gas_used: 21_403_329, + input: + "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f2850d8182004081820d58c0960ee115a7a4b6f2fd36a83da26c608d49e4160a3737655d0f637b81be81b018539809d35519b0b75ca06304b3b4d40c810e50b954e82c5119a8b4a64c3e762a7ae8a2d465d1cd5bf096c87c56ab0da879568378e5a2368c902eea9898cf1e2a1974ddb479ec6257b69aca7734d3b3e1e70428c77f9e528ffcb3dc3f050f0193c2cc005927a765c39a4931d67fb29aaba6e99f2c7d2566b98fdbf30d6e15a2bbd63b8fa059cfad231ccba1d8964542b50419eaad4bc442d3a1dc1f41941944c11a0037e5f45820d41114bb6abbf966c2528f5705447a53ee37b7055cd4478503ea5eaf1fe165c60000000000000000000000000000", + output: + "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + value: 0 + } + ]} = + Filecoin.fetch_block_internal_transactions( + [ + block_number + ], + json_rpc_named_arguments + ) + end + + test "parses smart-contract creation", %{json_rpc_named_arguments: json_rpc_named_arguments} do + block_number = 3_663_377 + block_quantity = EthereumJSONRPC.integer_to_quantity(block_number) + + expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id, params: [^block_quantity]}], _ -> + {:ok, + [ + %{ + id: id, + result: [ + %{ + "type" => "create", + "subtraces" => 0, + "traceAddress" => [ + 0 + ], + "action" => %{ + "from" => "0xff00000000000000000000000000000000000004", + "gas" => "0x53cf101", + "value" => "0x0", + "init" => "0xfe" + }, + "result" => %{ + "address" => "0xff000000000000000000000000000000002d44e6", + "gasUsed" => "0x1be32fc", + "code" => "0xfe" + }, + "blockHash" => "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", + "blockNumber" => 3_663_377, + "transactionHash" => "0x86ccda9dc76bd37c7201a6da1e10260bf984590efc6b221635c8dd33cc520067", + "transactionPosition" => 18 + } + ] + } + ]} + end) + + assert {:ok, + [ + %{ + block_number: ^block_number, + transaction_index: 18, + transaction_hash: "0x86ccda9dc76bd37c7201a6da1e10260bf984590efc6b221635c8dd33cc520067", + index: 0, + trace_address: [0], + type: "create", + from_address_hash: "0xff00000000000000000000000000000000000004", + created_contract_address_hash: "0xff000000000000000000000000000000002d44e6", + gas: 87_879_937, + gas_used: 29_242_108, + init: "0xfe", + created_contract_code: "0xfe", + value: 0 + } + ]} = + Filecoin.fetch_block_internal_transactions( + [ + block_number + ], + json_rpc_named_arguments + ) + end + end +end diff --git a/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/filecoin/mox.ex b/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/filecoin/mox.ex new file mode 100644 index 000000000000..12510755ad9e --- /dev/null +++ b/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/filecoin/mox.ex @@ -0,0 +1,17 @@ +defmodule EthereumJSONRPC.Case.Filecoin.Mox do + @moduledoc """ + `EthereumJSONRPC.Case` for mocking connecting to Filecoin using `Mox` + """ + + def setup do + %{ + block_interval: 500, + json_rpc_named_arguments: [ + transport: EthereumJSONRPC.Mox, + transport_options: [http_options: [timeout: 60000, recv_timeout: 60000]], + variant: EthereumJSONRPC.Filecoin + ], + subscribe_named_arguments: [transport: EthereumJSONRPC.Mox, transport_options: []] + } + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex index a423d75ff7af..ff9e144cace2 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex @@ -395,7 +395,9 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do block_hash = Map.fetch!(blocks_map, block_number) entries - |> Enum.sort_by(&{&1.transaction_hash, &1.index}) + |> Enum.sort_by( + &{(Map.has_key?(&1, :transaction_index) && &1.transaction_index) || &1.transaction_hash, &1.index} + ) |> Enum.with_index() |> Enum.map(fn {entry, index} -> entry diff --git a/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs b/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs index b27bed0ebfa6..280fe86ea4e3 100644 --- a/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs @@ -163,35 +163,38 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do internal_transaction_changes_2_1 ]) - assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction0.hash, where: i.index == 0) - |> Repo.one() - |> is_nil() + # transaction with index 0 is ignored in Nethermind JSON RPC Variant and not ignored in case of Geth + + # assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction0.hash, where: i.index == 0) + # |> Repo.one() + # |> is_nil() assert 1 == Repo.get_by!(InternalTransaction, transaction_hash: transaction0.hash, index: 1).block_index - assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction1.hash) |> Repo.one() |> is_nil() + # assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction1.hash) |> Repo.one() |> is_nil() - assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction2.hash, where: i.index == 0) - |> Repo.one() - |> is_nil() + # assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction2.hash, where: i.index == 0) + # |> Repo.one() + # |> is_nil() assert 4 == Repo.get_by!(InternalTransaction, transaction_hash: transaction2.hash, index: 1).block_index end - test "simple coin transfer has no internal transaction inserted" do - transaction = insert(:transaction) |> with_block(status: :ok) - insert(:pending_block_operation, block_hash: transaction.block_hash, block_number: transaction.block_number) + # test "simple coin transfer has no internal transaction inserted for Nethermind" do + # transaction = insert(:transaction) |> with_block(status: :ok) + # insert(:pending_block_operation, block_hash: transaction.block_hash, block_number: transaction.block_number) - assert :ok == transaction.status + # assert :ok == transaction.status - index = 0 + # # transaction with index 0 is ignored in Nethermind JSON RPC Variant and not ignored in case of Geth + # index = 0 - internal_transaction_changes = - make_internal_transaction_changes_for_simple_coin_transfers(transaction, index, nil) + # internal_transaction_changes = + # make_internal_transaction_changes_for_simple_coin_transfers(transaction, index, nil) - assert {:ok, _} = run_internal_transactions([internal_transaction_changes]) + # assert {:ok, _} = run_internal_transactions([internal_transaction_changes]) - assert !Repo.exists?(from(i in InternalTransaction, where: i.transaction_hash == ^transaction.hash)) - end + # assert !Repo.exists?(from(i in InternalTransaction, where: i.transaction_hash == ^transaction.hash)) + # end test "pending transactions don't get updated not its internal_transactions inserted" do transaction = insert(:transaction) |> with_block(status: :ok) @@ -283,8 +286,10 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do assert full_block.hash == inserted.block_hash - transaction_changes = make_internal_transaction_changes(inserted, 0, nil) - transaction_changes_2 = make_internal_transaction_changes(inserted, 1, nil) + # transaction with index 0 is ignored in Nethermind JSON RPC Variant and not ignored in case of Geth + _transaction_changes_0 = make_internal_transaction_changes(inserted, 0, nil) + transaction_changes = make_internal_transaction_changes(inserted, 1, nil) + transaction_changes_2 = make_internal_transaction_changes(inserted, 2, nil) empty_changes = make_empty_block_changes(empty_block.number) assert {:ok, _} = run_internal_transactions([empty_changes, transaction_changes, transaction_changes_2]) @@ -292,12 +297,12 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do assert %{consensus: true} = Repo.get(Block, empty_block.hash) assert PendingBlockOperation |> Repo.get(empty_block.hash) |> is_nil() - assert from(i in InternalTransaction, where: i.transaction_hash == ^inserted.hash, where: i.index == 0) + assert from(i in InternalTransaction, where: i.transaction_hash == ^inserted.hash, where: i.index == 1) |> Repo.one() |> is_nil() == - true + false - assert from(i in InternalTransaction, where: i.transaction_hash == ^inserted.hash, where: i.index == 1) + assert from(i in InternalTransaction, where: i.transaction_hash == ^inserted.hash, where: i.index == 2) |> Repo.one() |> is_nil() == false @@ -404,7 +409,12 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do to_address_hash: insert(:address).hash, call_type: :call, gas: 0, - gas_used: nil, + gas_used: + if is_nil(error) do + 100_500 + else + nil + end, input: %Data{bytes: <<>>}, output: if is_nil(error) do diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs index 0d2fbfa02de3..d8a41ce6eff6 100644 --- a/apps/explorer/test/explorer/chain/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -60,7 +60,8 @@ defmodule Explorer.Chain.ImportTest do block_number: 37, transaction_index: 0, transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - index: 0, + # transaction with index 0 is ignored in Nethermind JSON RPC Variant and not ignored in case of Geth + index: 1, trace_address: [], type: "call", call_type: "call", @@ -76,7 +77,7 @@ defmodule Explorer.Chain.ImportTest do block_number: 37, transaction_index: 1, transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - index: 1, + index: 2, trace_address: [0], type: "call", call_type: "call", @@ -269,6 +270,15 @@ defmodule Explorer.Chain.ImportTest do <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> } + }, + %{ + index: 2, + transaction_hash: %Hash{ + byte_count: 32, + 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>> + } } ], logs: [ @@ -502,7 +512,8 @@ defmodule Explorer.Chain.ImportTest do Subscriber.to(:internal_transactions, :realtime) Import.all(@import_data) - assert_receive {:chain_event, :internal_transactions, :realtime, [%{transaction_hash: _, index: _}]} + assert_receive {:chain_event, :internal_transactions, :realtime, + [%{transaction_hash: _, index: _}, %{transaction_hash: _, index: _}]} end test "publishes transactions data to subscribers on insert" do diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index e57ebb13a0bc..6f29e568ecf9 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -1294,7 +1294,8 @@ defmodule Explorer.ChainTest do %{ block_number: 37, transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - index: 0, + # transaction with index 0 is ignored in Nethermind JSON RPC Variant and not ignored in case of Geth + index: 1, trace_address: [], type: "call", call_type: "call", @@ -1382,7 +1383,7 @@ defmodule Explorer.ChainTest do } } - test "with valid data" do + test "with valid data", %{json_rpc_named_arguments: json_rpc_named_arguments} 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) @@ -1392,6 +1393,9 @@ defmodule Explorer.ChainTest do gas_limit = Decimal.new(6_946_336) gas_used = Decimal.new(50450) + gas_int = Decimal.new("4677320") + gas_used_int = Decimal.new("27770") + assert {:ok, %{ addresses: [ @@ -1473,7 +1477,41 @@ defmodule Explorer.ChainTest do updated_at: %{} } ], - internal_transactions: [], + internal_transactions: [ + %InternalTransaction{ + call_type: :call, + created_contract_code: nil, + error: nil, + gas: ^gas_int, + gas_used: ^gas_used_int, + index: 1, + init: nil, + input: %Explorer.Chain.Data{ + bytes: + <<16, 133, 82, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 134, 45, 103, 203, 7, 115, 238, 63, 140, + 231, 234, 137, 179, 40, 255, 234, 134, 26, 179, 239>> + }, + output: %Explorer.Chain.Data{bytes: ""}, + trace_address: [], + type: :call, + block_number: 37, + transaction_index: nil, + block_index: 0, + created_contract_address_hash: nil, + from_address_hash: %Explorer.Chain.Hash{ + byte_count: 20, + bytes: + <<232, 221, 197, 199, 162, 210, 240, 215, 169, 121, 132, 89, 192, 16, 79, 223, 94, 152, 122, + 202>> + }, + to_address_hash: %Explorer.Chain.Hash{ + byte_count: 20, + bytes: + <<139, 243, 141, 71, 100, 146, 144, 100, 242, 212, 211, 165, 101, 32, 167, 106, 179, 223, 65, + 91>> + } + } + ], logs: [ %Log{ address_hash: %Hash{ From e7d2dd0ee4edb963df53fffa68329a2e73e19cd3 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop <105209995+Qwerty5Uiop@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:09:44 +0400 Subject: [PATCH 491/607] Fetch coin balances in async mode in realtime fetcher (#9182) * Fetch coin balances in async mode in realtime fetcher * Coin balances fetcher refactor * Don't filter non-traceable blocks in realtime coin balances fetcher --- CHANGELOG.md | 1 + .../transaction_state_controller_test.exs | 4 +- apps/indexer/lib/indexer/block/fetcher.ex | 11 +- .../lib/indexer/block/realtime/fetcher.ex | 105 +---- .../lib/indexer/fetcher/block_reward.ex | 4 +- .../indexer/fetcher/coin_balance/catchup.ex | 77 ++++ .../helper.ex} | 90 +--- .../indexer/fetcher/coin_balance/realtime.ex | 61 +++ .../fetcher/coin_balance_daily_updater.ex | 68 --- .../indexer/fetcher/coin_balance_on_demand.ex | 8 +- .../lib/indexer/fetcher/contract_code.ex | 4 +- apps/indexer/lib/indexer/supervisor.ex | 9 +- .../bound_interval_supervisor_test.exs | 8 +- .../indexer/block/catchup/fetcher_test.exs | 11 +- .../test/indexer/block/fetcher_test.exs | 8 +- .../indexer/block/realtime/fetcher_test.exs | 391 ++---------------- .../indexer/fetcher/block_reward_test.exs | 12 +- .../catchup_test.exs} | 20 +- .../fetcher/internal_transaction_test.exs | 7 +- ...> coin_balance_catchup_supervisor_case.ex} | 6 +- .../coin_balance_realtime_supervisor_case.ex | 17 + config/runtime.exs | 20 +- docker-compose/envs/common-blockscout.env | 1 + 23 files changed, 295 insertions(+), 648 deletions(-) create mode 100644 apps/indexer/lib/indexer/fetcher/coin_balance/catchup.ex rename apps/indexer/lib/indexer/fetcher/{coin_balance.ex => coin_balance/helper.ex} (73%) create mode 100644 apps/indexer/lib/indexer/fetcher/coin_balance/realtime.ex delete mode 100644 apps/indexer/lib/indexer/fetcher/coin_balance_daily_updater.ex rename apps/indexer/test/indexer/fetcher/{coin_balance_test.exs => coin_balance/catchup_test.exs} (95%) rename apps/indexer/test/support/indexer/fetcher/{coin_balance_supervisor_case.ex => coin_balance_catchup_supervisor_case.ex} (69%) create mode 100644 apps/indexer/test/support/indexer/fetcher/coin_balance_realtime_supervisor_case.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index 577bfdc2a89c..972e103a8a52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - [#9351](https://github.com/blockscout/blockscout/pull/9351) - Noves.fi: add proxy endpoint for describeTxs endpoint - [#9282](https://github.com/blockscout/blockscout/pull/9282) - Add `license_type` to smart contracts - [#9202](https://github.com/blockscout/blockscout/pull/9202) - Add base and priority fee to gas oracle response +- [#9182](https://github.com/blockscout/blockscout/pull/9182) - Fetch coin balances in async mode in realtime fetcher - [#9168](https://github.com/blockscout/blockscout/pull/9168) - Support EIP4844 blobs indexing & API - [#9098](https://github.com/blockscout/blockscout/pull/9098) - Polygon zkEVM Bridge indexer and API v2 extension diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_state_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_state_controller_test.exs index ac5898f37f37..15217a697e11 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_state_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_state_controller_test.exs @@ -7,7 +7,7 @@ defmodule BlockScoutWeb.TransactionStateControllerTest do import BlockScoutWeb.WeiHelper, only: [format_wei_value: 2] import EthereumJSONRPC, only: [integer_to_quantity: 1] alias Explorer.Chain.Wei - alias Indexer.Fetcher.CoinBalance + alias Indexer.Fetcher.CoinBalance.Catchup, as: CoinBalanceCatchup alias Explorer.Counters.{AddressesCounter, AverageBlockTime} alias Indexer.Fetcher.CoinBalanceOnDemand @@ -182,7 +182,7 @@ defmodule BlockScoutWeb.TransactionStateControllerTest do test "fetch coin balances if needed", %{conn: conn} do json_rpc_named_arguments = Application.fetch_env!(:indexer, :json_rpc_named_arguments) - CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) EthereumJSONRPC.Mox |> stub(:json_rpc, fn diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index e85531e04ea9..39b984d9573e 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -16,13 +16,14 @@ defmodule Indexer.Block.Fetcher do alias Explorer.Chain.Cache.Blocks, as: BlocksCache alias Explorer.Chain.Cache.{Accounts, BlockNumber, Transactions, Uncles} alias Indexer.Block.Fetcher.Receipts + alias Indexer.Fetcher.CoinBalance.Catchup, as: CoinBalanceCatchup + alias Indexer.Fetcher.CoinBalance.Realtime, as: CoinBalanceRealtime alias Indexer.Fetcher.PolygonZkevm.BridgeL1Tokens, as: PolygonZkevmBridgeL1Tokens alias Indexer.Fetcher.TokenInstance.Realtime, as: TokenInstanceRealtime alias Indexer.Fetcher.{ Beacon.Blob, BlockReward, - CoinBalance, ContractCode, InternalTransaction, ReplacedTransaction, @@ -371,11 +372,17 @@ defmodule Indexer.Block.Fetcher do block_number = Map.fetch!(address_hash_to_block_number, to_string(address_hash)) %{address_hash: address_hash, block_number: block_number} end) - |> CoinBalance.async_fetch_balances() + |> CoinBalanceCatchup.async_fetch_balances() end def async_import_coin_balances(_, _), do: :ok + def async_import_realtime_coin_balances(%{address_coin_balances: balances}) do + CoinBalanceRealtime.async_fetch_balances(balances) + end + + def async_import_realtime_coin_balances(_), do: :ok + def async_import_created_contract_codes(%{transactions: transactions}) do transactions |> Enum.flat_map(fn diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index cb49b55d909e..31e3a83ea77c 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -9,10 +9,11 @@ defmodule Indexer.Block.Realtime.Fetcher do require Indexer.Tracer require Logger - import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1] + import EthereumJSONRPC, only: [quantity_to_integer: 1] import Indexer.Block.Fetcher, only: [ + async_import_realtime_coin_balances: 1, async_import_blobs: 1, async_import_block_rewards: 1, async_import_created_contract_codes: 1, @@ -27,20 +28,17 @@ defmodule Indexer.Block.Realtime.Fetcher do ] alias Ecto.Changeset - alias EthereumJSONRPC.{FetchedBalances, Subscription} + alias EthereumJSONRPC.Subscription alias Explorer.Chain - alias Explorer.Chain.Cache.Accounts alias Explorer.Chain.Events.Publisher alias Explorer.Counters.AverageBlockTime alias Explorer.Utility.MissingRangesManipulator alias Indexer.{Block, Tracer} alias Indexer.Block.Realtime.TaskSupervisor - alias Indexer.Fetcher.{CoinBalance, CoinBalanceDailyUpdater} alias Indexer.Fetcher.PolygonEdge.{DepositExecute, Withdrawal} alias Indexer.Fetcher.PolygonZkevm.BridgeL2, as: PolygonZkevmBridgeL2 alias Indexer.Fetcher.Shibarium.L2, as: ShibariumBridgeL2 alias Indexer.Prometheus - alias Indexer.Transform.Addresses alias Timex.Duration @behaviour Block.Fetcher @@ -195,43 +193,21 @@ defmodule Indexer.Block.Realtime.Fetcher do @import_options ~w(address_hash_to_fetched_balance_block_number)a @impl Block.Fetcher - def import( - block_fetcher, - %{ - address_coin_balances: %{params: address_coin_balances_params}, - addresses: %{params: addresses_params}, - block_rewards: block_rewards - } = options - ) do - with {:balances, - {:ok, - %{ - addresses_params: balances_addresses_params, - balances_params: balances_params, - balances_daily_params: balances_daily_params - }}} <- - {:balances, - balances(block_fetcher, %{ - addresses_params: addresses_params, - balances_params: address_coin_balances_params - })}, - {block_reward_errors, chain_import_block_rewards} = Map.pop(block_rewards, :errors), - chain_import_options = - options - |> Map.drop(@import_options) - |> put_in([:addresses, :params], balances_addresses_params) - |> put_in([:blocks, :params, Access.all(), :consensus], true) - |> put_in([:block_rewards], chain_import_block_rewards) - |> put_in([Access.key(:address_coin_balances, %{}), :params], balances_params), - CoinBalanceDailyUpdater.add_daily_balances_params(balances_daily_params), - {:import, {:ok, imported} = ok} <- {:import, Chain.import(chain_import_options)} do + def import(_block_fetcher, %{block_rewards: block_rewards} = options) do + {block_reward_errors, chain_import_block_rewards} = Map.pop(block_rewards, :errors) + + chain_import_options = + options + |> Map.drop(@import_options) + |> put_in([:blocks, :params, Access.all(), :consensus], true) + |> put_in([:block_rewards], chain_import_block_rewards) + + with {:import, {:ok, imported} = ok} <- {:import, Chain.import(chain_import_options)} do async_import_remaining_block_data( imported, %{block_rewards: %{errors: block_reward_errors}} ) - Accounts.drop(imported[:addresses]) - ok end end @@ -443,6 +419,7 @@ defmodule Indexer.Block.Realtime.Fetcher do imported, %{block_rewards: %{errors: block_reward_errors}} ) do + async_import_realtime_coin_balances(imported) async_import_block_rewards(block_reward_errors) async_import_created_contract_codes(imported) async_import_internal_transactions(imported) @@ -454,58 +431,4 @@ defmodule Indexer.Block.Realtime.Fetcher do async_import_blobs(imported) async_import_polygon_zkevm_bridge_l1_tokens(imported) end - - defp balances( - %Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments}, - %{addresses_params: addresses_params} = options - ) do - case options - |> fetch_balances_params_list() - |> EthereumJSONRPC.fetch_balances(json_rpc_named_arguments, CoinBalance.batch_size()) do - {:ok, %FetchedBalances{params_list: params_list, errors: []}} -> - merged_addresses_params = - %{address_coin_balances: params_list} - |> Addresses.extract_addresses() - |> Kernel.++(addresses_params) - |> Addresses.merge_addresses() - - value_fetched_at = DateTime.utc_now() - - importable_balances_params = Enum.map(params_list, &Map.put(&1, :value_fetched_at, value_fetched_at)) - - block_timestamp_map = CoinBalance.block_timestamp_map(params_list, json_rpc_named_arguments) - - importable_balances_daily_params = - Enum.map(params_list, fn param -> - day = Map.get(block_timestamp_map, "#{param.block_number}") - (day && Map.put(param, :day, day)) || param - end) - - {:ok, - %{ - addresses_params: merged_addresses_params, - balances_params: importable_balances_params, - balances_daily_params: importable_balances_daily_params - }} - - {:error, _} = error -> - error - - {:ok, %FetchedBalances{errors: errors}} -> - {:error, errors} - end - end - - defp fetch_balances_params_list(%{balances_params: balances_params}) do - balances_params - |> balances_params_to_fetch_balances_params_set() - # stable order for easier moxing - |> Enum.sort_by(fn %{hash_data: hash_data, block_quantity: block_quantity} -> {hash_data, block_quantity} end) - end - - defp balances_params_to_fetch_balances_params_set(balances_params) do - Enum.into(balances_params, MapSet.new(), fn %{address_hash: address_hash, block_number: block_number} -> - %{hash_data: address_hash, block_quantity: integer_to_quantity(block_number)} - end) - end end diff --git a/apps/indexer/lib/indexer/fetcher/block_reward.ex b/apps/indexer/lib/indexer/fetcher/block_reward.ex index e179538b2ccf..5b5ebe47fad0 100644 --- a/apps/indexer/lib/indexer/fetcher/block_reward.ex +++ b/apps/indexer/lib/indexer/fetcher/block_reward.ex @@ -20,7 +20,7 @@ defmodule Indexer.Fetcher.BlockReward do alias Explorer.Chain.Cache.Accounts alias Indexer.{BufferedTask, Tracer} alias Indexer.Fetcher.BlockReward.Supervisor, as: BlockRewardSupervisor - alias Indexer.Fetcher.CoinBalance + alias Indexer.Fetcher.CoinBalance.Catchup, as: CoinBalanceCatchup alias Indexer.Transform.{AddressCoinBalances, Addresses} @behaviour BufferedTask @@ -133,7 +133,7 @@ defmodule Indexer.Fetcher.BlockReward do {:ok, %{address_coin_balances: address_coin_balances, addresses: addresses}} -> Accounts.drop(addresses) - CoinBalance.async_fetch_balances(address_coin_balances) + CoinBalanceCatchup.async_fetch_balances(address_coin_balances) retry_errors(errors) diff --git a/apps/indexer/lib/indexer/fetcher/coin_balance/catchup.ex b/apps/indexer/lib/indexer/fetcher/coin_balance/catchup.ex new file mode 100644 index 000000000000..ee4e93d93931 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/coin_balance/catchup.ex @@ -0,0 +1,77 @@ +defmodule Indexer.Fetcher.CoinBalance.Catchup do + @moduledoc """ + Fetches `t:Explorer.Chain.Address.CoinBalance.t/0` and updates `t:Explorer.Chain.Address.t/0` `fetched_coin_balance` and + `fetched_coin_balance_block_number` to value at max `t:Explorer.Chain.Address.CoinBalance.t/0` `block_number` for the given `t:Explorer.Chain.Address.t/` `hash`. + """ + + use Indexer.Fetcher, restart: :permanent + use Spandex.Decorators + + alias Explorer.Chain + alias Explorer.Chain.{Block, Hash} + alias Indexer.{BufferedTask, Tracer} + alias Indexer.Fetcher.CoinBalance.Catchup.Supervisor, as: CoinBalanceSupervisor + alias Indexer.Fetcher.CoinBalance.Helper + + @behaviour BufferedTask + + @default_max_batch_size 500 + @default_max_concurrency 4 + + @doc """ + Asynchronously fetches balances for each address `hash` at the `block_number`. + """ + @spec async_fetch_balances([ + %{required(:address_hash) => Hash.Address.t(), required(:block_number) => Block.block_number()} + ]) :: :ok + def async_fetch_balances(balance_fields) when is_list(balance_fields) do + if CoinBalanceSupervisor.disabled?() do + :ok + else + entries = Enum.map(balance_fields, &Helper.entry/1) + + BufferedTask.buffer(__MODULE__, entries) + end + end + + def child_spec(params) do + Helper.child_spec(params, defaults(), __MODULE__) + end + + @impl BufferedTask + def init(initial, reducer, _) do + {:ok, final} = + Chain.stream_unfetched_balances( + initial, + fn address_fields, acc -> + address_fields + |> Helper.entry() + |> reducer.(acc) + end, + true + ) + + final + end + + @impl BufferedTask + @decorate trace( + name: "fetch", + resource: "Indexer.Fetcher.CoinBalance.Catchup.run/2", + service: :indexer, + tracer: Tracer + ) + def run(entries, json_rpc_named_arguments) do + Helper.run(entries, json_rpc_named_arguments, true) + end + + defp defaults do + [ + flush_interval: :timer.seconds(3), + max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size, + max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency, + task_supervisor: Indexer.Fetcher.CoinBalance.Catchup.TaskSupervisor, + metadata: [fetcher: :coin_balance_catchup] + ] + end +end diff --git a/apps/indexer/lib/indexer/fetcher/coin_balance.ex b/apps/indexer/lib/indexer/fetcher/coin_balance/helper.ex similarity index 73% rename from apps/indexer/lib/indexer/fetcher/coin_balance.ex rename to apps/indexer/lib/indexer/fetcher/coin_balance/helper.ex index 3d7171724547..e0b012f2adef 100644 --- a/apps/indexer/lib/indexer/fetcher/coin_balance.ex +++ b/apps/indexer/lib/indexer/fetcher/coin_balance/helper.ex @@ -1,90 +1,48 @@ -defmodule Indexer.Fetcher.CoinBalance do +defmodule Indexer.Fetcher.CoinBalance.Helper do @moduledoc """ - Fetches `t:Explorer.Chain.Address.CoinBalance.t/0` and updates `t:Explorer.Chain.Address.t/0` `fetched_coin_balance` and - `fetched_coin_balance_block_number` to value at max `t:Explorer.Chain.Address.CoinBalance.t/0` `block_number` for the given `t:Explorer.Chain.Address.t/` `hash`. + Common functions for `Indexer.Fetcher.CoinBalance.Catchup` and `Indexer.Fetcher.CoinBalance.Realtime` modules """ - use Indexer.Fetcher, restart: :permanent - use Spandex.Decorators + import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1] require Logger - import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1] - alias EthereumJSONRPC.{Blocks, FetchedBalances, Utility.RangesHelper} alias Explorer.Chain - alias Explorer.Chain.{Block, Hash} alias Explorer.Chain.Cache.Accounts - alias Indexer.{BufferedTask, Tracer} - alias Indexer.Fetcher.CoinBalance.Supervisor, as: CoinBalanceSupervisor - - @behaviour BufferedTask - - @default_max_batch_size 500 - @default_max_concurrency 4 - - def batch_size, do: defaults()[:max_batch_size] - - @doc """ - Asynchronously fetches balances for each address `hash` at the `block_number`. - """ - @spec async_fetch_balances([ - %{required(:address_hash) => Hash.Address.t(), required(:block_number) => Block.block_number()} - ]) :: :ok - def async_fetch_balances(balance_fields) when is_list(balance_fields) do - if CoinBalanceSupervisor.disabled?() do - :ok - else - entries = Enum.map(balance_fields, &entry/1) - - BufferedTask.buffer(__MODULE__, entries) - end - end + alias Explorer.Chain.Hash + alias Indexer.BufferedTask @doc false # credo:disable-for-next-line Credo.Check.Design.DuplicatedCode - def child_spec([init_options, gen_server_options]) do + def child_spec([init_options, gen_server_options], defaults, module) do {state, mergeable_init_options} = Keyword.pop(init_options, :json_rpc_named_arguments) unless state do raise ArgumentError, - ":json_rpc_named_arguments must be provided to `#{__MODULE__}.child_spec " <> + ":json_rpc_named_arguments must be provided to `#{module}.child_spec " <> "to allow for json_rpc calls when running." end merged_init_options = - defaults() + defaults |> Keyword.merge(mergeable_init_options) |> Keyword.put(:state, state) - Supervisor.child_spec({BufferedTask, [{__MODULE__, merged_init_options}, gen_server_options]}, id: __MODULE__) - end - - @impl BufferedTask - def init(initial, reducer, _) do - {:ok, final} = - Chain.stream_unfetched_balances( - initial, - fn address_fields, acc -> - address_fields - |> entry() - |> reducer.(acc) - end, - true - ) - - final + Supervisor.child_spec({BufferedTask, [{module, merged_init_options}, gen_server_options]}, id: module) end - @impl BufferedTask - @decorate trace(name: "fetch", resource: "Indexer.Fetcher.CoinBalance.run/2", service: :indexer, tracer: Tracer) - def run(entries, json_rpc_named_arguments) do + def run(entries, json_rpc_named_arguments, filter_non_traceable_blocks? \\ true) do # the same address may be used more than once in the same block, but we only want one `Balance` for a given # `{address, block}`, so take unique params only unique_entries = Enum.uniq(entries) unique_filtered_entries = - Enum.filter(unique_entries, fn {_hash, block_number} -> RangesHelper.traceable_block_number?(block_number) end) + if filter_non_traceable_blocks? do + Enum.filter(unique_entries, fn {_hash, block_number} -> RangesHelper.traceable_block_number?(block_number) end) + else + unique_entries + end unique_entry_count = Enum.count(unique_filtered_entries) Logger.metadata(count: unique_entry_count) @@ -110,15 +68,15 @@ defmodule Indexer.Fetcher.CoinBalance do end end + def entry(%{address_hash: %Hash{bytes: address_hash_bytes}, block_number: block_number}) do + {address_hash_bytes, block_number} + end + defp entry_to_params({address_hash_bytes, block_number}) when is_integer(block_number) do {:ok, address_hash} = Hash.Address.cast(address_hash_bytes) %{block_quantity: integer_to_quantity(block_number), hash_data: to_string(address_hash)} end - defp entry(%{address_hash: %Hash{bytes: address_hash_bytes}, block_number: block_number}) do - {address_hash_bytes, block_number} - end - # We want to record all historical balances for an address, but have the address itself have balance from the # `Balance` with the greatest block_number for that address. def balances_params_to_address_params(balances_params) do @@ -263,14 +221,4 @@ defmodule Indexer.Fetcher.CoinBalance do end end) end - - defp defaults do - [ - flush_interval: :timer.seconds(3), - max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size, - max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency, - task_supervisor: Indexer.Fetcher.CoinBalance.TaskSupervisor, - metadata: [fetcher: :coin_balance] - ] - end end diff --git a/apps/indexer/lib/indexer/fetcher/coin_balance/realtime.ex b/apps/indexer/lib/indexer/fetcher/coin_balance/realtime.ex new file mode 100644 index 000000000000..72b216c92a30 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/coin_balance/realtime.ex @@ -0,0 +1,61 @@ +defmodule Indexer.Fetcher.CoinBalance.Realtime do + @moduledoc """ + Separate version of `Indexer.Fetcher.CoinBalance.Catchup` for fetching balances from realtime block fetcher + """ + + use Indexer.Fetcher, restart: :permanent + use Spandex.Decorators + + alias Explorer.Chain.{Block, Hash} + alias Indexer.{BufferedTask, Tracer} + alias Indexer.Fetcher.CoinBalance.Helper + alias Indexer.Fetcher.CoinBalance.Realtime.Supervisor, as: CoinBalanceSupervisor + + @behaviour BufferedTask + + @default_max_batch_size 500 + @default_max_concurrency 4 + + @doc """ + Asynchronously fetches balances for each address `hash` at the `block_number`. + """ + @spec async_fetch_balances([ + %{required(:address_hash) => Hash.Address.t(), required(:block_number) => Block.block_number()} + ]) :: :ok + def async_fetch_balances(balance_fields) when is_list(balance_fields) do + entries = Enum.map(balance_fields, &Helper.entry/1) + + BufferedTask.buffer(__MODULE__, entries) + end + + def child_spec(params) do + Helper.child_spec(params, defaults(), __MODULE__) + end + + @impl BufferedTask + def init(_, _, _) do + {0, []} + end + + @impl BufferedTask + @decorate trace( + name: "fetch", + resource: "Indexer.Fetcher.CoinBalance.Realtime.run/2", + service: :indexer, + tracer: Tracer + ) + def run(entries, json_rpc_named_arguments) do + Helper.run(entries, json_rpc_named_arguments, false) + end + + defp defaults do + [ + poll: false, + flush_interval: :timer.seconds(3), + max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size, + max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency, + task_supervisor: Indexer.Fetcher.CoinBalance.Realtime.TaskSupervisor, + metadata: [fetcher: :coin_balance_realtime] + ] + end +end diff --git a/apps/indexer/lib/indexer/fetcher/coin_balance_daily_updater.ex b/apps/indexer/lib/indexer/fetcher/coin_balance_daily_updater.ex deleted file mode 100644 index 101178630611..000000000000 --- a/apps/indexer/lib/indexer/fetcher/coin_balance_daily_updater.ex +++ /dev/null @@ -1,68 +0,0 @@ -defmodule Indexer.Fetcher.CoinBalanceDailyUpdater do - @moduledoc """ - Accumulates and periodically updates daily coin balances - """ - - use GenServer - - alias Explorer.Chain - alias Explorer.Counters.AverageBlockTime - alias Timex.Duration - - @default_update_interval :timer.seconds(10) - - def start_link(_) do - GenServer.start_link(__MODULE__, :ok, name: __MODULE__) - end - - @impl true - def init(_) do - schedule_next_update() - - {:ok, %{}} - end - - def add_daily_balances_params(daily_balances_params) do - GenServer.cast(__MODULE__, {:add_daily_balances_params, daily_balances_params}) - end - - @impl true - def handle_cast({:add_daily_balances_params, daily_balances_params}, state) do - {:noreply, Enum.reduce(daily_balances_params, state, &put_new_param/2)} - end - - defp put_new_param(%{day: day, address_hash: address_hash, value: value} = param, acc) do - Map.update(acc, {address_hash, day}, param, fn %{value: old_value} = old_param -> - if is_nil(old_value) or value > old_value, do: param, else: old_param - end) - end - - @impl true - def handle_info(:update, state) when state == %{} do - schedule_next_update() - - {:noreply, %{}} - end - - def handle_info(:update, state) do - Chain.import(%{address_coin_balances_daily: %{params: Map.values(state)}}) - - schedule_next_update() - - {:noreply, %{}} - end - - def handle_info(_, state) do - {:noreply, state} - end - - defp schedule_next_update do - update_interval = - case AverageBlockTime.average_block_time() do - {:error, :disabled} -> @default_update_interval - block_time -> round(Duration.to_milliseconds(block_time)) - end - - Process.send_after(self(), :update, update_interval) - end -end diff --git a/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex b/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex index a4a5f5f97d6f..e56210292770 100644 --- a/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex +++ b/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex @@ -19,7 +19,7 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do alias Explorer.Chain.Address.{CoinBalance, CoinBalanceDaily} alias Explorer.Chain.Cache.{Accounts, BlockNumber} alias Explorer.Counters.AverageBlockTime - alias Indexer.Fetcher.CoinBalance, as: CoinBalanceFetcher + alias Indexer.Fetcher.CoinBalance.Helper, as: CoinBalanceHelper alias Timex.Duration @type block_number :: integer @@ -205,7 +205,7 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do :ok {:ok, %{params_list: params_list}} -> - address_params = CoinBalanceFetcher.balances_params_to_address_params(params_list) + address_params = CoinBalanceHelper.balances_params_to_address_params(params_list) Chain.import(%{ addresses: %{params: address_params, with: :balance_changeset}, @@ -224,14 +224,14 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do end defp do_import(%FetchedBalances{} = fetched_balances) do - case CoinBalanceFetcher.import_fetched_balances(fetched_balances, :on_demand) do + case CoinBalanceHelper.import_fetched_balances(fetched_balances, :on_demand) do {:ok, %{addresses: [address]}} -> {:ok, address} _ -> :error end end defp do_import_daily_balances(%FetchedBalances{} = fetched_balances) do - case CoinBalanceFetcher.import_fetched_daily_balances(fetched_balances, :on_demand) do + case CoinBalanceHelper.import_fetched_daily_balances(fetched_balances, :on_demand) do {:ok, %{addresses: [address]}} -> {:ok, address} _ -> :error end diff --git a/apps/indexer/lib/indexer/fetcher/contract_code.ex b/apps/indexer/lib/indexer/fetcher/contract_code.ex index 716b8f25e204..891b52676fe9 100644 --- a/apps/indexer/lib/indexer/fetcher/contract_code.ex +++ b/apps/indexer/lib/indexer/fetcher/contract_code.ex @@ -14,7 +14,7 @@ defmodule Indexer.Fetcher.ContractCode do alias Explorer.Chain.{Block, Hash} alias Explorer.Chain.Cache.Accounts alias Indexer.{BufferedTask, Tracer} - alias Indexer.Fetcher.CoinBalance, as: CoinBalanceFetcher + alias Indexer.Fetcher.CoinBalance.Helper, as: CoinBalanceHelper alias Indexer.Transform.Addresses @behaviour BufferedTask @@ -122,7 +122,7 @@ defmodule Indexer.Fetcher.ContractCode do |> EthereumJSONRPC.fetch_balances(json_rpc_named_arguments) |> case do {:ok, fetched_balances} -> - balance_addresses_params = CoinBalanceFetcher.balances_params_to_address_params(fetched_balances.params_list) + balance_addresses_params = CoinBalanceHelper.balances_params_to_address_params(fetched_balances.params_list) merged_addresses_params = Addresses.merge_addresses(addresses_params ++ balance_addresses_params) diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index a560d7642533..defc0e3bae22 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -18,6 +18,8 @@ defmodule Indexer.Supervisor do alias Indexer.Block.Catchup, as: BlockCatchup alias Indexer.Block.Realtime, as: BlockRealtime + alias Indexer.Fetcher.CoinBalance.Catchup, as: CoinBalanceCatchup + alias Indexer.Fetcher.CoinBalance.Realtime, as: CoinBalanceRealtime alias Indexer.Fetcher.TokenInstance.LegacySanitize, as: TokenInstanceLegacySanitize alias Indexer.Fetcher.TokenInstance.Realtime, as: TokenInstanceRealtime alias Indexer.Fetcher.TokenInstance.Retry, as: TokenInstanceRetry @@ -27,8 +29,6 @@ defmodule Indexer.Supervisor do alias Indexer.Fetcher.{ BlockReward, - CoinBalance, - CoinBalanceDailyUpdater, ContractCode, EmptyBlocksSanitizer, InternalTransaction, @@ -118,7 +118,9 @@ defmodule Indexer.Supervisor do [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, {InternalTransaction.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, - {CoinBalance.Supervisor, + {CoinBalanceCatchup.Supervisor, + [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, + {CoinBalanceRealtime.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, {Token.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, {TokenInstanceRealtime.Supervisor, [[memory_monitor: memory_monitor]]}, @@ -162,7 +164,6 @@ defmodule Indexer.Supervisor do {EmptyBlocksSanitizer.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments]]}, {PendingTransactionsSanitizer, [[json_rpc_named_arguments: json_rpc_named_arguments]]}, {TokenTotalSupplyUpdater, [[]]}, - {CoinBalanceDailyUpdater, [[]]}, # Temporary workers {UncatalogedTokenTransfers.Supervisor, [[]]}, diff --git a/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs b/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs index 774ebfe3a5ab..f55ec9134f95 100644 --- a/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs +++ b/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs @@ -11,9 +11,9 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do alias Indexer.BoundInterval alias Indexer.Block.Catchup alias Indexer.Block.Catchup.MissingRangesCollector + alias Indexer.Fetcher.CoinBalance.Catchup, as: CoinBalanceCatchup alias Indexer.Fetcher.{ - CoinBalance, ContractCode, InternalTransaction, ReplacedTransaction, @@ -228,7 +228,7 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do previous_batch_block_number = first_catchup_block_number - default_blocks_batch_size Application.put_env(:indexer, :block_ranges, "#{previous_batch_block_number}..#{first_catchup_block_number}") - CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) ContractCode.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) @@ -431,7 +431,7 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do MissingRangesCollector.start_link([]) start_supervised!({Task.Supervisor, name: Indexer.Block.Catchup.TaskSupervisor}) - CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) ContractCode.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) @@ -523,7 +523,7 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do Application.put_env(:indexer, :block_ranges, "0..0") MissingRangesCollector.start_link([]) start_supervised({Task.Supervisor, name: Indexer.Block.Catchup.TaskSupervisor}) - CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) ContractCode.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) diff --git a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs index 93f687907ca0..dc3d820e4b79 100644 --- a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs @@ -13,7 +13,8 @@ defmodule Indexer.Block.Catchup.FetcherTest do alias Indexer.Block alias Indexer.Block.Catchup.Fetcher alias Indexer.Block.Catchup.MissingRangesCollector - alias Indexer.Fetcher.{BlockReward, CoinBalance, InternalTransaction, Token, TokenBalance, UncleBlock} + alias Indexer.Fetcher.CoinBalance.Catchup, as: CoinBalanceCatchup + alias Indexer.Fetcher.{BlockReward, InternalTransaction, Token, TokenBalance, UncleBlock} @moduletag capture_log: true @@ -46,7 +47,7 @@ defmodule Indexer.Block.Catchup.FetcherTest do end test "fetches uncles asynchronously", %{json_rpc_named_arguments: json_rpc_named_arguments} do - CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) TokenBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) @@ -148,7 +149,7 @@ defmodule Indexer.Block.Catchup.FetcherTest do Application.put_env(:indexer, Indexer.Block.Catchup.Fetcher, batch_size: 1, concurrency: 10) Application.put_env(:indexer, :block_ranges, "0..1") start_supervised!({Task.Supervisor, name: Indexer.Block.Catchup.TaskSupervisor}) - CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) TokenBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) @@ -308,7 +309,7 @@ defmodule Indexer.Block.Catchup.FetcherTest do Application.put_env(:indexer, Indexer.Block.Catchup.Fetcher, batch_size: 1, concurrency: 10) Application.put_env(:indexer, :block_ranges, "0..1") start_supervised!({Task.Supervisor, name: Indexer.Block.Catchup.TaskSupervisor}) - CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) TokenBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) @@ -465,7 +466,7 @@ defmodule Indexer.Block.Catchup.FetcherTest do Application.put_env(:indexer, Indexer.Block.Catchup.Fetcher, batch_size: 1, concurrency: 10) Application.put_env(:indexer, :block_ranges, "0..1") start_supervised!({Task.Supervisor, name: Indexer.Block.Catchup.TaskSupervisor}) - CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) TokenBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) diff --git a/apps/indexer/test/indexer/block/fetcher_test.exs b/apps/indexer/test/indexer/block/fetcher_test.exs index 0498cfd37ecb..ca9ae2a099de 100644 --- a/apps/indexer/test/indexer/block/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/fetcher_test.exs @@ -10,9 +10,9 @@ defmodule Indexer.Block.FetcherTest do alias Explorer.Chain.{Address, Log, Transaction, Wei} alias Indexer.Block.Fetcher alias Indexer.BufferedTask + alias Indexer.Fetcher.CoinBalance.Catchup, as: CoinBalanceCatchup alias Indexer.Fetcher.{ - CoinBalance, ContractCode, InternalTransaction, ReplacedTransaction, @@ -49,7 +49,7 @@ defmodule Indexer.Block.FetcherTest do describe "import_range/2" do setup %{json_rpc_named_arguments: json_rpc_named_arguments} do - CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) ContractCode.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) @@ -581,7 +581,7 @@ defmodule Indexer.Block.FetcherTest do }} = Fetcher.fetch_and_import_range(block_fetcher, block_number..block_number) wait_for_tasks(InternalTransaction) - wait_for_tasks(CoinBalance) + wait_for_tasks(CoinBalanceCatchup) assert Repo.aggregate(Block, :count, :hash) == 1 assert Repo.aggregate(Address, :count, :hash) == 5 @@ -675,7 +675,7 @@ defmodule Indexer.Block.FetcherTest do }} = Fetcher.fetch_and_import_range(block_fetcher, block_number..block_number) wait_for_tasks(InternalTransaction) - wait_for_tasks(CoinBalance) + wait_for_tasks(CoinBalanceCatchup) assert Repo.aggregate(Chain.Block, :count, :hash) == 1 assert Repo.aggregate(Address, :count, :hash) == 2 diff --git a/apps/indexer/test/indexer/block/realtime/fetcher_test.exs b/apps/indexer/test/indexer/block/realtime/fetcher_test.exs index 9d0459e915bc..d7e4f2354409 100644 --- a/apps/indexer/test/indexer/block/realtime/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/realtime/fetcher_test.exs @@ -8,6 +8,7 @@ defmodule Indexer.Block.Realtime.FetcherTest do alias Explorer.Chain.{Address, Transaction, Wei} alias Indexer.Block.Catchup.Sequence alias Indexer.Block.Realtime + alias Indexer.Fetcher.CoinBalance.Realtime, as: CoinBalanceRealtime alias Indexer.Fetcher.{ContractCode, InternalTransaction, ReplacedTransaction, Token, TokenBalance, UncleBlock} @moduletag capture_log: true @@ -36,6 +37,7 @@ defmodule Indexer.Block.Realtime.FetcherTest do } TokenBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalanceRealtime.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) %{block_fetcher: block_fetcher, json_rpc_named_arguments: core_json_rpc_named_arguments} end @@ -205,7 +207,7 @@ defmodule Indexer.Block.Realtime.FetcherTest do } ]} end) - |> expect(:json_rpc, 4, fn + |> expect(:json_rpc, 1, fn [ %{id: 0, jsonrpc: "2.0", method: "trace_block", params: ["0x3C365F"]}, %{id: 1, jsonrpc: "2.0", method: "trace_block", params: ["0x3C3660"]} @@ -470,43 +472,6 @@ defmodule Indexer.Block.Realtime.FetcherTest do ] } ]} - - [ - [ - %{ - id: 0, - jsonrpc: "2.0", - method: "eth_getBalance", - params: ["0x40b18103537c0f15d5e137dd8ddd019b84949d16", "0x3C365F"] - }, - %{ - id: 1, - jsonrpc: "2.0", - method: "eth_getBalance", - params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"] - }, - %{ - id: 2, - jsonrpc: "2.0", - method: "eth_getBalance", - params: ["0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3", "0x3C3660"] - }, - %{ - id: 3, - jsonrpc: "2.0", - method: "eth_getBalance", - params: ["0x698bf6943bab687b2756394624aa183f434f65da", "0x3C365F"] - } - ] - ], - _ -> - {:ok, - [ - %{id: 0, jsonrpc: "2.0", result: "0x148adc763b603291685"}, - %{id: 1, jsonrpc: "2.0", result: "0x53474fa377a46000"}, - %{id: 2, jsonrpc: "2.0", result: "0x53507afe51f28000"}, - %{id: 3, jsonrpc: "2.0", result: "0x3e1a95d7517dc197108"} - ]} end) end @@ -514,10 +479,10 @@ defmodule Indexer.Block.Realtime.FetcherTest do %{ inserted: %{ addresses: [ - %Address{hash: first_address_hash, fetched_coin_balance_block_number: 3_946_079}, - %Address{hash: second_address_hash, fetched_coin_balance_block_number: 3_946_079}, - %Address{hash: third_address_hash, fetched_coin_balance_block_number: 3_946_080}, - %Address{hash: fourth_address_hash, fetched_coin_balance_block_number: 3_946_079} + %Address{hash: first_address_hash}, + %Address{hash: second_address_hash}, + %Address{hash: third_address_hash}, + %Address{hash: fourth_address_hash} ], address_coin_balances: [ %{ @@ -710,298 +675,6 @@ defmodule Indexer.Block.Realtime.FetcherTest do } ]} end) - |> expect(:json_rpc, 3, fn - [ - %{ - id: 0, - jsonrpc: "2.0", - method: "eth_getBlockByNumber", - params: ["0x3C365F", true] - } - ], - _ -> - {:ok, - [ - %{ - id: 0, - jsonrpc: "2.0", - result: %{ - "author" => "0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", - "difficulty" => "0xfffffffffffffffffffffffffffffffe", - "extraData" => "0xd583010b088650617269747986312e32372e32826c69", - "gasLimit" => "0x7a1200", - "gasUsed" => "0x2886e", - "hash" => "0xa4ec735cabe1510b5ae081b30f17222580b4588dbec52830529753a688b046cc", - "logsBloom" => - "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "miner" => "0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", - "number" => "0x3c365f", - "parentHash" => "0x57f6d66e07488defccd5216c4d2968dd6afd3bd32415e284de3b02af6535e8dc", - "receiptsRoot" => "0x111be72e682cea9c93e02f1ef503fb64aa821b2ef510fd9177c49b37d0af98b5", - "sealFields" => [ - "0x841246c63f", - "0xb841ba3d11db672fd7893d1b7906275fa7c4c7f4fbcc8fa29eab0331480332361516545ef10a36d800ad2be2b449dde8d5703125156a9cf8a035f5a8623463e051b700" - ], - "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "signature" => - "ba3d11db672fd7893d1b7906275fa7c4c7f4fbcc8fa29eab0331480332361516545ef10a36d800ad2be2b449dde8d5703125156a9cf8a035f5a8623463e051b700", - "size" => "0x33e", - "stateRoot" => "0x7f73f5fb9f891213b671356126c31e9795d038844392c7aa8800ed4f52307209", - "step" => "306628159", - "timestamp" => "0x5b61df3b", - "totalDifficulty" => "0x3c365effffffffffffffffffffffffed7f0362", - "transactions" => [ - %{ - "blockHash" => "0xa4ec735cabe1510b5ae081b30f17222580b4588dbec52830529753a688b046cc", - "blockNumber" => "0x3c365f", - "chainId" => "0x63", - "condition" => nil, - "creates" => nil, - "from" => "0x40b18103537c0f15d5e137dd8ddd019b84949d16", - "gas" => "0x3d9c5", - "gasPrice" => "0x3b9aca00", - "hash" => "0xd3937e70fab3fb2bfe8feefac36815408bf07de3b9e09fe81114b9a6b17f55c8", - "input" => - "0x8841ac11000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005", - "nonce" => "0x65b", - "publicKey" => - "0x89c2123ed4b5d141cf1f4b6f5f3d754418f03aea2e870a1c50888d94bf5531f74237e2fea72d0bc198ef213272b62c6869615720757255e6cba087f9db6e759f", - "r" => "0x55a1a93541d7f782f97f6699437bb60fa4606d63760b30c1ee317e648f93995", - "raw" => - "0xf8f582065b843b9aca008303d9c594698bf6943bab687b2756394624aa183f434f65da8901158e4f216242a000b8848841ac11000000000000000000000000000000000000000000000000000000000000006c00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000581eaa0055a1a93541d7f782f97f6699437bb60fa4606d63760b30c1ee317e648f93995a06affd4da5eca84fbca2b016c980f861e0af1f8d6535e2fe29d8f96dc0ce358f7", - "s" => "0x6affd4da5eca84fbca2b016c980f861e0af1f8d6535e2fe29d8f96dc0ce358f7", - "standardV" => "0x1", - "to" => "0x698bf6943bab687b2756394624aa183f434f65da", - "transactionIndex" => "0x0", - "v" => "0xea", - "value" => "0x1158e4f216242a000" - } - ], - "transactionsRoot" => "0xd7c39a93eafe0bdcbd1324c13dcd674bed8c9fa8adbf8f95bf6a59788985da6f", - "uncles" => ["0xa4ec735cabe1510b5ae081b30f17222580b4588dbec52830529753a688b046cd"] - } - } - ]} - - [ - %{ - id: 0, - jsonrpc: "2.0", - method: "eth_getBlockByNumber", - params: ["0x3C3660", true] - } - ], - _ -> - {:ok, - [ - %{ - id: 0, - jsonrpc: "2.0", - result: %{ - "author" => "0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3", - "difficulty" => "0xfffffffffffffffffffffffffffffffe", - "extraData" => "0xd583010a068650617269747986312e32362e32826c69", - "gasLimit" => "0x7a1200", - "gasUsed" => "0x0", - "hash" => "0xfb483e511d316fa4072694da3f7abc94b06286406af45061e5e681395bdc6815", - "logsBloom" => - "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "miner" => "0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3", - "number" => "0x3c3660", - "parentHash" => "0xa4ec735cabe1510b5ae081b30f17222580b4588dbec52830529753a688b046cc", - "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "sealFields" => [ - "0x841246c640", - "0xb84114db3fd7526b7ea3635f5c85c30dd8a645453aa2f8afe5fd33fe0ec663c9c7b653b0fb5d8dc7d0b809674fa9dca9887d1636a586bf62191da22255eb068bf20800" - ], - "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "signature" => - "14db3fd7526b7ea3635f5c85c30dd8a645453aa2f8afe5fd33fe0ec663c9c7b653b0fb5d8dc7d0b809674fa9dca9887d1636a586bf62191da22255eb068bf20800", - "size" => "0x243", - "stateRoot" => "0x3174c461989e9f99e08fa9b4ffb8bce8d9a281c8fc9f80694bb9d3acd4f15559", - "step" => "306628160", - "timestamp" => "0x5b61df40", - "totalDifficulty" => "0x3c365fffffffffffffffffffffffffed7f0360", - "transactions" => [], - "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "uncles" => [] - } - } - ]} - - [ - %{ - id: 0, - jsonrpc: "2.0", - method: "trace_replayBlockTransactions", - params: [ - "0x3C3660", - ["trace"] - ] - }, - %{ - id: 1, - jsonrpc: "2.0", - method: "trace_replayBlockTransactions", - params: [ - "0x3C365F", - ["trace"] - ] - } - ], - _ -> - {:ok, - [ - %{id: 0, jsonrpc: "2.0", result: []}, - %{ - id: 1, - jsonrpc: "2.0", - result: [ - %{ - "output" => "0x", - "stateDiff" => nil, - "trace" => [ - %{ - "action" => %{ - "callType" => "call", - "from" => "0x40b18103537c0f15d5e137dd8ddd019b84949d16", - "gas" => "0x383ad", - "input" => - "0x8841ac11000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005", - "to" => "0x698bf6943bab687b2756394624aa183f434f65da", - "value" => "0x1158e4f216242a000" - }, - "result" => %{"gasUsed" => "0x23256", "output" => "0x"}, - "subtraces" => 5, - "traceAddress" => [], - "type" => "call" - }, - %{ - "action" => %{ - "callType" => "call", - "from" => "0x698bf6943bab687b2756394624aa183f434f65da", - "gas" => "0x36771", - "input" => "0x6352211e000000000000000000000000000000000000000000000000000000000000006c", - "to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c", - "value" => "0x0" - }, - "result" => %{ - "gasUsed" => "0x495", - "output" => "0x00000000000000000000000040b18103537c0f15d5e137dd8ddd019b84949d16" - }, - "subtraces" => 0, - "traceAddress" => [0], - "type" => "call" - }, - %{ - "action" => %{ - "callType" => "call", - "from" => "0x698bf6943bab687b2756394624aa183f434f65da", - "gas" => "0x35acb", - "input" => "0x33f30a43000000000000000000000000000000000000000000000000000000000000006c", - "to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c", - "value" => "0x0" - }, - "result" => %{ - "gasUsed" => "0x52d2", - "output" => - "0x00000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000058000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000004f000000000000000000000000000000000000000000000000000000000000004d000000000000000000000000000000000000000000000000000000000000004b000000000000000000000000000000000000000000000000000000000000004f00000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000005b61df09000000000000000000000000000000000000000000000000000000005b61df5e000000000000000000000000000000000000000000000000000000005b61df8b000000000000000000000000000000000000000000000000000000005b61df2c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c00000000000000000000000000000000000000000000000000000000000000fd000000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000007a000000000000000000000000000000000000000000000000000000000000004e0000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000189000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000054c65696c61000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002566303430313037303331343330303332333036303933333235303131323036303730373131000000000000000000000000000000000000000000000000000000" - }, - "subtraces" => 0, - "traceAddress" => [1], - "type" => "call" - }, - %{ - "action" => %{ - "callType" => "call", - "from" => "0x698bf6943bab687b2756394624aa183f434f65da", - "gas" => "0x2fc79", - "input" => "0x1b8ef0bb000000000000000000000000000000000000000000000000000000000000006c", - "to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c", - "value" => "0x0" - }, - "result" => %{ - "gasUsed" => "0x10f2", - "output" => "0x0000000000000000000000000000000000000000000000000000000000000013" - }, - "subtraces" => 0, - "traceAddress" => [2], - "type" => "call" - }, - %{ - "action" => %{ - "callType" => "call", - "from" => "0x698bf6943bab687b2756394624aa183f434f65da", - "gas" => "0x2e21f", - "input" => - "0xcf5f87d0000000000000000000000000000000000000000000000000000000000000006c0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000a", - "to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c", - "value" => "0x0" - }, - "result" => %{"gasUsed" => "0x1ca1", "output" => "0x"}, - "subtraces" => 0, - "traceAddress" => [3], - "type" => "call" - }, - %{ - "action" => %{ - "callType" => "call", - "from" => "0x698bf6943bab687b2756394624aa183f434f65da", - "gas" => "0x8fc", - "input" => "0x", - "to" => "0x40b18103537c0f15d5e137dd8ddd019b84949d16", - "value" => "0x9184e72a000" - }, - "result" => %{"gasUsed" => "0x0", "output" => "0x"}, - "subtraces" => 0, - "traceAddress" => [4], - "type" => "call" - } - ], - "transactionHash" => "0xd3937e70fab3fb2bfe8feefac36815408bf07de3b9e09fe81114b9a6b17f55c8", - "vmTrace" => nil - } - ] - } - ]} - - [ - [ - %{ - id: 0, - jsonrpc: "2.0", - method: "eth_getBalance", - params: ["0x40b18103537c0f15d5e137dd8ddd019b84949d16", "0x3C365F"] - }, - %{ - id: 1, - jsonrpc: "2.0", - method: "eth_getBalance", - params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"] - }, - %{ - id: 2, - jsonrpc: "2.0", - method: "eth_getBalance", - params: ["0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3", "0x3C3660"] - }, - %{ - id: 3, - jsonrpc: "2.0", - method: "eth_getBalance", - params: ["0x698bf6943bab687b2756394624aa183f434f65da", "0x3C365F"] - } - ] - ], - _ -> - {:ok, - [ - %{id: 0, jsonrpc: "2.0", result: "0x148adc763b603291685"}, - %{id: 1, jsonrpc: "2.0", result: "0x53474fa377a46000"}, - %{id: 2, jsonrpc: "2.0", result: "0x53507afe51f28000"}, - %{id: 3, jsonrpc: "2.0", result: "0x3e1a95d7517dc197108"} - ]} - end) end first_expected_reward = %Wei{value: Decimal.new(165_998_000_000_000)} @@ -1011,10 +684,10 @@ defmodule Indexer.Block.Realtime.FetcherTest do %{ inserted: %{ addresses: [ - %Address{hash: first_address_hash, fetched_coin_balance_block_number: 3_946_079}, - %Address{hash: second_address_hash, fetched_coin_balance_block_number: 3_946_079}, - %Address{hash: third_address_hash, fetched_coin_balance_block_number: 3_946_080}, - %Address{hash: fourth_address_hash, fetched_coin_balance_block_number: 3_946_079} + %Address{hash: first_address_hash}, + %Address{hash: second_address_hash}, + %Address{hash: third_address_hash}, + %Address{hash: fourth_address_hash} ], address_coin_balances: [ %{ @@ -1187,27 +860,23 @@ defmodule Indexer.Block.Realtime.FetcherTest do ]} [ - [ - %{ - id: 0, - jsonrpc: "2.0", - method: "eth_getBalance", - params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"] - } - ] + %{ + id: 0, + jsonrpc: "2.0", + method: "eth_getBalance", + params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"] + } ], _ -> {:ok, [%{id: 0, jsonrpc: "2.0", result: "0x53474fa377a46000"}]} [ - [ - %{ - id: 0, - jsonrpc: "2.0", - method: "eth_getBalance", - params: ["0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3", "0x3C3660"] - } - ] + %{ + id: 0, + jsonrpc: "2.0", + method: "eth_getBalance", + params: ["0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3", "0x3C3660"] + } ], _ -> {:ok, [%{id: 0, jsonrpc: "2.0", result: "0x53507afe51f28000"}]} @@ -1232,14 +901,12 @@ defmodule Indexer.Block.Realtime.FetcherTest do ]} [ - [ - %{ - id: 0, - jsonrpc: "2.0", - method: "eth_getBalance", - params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"] - } - ] + %{ + id: 0, + jsonrpc: "2.0", + method: "eth_getBalance", + params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"] + } ], _ -> {:ok, [%{id: 0, jsonrpc: "2.0", result: "0x53474fa377a46000"}]} diff --git a/apps/indexer/test/indexer/fetcher/block_reward_test.exs b/apps/indexer/test/indexer/fetcher/block_reward_test.exs index 2cf2c01819d3..82bb612feed2 100644 --- a/apps/indexer/test/indexer/fetcher/block_reward_test.exs +++ b/apps/indexer/test/indexer/fetcher/block_reward_test.exs @@ -132,7 +132,7 @@ defmodule Indexer.Fetcher.BlockRewardTest do end end) - Process.register(pid, Indexer.Fetcher.CoinBalance) + Process.register(pid, Indexer.Fetcher.CoinBalance.Catchup) assert :ok = BlockReward.async_fetch([block_number]) @@ -205,7 +205,7 @@ defmodule Indexer.Fetcher.BlockRewardTest do end end) - Process.register(pid, Indexer.Fetcher.CoinBalance) + Process.register(pid, Indexer.Fetcher.CoinBalance.Catchup) assert :ok = BlockReward.async_fetch([block_number]) @@ -340,7 +340,7 @@ defmodule Indexer.Fetcher.BlockRewardTest do end end) - Process.register(pid, Indexer.Fetcher.CoinBalance) + Process.register(pid, Indexer.Fetcher.CoinBalance.Catchup) assert :ok = BlockReward.run([block_number], json_rpc_named_arguments) @@ -430,7 +430,7 @@ defmodule Indexer.Fetcher.BlockRewardTest do end end) - Process.register(pid, Indexer.Fetcher.CoinBalance) + Process.register(pid, Indexer.Fetcher.CoinBalance.Catchup) assert :ok = BlockReward.run([block_number], json_rpc_named_arguments) @@ -514,7 +514,7 @@ defmodule Indexer.Fetcher.BlockRewardTest do end end) - Process.register(pid, Indexer.Fetcher.CoinBalance) + Process.register(pid, Indexer.Fetcher.CoinBalance.Catchup) assert :ok = BlockReward.run([block_number], json_rpc_named_arguments) @@ -651,7 +651,7 @@ defmodule Indexer.Fetcher.BlockRewardTest do end end) - Process.register(pid, Indexer.Fetcher.CoinBalance) + Process.register(pid, Indexer.Fetcher.CoinBalance.Catchup) assert {:retry, [^error_block_number]} = BlockReward.run([block_number, error_block_number], json_rpc_named_arguments) diff --git a/apps/indexer/test/indexer/fetcher/coin_balance_test.exs b/apps/indexer/test/indexer/fetcher/coin_balance/catchup_test.exs similarity index 95% rename from apps/indexer/test/indexer/fetcher/coin_balance_test.exs rename to apps/indexer/test/indexer/fetcher/coin_balance/catchup_test.exs index 708a1e058663..358598a17da4 100644 --- a/apps/indexer/test/indexer/fetcher/coin_balance_test.exs +++ b/apps/indexer/test/indexer/fetcher/coin_balance/catchup_test.exs @@ -1,4 +1,4 @@ -defmodule Indexer.Fetcher.CoinBalanceTest do +defmodule Indexer.Fetcher.CoinBalance.CatchupTest do # MUST be `async: false` so that {:shared, pid} is set for connection to allow CoinBalanceFetcher's self-send to have # connection allowed immediately. use EthereumJSONRPC.Case, async: false @@ -8,7 +8,7 @@ defmodule Indexer.Fetcher.CoinBalanceTest do import Mox alias Explorer.Chain.{Address, Hash, Wei} - alias Indexer.Fetcher.CoinBalance + alias Indexer.Fetcher.CoinBalance.Catchup, as: CoinBalanceCatchup @moduletag :capture_log @@ -83,7 +83,7 @@ defmodule Indexer.Fetcher.CoinBalanceTest do assert miner.fetched_coin_balance == nil assert miner.fetched_coin_balance_block_number == nil - CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) fetched_address = wait(fn -> @@ -151,7 +151,7 @@ defmodule Indexer.Fetcher.CoinBalanceTest do block = insert(:block, miner: miner, number: block_number) insert(:unfetched_balance, address_hash: miner.hash, block_number: block_number) - CoinBalance.Supervisor.Case.start_supervised!( + CoinBalanceCatchup.Supervisor.Case.start_supervised!( json_rpc_named_arguments: json_rpc_named_arguments, max_batch_size: 2 ) @@ -225,9 +225,9 @@ defmodule Indexer.Fetcher.CoinBalanceTest do end) end - CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) - assert :ok = CoinBalance.async_fetch_balances([%{address_hash: hash, block_number: block_number}]) + assert :ok = CoinBalanceCatchup.async_fetch_balances([%{address_hash: hash, block_number: block_number}]) address = wait(fn -> @@ -318,7 +318,7 @@ defmodule Indexer.Fetcher.CoinBalanceTest do {:ok, [res2]} end) - case CoinBalance.run(entries, json_rpc_named_arguments) do + case CoinBalanceCatchup.run(entries, json_rpc_named_arguments) do :ok -> balances = Repo.all(from(balance in Address.CoinBalance, where: balance.address_hash == ^hash_data)) @@ -373,7 +373,7 @@ defmodule Indexer.Fetcher.CoinBalanceTest do {:ok, [%{id: id, error: %{code: 1, message: "Bad"}}]} end) - assert {:retry, ^entries} = CoinBalance.run(entries, json_rpc_named_arguments) + assert {:retry, ^entries} = CoinBalanceCatchup.run(entries, json_rpc_named_arguments) end test "retries none if all imported and no fetch errors", %{json_rpc_named_arguments: json_rpc_named_arguments} do @@ -401,7 +401,7 @@ defmodule Indexer.Fetcher.CoinBalanceTest do {:ok, [res]} end) - assert :ok = CoinBalance.run(entries, json_rpc_named_arguments) + assert :ok = CoinBalanceCatchup.run(entries, json_rpc_named_arguments) end test "retries fetch errors if all imported", %{json_rpc_named_arguments: json_rpc_named_arguments} do @@ -457,7 +457,7 @@ defmodule Indexer.Fetcher.CoinBalanceTest do end) assert {:retry, [{^address_hash_bytes, ^bad_block_number}]} = - CoinBalance.run( + CoinBalanceCatchup.run( [{address_hash_bytes, good_block_number}, {address_hash_bytes, bad_block_number}], json_rpc_named_arguments ) diff --git a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs index e451525869b1..7e285aae23c6 100644 --- a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs +++ b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs @@ -9,7 +9,8 @@ defmodule Indexer.Fetcher.InternalTransactionTest do alias Explorer.{Chain, Repo} alias Explorer.Chain.{Block, PendingBlockOperation} alias Explorer.Chain.Import.Runner.Blocks - alias Indexer.Fetcher.{CoinBalance, InternalTransaction, PendingTransaction} + alias Indexer.Fetcher.CoinBalance.Catchup, as: CoinBalanceCatchup + alias Indexer.Fetcher.{InternalTransaction, PendingTransaction} # MUST use global mode because we aren't guaranteed to get PendingTransactionFetcher's pid back fast enough to `allow` # it to use expectations and stubs from test's pid. @@ -65,7 +66,7 @@ defmodule Indexer.Fetcher.InternalTransactionTest do end end - CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) PendingTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) wait_for_results(fn -> @@ -276,7 +277,7 @@ defmodule Indexer.Fetcher.InternalTransactionTest do end end - CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) assert %{block_hash: block_hash} = Repo.get(PendingBlockOperation, block_hash) diff --git a/apps/indexer/test/support/indexer/fetcher/coin_balance_supervisor_case.ex b/apps/indexer/test/support/indexer/fetcher/coin_balance_catchup_supervisor_case.ex similarity index 69% rename from apps/indexer/test/support/indexer/fetcher/coin_balance_supervisor_case.ex rename to apps/indexer/test/support/indexer/fetcher/coin_balance_catchup_supervisor_case.ex index 6e2a9e22afc9..17831099c327 100644 --- a/apps/indexer/test/support/indexer/fetcher/coin_balance_supervisor_case.ex +++ b/apps/indexer/test/support/indexer/fetcher/coin_balance_catchup_supervisor_case.ex @@ -1,5 +1,5 @@ -defmodule Indexer.Fetcher.CoinBalance.Supervisor.Case do - alias Indexer.Fetcher.CoinBalance +defmodule Indexer.Fetcher.CoinBalance.Catchup.Supervisor.Case do + alias Indexer.Fetcher.CoinBalance.Catchup def start_supervised!(fetcher_arguments \\ []) when is_list(fetcher_arguments) do merged_fetcher_arguments = @@ -11,7 +11,7 @@ defmodule Indexer.Fetcher.CoinBalance.Supervisor.Case do ) [merged_fetcher_arguments] - |> CoinBalance.Supervisor.child_spec() + |> Catchup.Supervisor.child_spec() |> ExUnit.Callbacks.start_supervised!() end end diff --git a/apps/indexer/test/support/indexer/fetcher/coin_balance_realtime_supervisor_case.ex b/apps/indexer/test/support/indexer/fetcher/coin_balance_realtime_supervisor_case.ex new file mode 100644 index 000000000000..878f6ea7f3f0 --- /dev/null +++ b/apps/indexer/test/support/indexer/fetcher/coin_balance_realtime_supervisor_case.ex @@ -0,0 +1,17 @@ +defmodule Indexer.Fetcher.CoinBalance.Realtime.Supervisor.Case do + alias Indexer.Fetcher.CoinBalance.Realtime + + def start_supervised!(fetcher_arguments \\ []) when is_list(fetcher_arguments) do + merged_fetcher_arguments = + Keyword.merge( + fetcher_arguments, + flush_interval: 50, + max_batch_size: 1, + max_concurrency: 1 + ) + + [merged_fetcher_arguments] + |> Realtime.Supervisor.child_spec() + |> ExUnit.Callbacks.start_supervised!() + end +end diff --git a/config/runtime.exs b/config/runtime.exs index 7f17a40cf7ae..729747fe2b4d 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -573,8 +573,11 @@ config :indexer, Indexer.Fetcher.BlockReward.Supervisor, config :indexer, Indexer.Fetcher.InternalTransaction.Supervisor, disabled?: ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER") -config :indexer, Indexer.Fetcher.CoinBalance.Supervisor, - disabled?: ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_ADDRESS_COIN_BALANCE_FETCHER") +disable_coin_balances_fetcher? = ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_ADDRESS_COIN_BALANCE_FETCHER") + +config :indexer, Indexer.Fetcher.CoinBalance.Catchup.Supervisor, disabled?: disable_coin_balances_fetcher? + +config :indexer, Indexer.Fetcher.CoinBalance.Realtime.Supervisor, disabled?: disable_coin_balances_fetcher? config :indexer, Indexer.Fetcher.TokenUpdater.Supervisor, disabled?: ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_CATALOGED_TOKEN_UPDATER_FETCHER") @@ -659,9 +662,16 @@ config :indexer, Indexer.Fetcher.InternalTransaction, indexing_finished_threshold: ConfigHelper.parse_integer_env_var("INDEXER_INTERNAL_TRANSACTIONS_INDEXING_FINISHED_THRESHOLD", 1000) -config :indexer, Indexer.Fetcher.CoinBalance, - batch_size: ConfigHelper.parse_integer_env_var("INDEXER_COIN_BALANCES_BATCH_SIZE", 500), - concurrency: ConfigHelper.parse_integer_env_var("INDEXER_COIN_BALANCES_CONCURRENCY", 4) +coin_balances_batch_size = ConfigHelper.parse_integer_env_var("INDEXER_COIN_BALANCES_BATCH_SIZE", 100) +coin_balances_concurrency = ConfigHelper.parse_integer_env_var("INDEXER_COIN_BALANCES_CONCURRENCY", 4) + +config :indexer, Indexer.Fetcher.CoinBalance.Catchup, + batch_size: coin_balances_batch_size, + concurrency: coin_balances_concurrency + +config :indexer, Indexer.Fetcher.CoinBalance.Realtime, + batch_size: coin_balances_batch_size, + concurrency: coin_balances_concurrency config :indexer, Indexer.Fetcher.Withdrawal.Supervisor, disabled?: System.get_env("INDEXER_DISABLE_WITHDRAWALS_FETCHER", "true") == "true" diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 697dbe4962c5..53ed9eed2f3b 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -117,6 +117,7 @@ API_RATE_LIMIT_BY_IP=3000 DISABLE_INDEXER=false DISABLE_REALTIME_INDEXER=false DISABLE_CATCHUP_INDEXER=false +INDEXER_DISABLE_ADDRESS_COIN_BALANCE_FETCHER=false INDEXER_DISABLE_TOKEN_INSTANCE_REALTIME_FETCHER=false INDEXER_DISABLE_TOKEN_INSTANCE_RETRY_FETCHER=false INDEXER_DISABLE_TOKEN_INSTANCE_SANITIZE_FETCHER=false From a0e801ac413bfc1f103ad6bc74c5eb5e5f2e69b5 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 21 Feb 2024 12:23:16 +0300 Subject: [PATCH 492/607] Rename some Noves.fi proxy endpoints --- apps/block_scout_web/lib/block_scout_web/api_router.ex | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 467793c43cd1..604055443629 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -326,8 +326,16 @@ defmodule BlockScoutWeb.ApiRouter do scope "/proxy" do scope "/noves-fi" do get("/transactions/:transaction_hash_param", V2.Proxy.NovesFiController, :transaction) + # todo: remove in the future get("/transactions/:transaction_hash_param/describe", V2.Proxy.NovesFiController, :describe_transaction) - get("/addresses/:address_hash_param/transactions", V2.Proxy.NovesFiController, :address_transactions) + get("/transactions/:transaction_hash_param/description", V2.Proxy.NovesFiController, :describe_transaction) + + get( + "/addresses/:address_hash_param/transaction-descriptions", + V2.Proxy.NovesFiController, + :address_transactions + ) + get("/transactions", V2.Proxy.NovesFiController, :describe_transactions) end From 08bf588955e721eeda86c62b6ca018e377ddce8f Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 21 Feb 2024 12:53:31 +0300 Subject: [PATCH 493/607] Solidityscan integration enhancements --- CHANGELOG.md | 1 + .../controllers/api/v2/fallback_controller.ex | 8 ++++++ .../api/v2/smart_contract_controller.ex | 26 +++++++------------ .../third_party_integrations/solidityscan.ex | 25 +++++++++++++----- 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 972e103a8a52..bdb8c07c137e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ ### Chore +- [#9439](https://github.com/blockscout/blockscout/pull/9439) - Solidityscan integration enhancements - [#9398](https://github.com/blockscout/blockscout/pull/9398) - Improve elixir dependencies caching in CI - [#9393](https://github.com/blockscout/blockscout/pull/9393) - Bump actions/cache to v4 - [#9389](https://github.com/blockscout/blockscout/pull/9389) - Output user address as an object in API v2 for Shibarium diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex index 23dfa66da48c..4948bc20eea4 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex @@ -28,6 +28,7 @@ defmodule BlockScoutWeb.API.V2.FallbackController do @address_not_found "Address not found" @address_is_not_smart_contract "Address is not smart-contract" @vyper_smart_contract_is_not_supported "Vyper smart-contracts are not supported by SolidityScan" + @unverified_smart_contract "Smart-contract is unverified" @empty_response "Empty response" @tx_interpreter_service_disabled "Transaction Interpretation Service is not enabled" @disabled "API endpoint is disabled" @@ -269,6 +270,13 @@ defmodule BlockScoutWeb.API.V2.FallbackController do |> render(:message, %{message: @vyper_smart_contract_is_not_supported}) end + def call(conn, {:is_verified_smart_contract, result}) when result == false do + conn + |> put_status(:not_found) + |> put_view(ApiView) + |> render(:message, %{message: @unverified_smart_contract}) + end + def call(conn, {:is_empty_response, true}) do conn |> put_status(500) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex index 805dfe1065ac..864a7243c5b8 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex @@ -195,28 +195,22 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do | {:is_empty_response, true} | {:is_smart_contract, false | nil} | {:restricted_access, true} + | {:is_verified_smart_contract, false} | {:is_vyper_contract, true} | Plug.Conn.t() def solidityscan_report(conn, %{"address_hash" => address_hash_string} = params) do with {:format_address, {:ok, address_hash}} <- {:format_address, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), {:address, {:ok, address}} <- {:address, Chain.hash_to_address(address_hash)}, - {:is_smart_contract, true} <- {:is_smart_contract, Address.smart_contract?(address)} do - smart_contract = SmartContract.address_hash_to_smart_contract_without_twin(address_hash, @api_true) - - if smart_contract && smart_contract.is_vyper_contract do - {:is_vyper_contract, true} - else - response = SolidityScan.solidityscan_request(address_hash_string) - - if is_nil(response) do - {:is_empty_response, true} - else - conn - |> put_status(200) - |> json(response) - end - end + {:is_smart_contract, true} <- {:is_smart_contract, Address.smart_contract?(address)}, + smart_contract = SmartContract.address_hash_to_smart_contract_without_twin(address_hash, @api_true), + {:is_verified_smart_contract, true} <- {:is_verified_smart_contract, !is_nil(smart_contract)}, + {:is_vyper_contract, false} <- {:is_vyper_contract, smart_contract.is_vyper_contract}, + response = SolidityScan.solidityscan_request(address_hash_string), + {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do + conn + |> put_status(200) + |> json(response) end end diff --git a/apps/explorer/lib/explorer/third_party_integrations/solidityscan.ex b/apps/explorer/lib/explorer/third_party_integrations/solidityscan.ex index 40a5bffb9784..27a53f5b6a2e 100644 --- a/apps/explorer/lib/explorer/third_party_integrations/solidityscan.ex +++ b/apps/explorer/lib/explorer/third_party_integrations/solidityscan.ex @@ -3,6 +3,7 @@ defmodule Explorer.ThirdPartyIntegrations.SolidityScan do Module for SolidityScan integration https://apidoc.solidityscan.com/solidityscan-security-api/solidityscan-other-apis/quickscan-api-v1 """ + require Logger alias Explorer.Helper @blockscout_platform_id "16" @@ -17,17 +18,29 @@ defmodule Explorer.ThirdPartyIntegrations.SolidityScan do url = base_url(address_hash_string) - case HTTPoison.get(url, headers, recv_timeout: @recv_timeout) do - {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> - Helper.decode_json(body) + if url do + case HTTPoison.get(url, headers, recv_timeout: @recv_timeout) do + {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> + Helper.decode_json(body) - _ -> - nil + _ -> + nil + end + else + Logger.warning( + "SOLIDITYSCAN_CHAIN_ID or SOLIDITYSCAN_API_TOKEN env variable is not configured on the backend. Please, set it." + ) + + nil end end defp base_url(address_hash_string) do - "https://api.solidityscan.com/api/v1/quickscan/#{@blockscout_platform_id}/#{chain_id()}/#{address_hash_string}" + if chain_id() && api_key() do + "https://api.solidityscan.com/api/v1/quickscan/#{@blockscout_platform_id}/#{chain_id()}/#{address_hash_string}" + else + nil + end end defp chain_id do From 97ea7ccf17ef780cc77ab922fb30939f87d41f82 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:16:34 +0300 Subject: [PATCH 494/607] Add `debug_traceBlockByNumber` to `method_to_url` --- CHANGELOG.md | 1 + apps/explorer/config/dev/geth.exs | 3 ++- apps/explorer/config/prod/geth.exs | 3 ++- apps/indexer/config/dev/geth.exs | 3 ++- apps/indexer/config/prod/geth.exs | 3 ++- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 972e103a8a52..973103b8e082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ ### Fixes +- [#9440](https://github.com/blockscout/blockscout/pull/9440) - Add `debug_traceBlockByNumber` to `method_to_url` - [#9387](https://github.com/blockscout/blockscout/pull/9387) - Filter out Vyper contracts in Solidityscan API endpoint - [#9377](https://github.com/blockscout/blockscout/pull/9377) - Speed up account abstraction proxy - [#9371](https://github.com/blockscout/blockscout/pull/9371) - Filter empty values before token update diff --git a/apps/explorer/config/dev/geth.exs b/apps/explorer/config/dev/geth.exs index 8b644ff987d2..97d2ca3afdab 100644 --- a/apps/explorer/config/dev/geth.exs +++ b/apps/explorer/config/dev/geth.exs @@ -17,7 +17,8 @@ config :explorer, fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), - debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" + debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", + debug_traceBlockByNumber: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], diff --git a/apps/explorer/config/prod/geth.exs b/apps/explorer/config/prod/geth.exs index 46d1f6bc110b..6080d6f7ca44 100644 --- a/apps/explorer/config/prod/geth.exs +++ b/apps/explorer/config/prod/geth.exs @@ -17,7 +17,8 @@ config :explorer, fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url(), - debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") + debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), + debug_traceBlockByNumber: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], diff --git a/apps/indexer/config/dev/geth.exs b/apps/indexer/config/dev/geth.exs index b7eb4d7facf0..097e48e223f0 100644 --- a/apps/indexer/config/dev/geth.exs +++ b/apps/indexer/config/dev/geth.exs @@ -22,7 +22,8 @@ config :indexer, fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), - debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" + debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", + debug_traceBlockByNumber: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], diff --git a/apps/indexer/config/prod/geth.exs b/apps/indexer/config/prod/geth.exs index 59bb2c23e1f4..cfa1c8372436 100644 --- a/apps/indexer/config/prod/geth.exs +++ b/apps/indexer/config/prod/geth.exs @@ -22,7 +22,8 @@ config :indexer, fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url(), - debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") + debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), + debug_traceBlockByNumber: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], From 81bbf4bae2736f85d5d5efb6e129462dbbb98bd7 Mon Sep 17 00:00:00 2001 From: sevenzing <41516657+sevenzing@users.noreply.github.com> Date: Wed, 21 Feb 2024 17:43:00 +0700 Subject: [PATCH 495/607] Change bens address seach to get_address func --- .../explorer/microservice_interfaces/bens.ex | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex index 59eb1c82403f..8dc28534690b 100644 --- a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -66,6 +66,13 @@ defmodule Explorer.MicroserviceInterfaces.BENS do end end + @spec get_address(binary()) :: {:error, :disabled | binary() | Jason.DecodeError.t()} | {:ok, any} + def get_address(address) do + with :ok <- Microservice.check_enabled(__MODULE__) do + http_get_request(get_address_url(address), nil) + end + end + @doc """ Lookup for ENS domain name via GET {{baseUrl}}/api/v1/:chainId/domains:lookup """ @@ -138,6 +145,10 @@ defmodule Explorer.MicroserviceInterfaces.BENS do "#{addresses_url()}:lookup" end + defp get_address_url(address) do + "#{addresses_url()}/#{address}" + end + defp domain_lookup_url do "#{domains_url()}:lookup" end @@ -214,7 +225,7 @@ defmodule Explorer.MicroserviceInterfaces.BENS do end @doc """ - Preload ENS info to search result, using address_lookup/1 + Preload ENS info to search result, using get_address/1 """ @spec preload_ens_info_to_search_results(list) :: list def preload_ens_info_to_search_results(list) do @@ -223,7 +234,7 @@ defmodule Explorer.MicroserviceInterfaces.BENS do search_result %{type: "address"} = search_result -> - ens_info = search_result[:address_hash] |> address_lookup() |> parse_lookup_response() + ens_info = search_result[:address_hash] |> get_address() |> parse_get_address_response() Map.put(search_result, :ens_info, ens_info) search_result -> @@ -259,6 +270,29 @@ defmodule Explorer.MicroserviceInterfaces.BENS do defp parse_lookup_response(_), do: nil + defp parse_get_address_response( + {:ok, + %{ + "domain" => %{ + "name" => name, + "expiry_date" => expiry_date, + "resolved_address" => %{"hash" => address_hash_string} + }, + "resolved_domains_count" => resolved_domains_count + }} + ) do + {:ok, hash} = Chain.string_to_address_hash(address_hash_string) + + %{ + name: name, + expiry_date: expiry_date, + names_count: resolved_domains_count, + address_hash: Address.checksum(hash) + } + end + + defp parse_get_address_response(_), do: nil + defp item_to_address_hash_strings(%Transaction{ to_address_hash: nil, created_contract_address_hash: created_contract_address_hash, From a2bb240a3838bc920b058474bd7ec7871afe36af Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 21 Feb 2024 14:22:51 +0300 Subject: [PATCH 496/607] Remove Noves.fi /describe endpoint since it is unused anymore --- .../lib/block_scout_web/api_router.ex | 3 --- .../api/v2/proxy/noves_fi_controller.ex | 21 +------------------ .../third_party_integrations/noves_fi.ex | 8 ------- 3 files changed, 1 insertion(+), 31 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 604055443629..3fd60a419ba0 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -326,9 +326,6 @@ defmodule BlockScoutWeb.ApiRouter do scope "/proxy" do scope "/noves-fi" do get("/transactions/:transaction_hash_param", V2.Proxy.NovesFiController, :transaction) - # todo: remove in the future - get("/transactions/:transaction_hash_param/describe", V2.Proxy.NovesFiController, :describe_transaction) - get("/transactions/:transaction_hash_param/description", V2.Proxy.NovesFiController, :describe_transaction) get( "/addresses/:address_hash_param/transaction-descriptions", diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_controller.ex index e68c260f344a..8782125f2def 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_controller.ex @@ -26,26 +26,7 @@ defmodule BlockScoutWeb.API.V2.Proxy.NovesFiController do end @doc """ - Function to handle GET requests to `/api/v2/proxy/noves-fi/transactions/:transaction_hash_param/describe` endpoint. - """ - @spec describe_transaction(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} - def describe_transaction(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do - with {:ok, _transaction, _transaction_hash} <- - TransactionController.validate_transaction(transaction_hash_string, params, - necessity_by_association: %{}, - api?: true - ), - url = NovesFi.describe_tx_url(transaction_hash_string), - {response, status} <- NovesFi.noves_fi_api_request(url, conn), - {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do - conn - |> put_status(status) - |> json(response) - end - end - - @doc """ - Function to handle GET requests to `/api/v2/proxy/noves-fi/transactions/:transaction_hash_param/transactions` endpoint. + Function to handle GET requests to `/api/v2/proxy/noves-fi/transactions/:transaction_hash_param/transaction-descriptions` endpoint. """ @spec address_transactions(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} def address_transactions(conn, %{"address_hash_param" => address_hash_string} = params) do diff --git a/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex b/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex index 39dad308954c..c63501cc071d 100644 --- a/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex +++ b/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex @@ -60,14 +60,6 @@ defmodule Explorer.ThirdPartyIntegrations.NovesFi do "#{base_url()}/evm/#{chain_name()}/tx/#{transaction_hash_string}" end - @doc """ - Noves.fi /evm/{chain}/describeTx/{txHash} endpoint - """ - @spec describe_tx_url(String.t()) :: String.t() - def describe_tx_url(transaction_hash_string) do - "#{base_url()}/evm/#{chain_name()}/describeTx/#{transaction_hash_string}" - end - @doc """ Noves.fi /evm/{chain}/describeTxs endpoint """ From 9e9a6a33a4b9e3b336ad320d1deaf82ac141626b Mon Sep 17 00:00:00 2001 From: sevenzing <41516657+sevenzing@users.noreply.github.com> Date: Wed, 21 Feb 2024 18:49:08 +0700 Subject: [PATCH 497/607] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdb8c07c137e..338773e6d2b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#9441](https://github.com/blockscout/blockscout/pull/9441) - Update BENS integration: change endpoint for resolving address in search - [#9437](https://github.com/blockscout/blockscout/pull/9437) - Add Enum.uniq before sanitizing token transfers - [#9403](https://github.com/blockscout/blockscout/pull/9403) - Null round handling - [#9401](https://github.com/blockscout/blockscout/pull/9401) - Eliminate incorrect token transfers with empty token_ids From 758d64b9c04e8a5fbc8b928598fae769c287b6a3 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Wed, 21 Feb 2024 15:25:14 +0300 Subject: [PATCH 498/607] Fix quick search bug --- apps/explorer/lib/explorer/chain/search.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/explorer/lib/explorer/chain/search.ex b/apps/explorer/lib/explorer/chain/search.ex index 4e2a50ef8c74..ce64b8d0d2fd 100644 --- a/apps/explorer/lib/explorer/chain/search.ex +++ b/apps/explorer/lib/explorer/chain/search.ex @@ -611,6 +611,7 @@ defmodule Explorer.Chain.Search do |> Map.put(:address_hash, ens_info[:address_hash]) |> Map.put(:type, "address") |> Map.put(:ens_info, ens_info) + |> Map.put(:timestamp, nil) end defp merge_address_search_result_with_ens_info([address], ens_info) do From 2384e9aff045cec3e927a5da41f307ed2b6fdb2d Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Wed, 21 Feb 2024 15:26:24 +0300 Subject: [PATCH 499/607] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6be767212643..27652a88ae74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ ### Fixes +- [#9444](https://github.com/blockscout/blockscout/pull/9444) - Fix quick search bug - [#9440](https://github.com/blockscout/blockscout/pull/9440) - Add `debug_traceBlockByNumber` to `method_to_url` - [#9387](https://github.com/blockscout/blockscout/pull/9387) - Filter out Vyper contracts in Solidityscan API endpoint - [#9377](https://github.com/blockscout/blockscout/pull/9377) - Speed up account abstraction proxy From 97e7f6dd576d5b8d5515defe11ab03fa19395ba7 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 21 Feb 2024 22:35:12 +0300 Subject: [PATCH 500/607] v6.2.0 --- CHANGELOG.md | 37 +++++++++++++++++++++++++++++++ apps/block_scout_web/mix.exs | 2 +- apps/ethereum_jsonrpc/mix.exs | 2 +- apps/explorer/mix.exs | 2 +- apps/indexer/mix.exs | 2 +- docker-compose/docker-compose.yml | 2 +- docker/Makefile | 2 +- mix.exs | 2 +- rel/config.exs | 2 +- 9 files changed, 45 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10421f29f3cc..94a70192b09e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ ### Features +### Fixes + +### Chore + +
+ Dependencies version bumps + +
+ +## 6.2.0 + +### Features + - [#9441](https://github.com/blockscout/blockscout/pull/9441) - Update BENS integration: change endpoint for resolving address in search - [#9437](https://github.com/blockscout/blockscout/pull/9437) - Add Enum.uniq before sanitizing token transfers - [#9403](https://github.com/blockscout/blockscout/pull/9403) - Null round handling @@ -49,6 +62,30 @@
Dependencies version bumps +- [#9335](https://github.com/blockscout/blockscout/pull/9335) - Bump mini-css-extract-plugin from 2.7.7 to 2.8.0 in /apps/block_scout_web/assets +- [#9333](https://github.com/blockscout/blockscout/pull/9333) - Bump sweetalert2 from 11.10.3 to 11.10.5 in /apps/block_scout_web/assets +- [#9288](https://github.com/blockscout/blockscout/pull/9288) - Bump solc from 0.8.23 to 0.8.24 in /apps/explorer +- [#9287](https://github.com/blockscout/blockscout/pull/9287) - Bump @babel/preset-env from 7.23.8 to 7.23.9 in /apps/block_scout_web/assets +- [#9331](https://github.com/blockscout/blockscout/pull/9331) - Bump logger_json from 5.1.2 to 5.1.3 +- [#9330](https://github.com/blockscout/blockscout/pull/9330) - Bump hammer from 6.1.0 to 6.2.0 +- [#9294](https://github.com/blockscout/blockscout/pull/9294) - Bump exvcr from 0.15.0 to 0.15.1 +- [#9293](https://github.com/blockscout/blockscout/pull/9293) - Bump floki from 0.35.2 to 0.35.3 +- [#9338](https://github.com/blockscout/blockscout/pull/9338) - Bump postcss-loader from 8.0.0 to 8.1.0 in /apps/block_scout_web/assets +- [#9336](https://github.com/blockscout/blockscout/pull/9336) - Bump web3 from 1.10.3 to 1.10.4 in /apps/block_scout_web/assets +- [#9290](https://github.com/blockscout/blockscout/pull/9290) - Bump ex_doc from 0.31.0 to 0.31.1 +- [#9285](https://github.com/blockscout/blockscout/pull/9285) - Bump @amplitude/analytics-browser from 2.3.8 to 2.4.0 in /apps/block_scout_web/assets +- [#9283](https://github.com/blockscout/blockscout/pull/9283) - Bump @babel/core from 7.23.7 to 7.23.9 in /apps/block_scout_web/assets +- [#9337](https://github.com/blockscout/blockscout/pull/9337) - Bump css-loader from 6.9.1 to 6.10.0 in /apps/block_scout_web/assets +- [#9334](https://github.com/blockscout/blockscout/pull/9334) - Bump sass-loader from 14.0.0 to 14.1.0 in /apps/block_scout_web/assets +- [#9339](https://github.com/blockscout/blockscout/pull/9339) - Bump webpack from 5.89.0 to 5.90.1 in /apps/block_scout_web/assets +- [#9383](https://github.com/blockscout/blockscout/pull/9383) - Bump credo from 1.7.3 to 1.7.4 +- [#9384](https://github.com/blockscout/blockscout/pull/9384) - Bump postcss from 8.4.33 to 8.4.35 in /apps/block_scout_web/assets +- [#9385](https://github.com/blockscout/blockscout/pull/9385) - Bump mixpanel-browser from 2.48.1 to 2.49.0 in /apps/block_scout_web/assets +- [#9423](https://github.com/blockscout/blockscout/pull/9423) - Bump @amplitude/analytics-browser from 2.4.0 to 2.4.1 in /apps/block_scout_web/assets +- [#9422](https://github.com/blockscout/blockscout/pull/9422) - Bump core-js from 3.35.1 to 3.36.0 in /apps/block_scout_web/assets +- [#9424](https://github.com/blockscout/blockscout/pull/9424) - Bump webpack from 5.90.1 to 5.90.3 in /apps/block_scout_web/assets +- [#9425](https://github.com/blockscout/blockscout/pull/9425) - Bump sass-loader from 14.1.0 to 14.1.1 in /apps/block_scout_web/assets +- [#9421](https://github.com/blockscout/blockscout/pull/9421) - Bump sass from 1.70.0 to 1.71.0 in /apps/block_scout_web/assets
## 6.1.0 diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index 5f364a4b16e9..81be6caa8eb6 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -23,7 +23,7 @@ defmodule BlockScoutWeb.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.1.0", + version: "6.2.0", xref: [exclude: [Explorer.Chain.PolygonZkevm.Reader, Explorer.Chain.Beacon.Reader]] ] end diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index 2b10d438ef6d..c6dafe238226 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -23,7 +23,7 @@ defmodule EthereumJsonrpc.MixProject do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.1.0" + version: "6.2.0" ] end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 51cf73034e63..aad16f8467f0 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -24,7 +24,7 @@ defmodule Explorer.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.1.0", + version: "6.2.0", xref: [exclude: [BlockScoutWeb.WebRouter.Helpers, Indexer.Helper]] ] end diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs index 2303df05fc7b..0f1a39815391 100644 --- a/apps/indexer/mix.exs +++ b/apps/indexer/mix.exs @@ -14,7 +14,7 @@ defmodule Indexer.MixProject do elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", start_permanent: Mix.env() == :prod, - version: "6.1.0" + version: "6.2.0" ] end diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index d17409f98fc0..a62c1024b3de 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -34,7 +34,7 @@ services: CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED: "" CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL: "" ADMIN_PANEL_ENABLED: "" - RELEASE_VERSION: 6.1.0 + RELEASE_VERSION: 6.2.0 links: - db:database environment: diff --git a/docker/Makefile b/docker/Makefile index 9d356b7d5512..b7521d41ad9d 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -10,7 +10,7 @@ STATS_CONTAINER_NAME := stats STATS_DB_CONTAINER_NAME := stats-db PROXY_CONTAINER_NAME := proxy PG_CONTAINER_NAME := postgres -RELEASE_VERSION ?= '6.1.0' +RELEASE_VERSION ?= '6.2.0' TAG := $(RELEASE_VERSION)-commit-$(shell git log -1 --pretty=format:"%h") STABLE_TAG := $(RELEASE_VERSION) diff --git a/mix.exs b/mix.exs index baf86033be79..8b59a29aeb0e 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule BlockScout.Mixfile do [ # app: :block_scout, # aliases: aliases(config_env()), - version: "6.1.0", + version: "6.2.0", apps_path: "apps", deps: deps(), dialyzer: dialyzer(), diff --git a/rel/config.exs b/rel/config.exs index ffb5fbc4ae7b..ee8931e915d2 100644 --- a/rel/config.exs +++ b/rel/config.exs @@ -71,7 +71,7 @@ end # will be used by default release :blockscout do - set version: "6.1.0-beta" + set version: "6.2.0-beta" set applications: [ :runtime_tools, block_scout_web: :permanent, From a86035aac7e809ccbce7f5e130bf8f785afd6adf Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 22 Feb 2024 11:40:50 +0300 Subject: [PATCH 501/607] Update master tag with pre-release workflow --- .github/workflows/prerelease.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 4ec067e47968..b47c998c8a35 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -33,7 +33,7 @@ jobs: push: true cache-from: type=registry,ref=blockscout/blockscout:buildcache cache-to: type=registry,ref=blockscout/blockscout:buildcache,mode=max - tags: blockscout/blockscout:latest, blockscout/blockscout:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + tags: blockscout/blockscout:master, blockscout/blockscout:latest, blockscout/blockscout:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} platforms: | linux/amd64 linux/arm64/v8 From 46418233a944f9486b57135a0237283fdd8790c4 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 22 Feb 2024 21:34:04 +0300 Subject: [PATCH 502/607] Fix Noves.fi endpoints --- apps/block_scout_web/lib/block_scout_web/api_router.ex | 8 ++------ .../controllers/api/v2/proxy/noves_fi_controller.ex | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 3fd60a419ba0..2baa39ba7707 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -327,13 +327,9 @@ defmodule BlockScoutWeb.ApiRouter do scope "/noves-fi" do get("/transactions/:transaction_hash_param", V2.Proxy.NovesFiController, :transaction) - get( - "/addresses/:address_hash_param/transaction-descriptions", - V2.Proxy.NovesFiController, - :address_transactions - ) + get("/addresses/:address_hash_param/transaction", V2.Proxy.NovesFiController, :address_transactions) - get("/transactions", V2.Proxy.NovesFiController, :describe_transactions) + get("/transaction-descriptions", V2.Proxy.NovesFiController, :describe_transactions) end scope "/account-abstraction" do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_controller.ex index 8782125f2def..69463dae21f8 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_controller.ex @@ -26,7 +26,7 @@ defmodule BlockScoutWeb.API.V2.Proxy.NovesFiController do end @doc """ - Function to handle GET requests to `/api/v2/proxy/noves-fi/transactions/:transaction_hash_param/transaction-descriptions` endpoint. + Function to handle GET requests to `/api/v2/proxy/noves-fi/transactions/:transaction_hash_param/transaction` endpoint. """ @spec address_transactions(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} def address_transactions(conn, %{"address_hash_param" => address_hash_string} = params) do From c9a2d1431fb94312fde6c8566e48ac843dc564b9 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 22 Feb 2024 22:17:45 +0300 Subject: [PATCH 503/607] Fix Noves.fi endpoint --- apps/block_scout_web/lib/block_scout_web/api_router.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 2baa39ba7707..9b3e12dd76c5 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -327,7 +327,7 @@ defmodule BlockScoutWeb.ApiRouter do scope "/noves-fi" do get("/transactions/:transaction_hash_param", V2.Proxy.NovesFiController, :transaction) - get("/addresses/:address_hash_param/transaction", V2.Proxy.NovesFiController, :address_transactions) + get("/addresses/:address_hash_param/transactions", V2.Proxy.NovesFiController, :address_transactions) get("/transaction-descriptions", V2.Proxy.NovesFiController, :describe_transactions) end From b74ddce2a2ccff62ce2c7657a40a0ed510834f26 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Sat, 24 Feb 2024 11:18:35 -0500 Subject: [PATCH 504/607] feat: stream blocks without internal transactions backwards --- apps/explorer/lib/explorer/chain.ex | 3 ++- ...reate_index_pending_block_operations_block_number.exs | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 apps/explorer/priv/repo/migrations/20240224112210_create_index_pending_block_operations_block_number.exs diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index c18ecd787f55..625025bba177 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1829,7 +1829,8 @@ defmodule Explorer.Chain do from( po in PendingBlockOperation, where: not is_nil(po.block_number), - select: po.block_number + select: po.block_number, + order_by: [desc: po.block_number] ) query diff --git a/apps/explorer/priv/repo/migrations/20240224112210_create_index_pending_block_operations_block_number.exs b/apps/explorer/priv/repo/migrations/20240224112210_create_index_pending_block_operations_block_number.exs new file mode 100644 index 000000000000..ad590ae77aa6 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240224112210_create_index_pending_block_operations_block_number.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.CreateIndexPendingBlockOperationsBlockNumber do + use Ecto.Migration + @disable_ddl_transaction true + @disable_migration_lock true + + def change do + create_if_not_exists(index(:pending_block_operations, :block_number, concurrently: true)) + end +end From ca54dce67d49427f730c2ba11d114177245a9382 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Sat, 24 Feb 2024 11:55:11 -0500 Subject: [PATCH 505/607] chore: changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94a70192b09e..7d56741ee5e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Features +- [#9461](https://github.com/blockscout/blockscout/pull/9461) - Fetch blocks without internal transactions backwards + ### Fixes ### Chore From c4eda099ea9f970a14ed95745efc068472de93d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 18:53:05 +0000 Subject: [PATCH 506/607] Bump sass from 1.71.0 to 1.71.1 in /apps/block_scout_web/assets Bumps [sass](https://github.com/sass/dart-sass) from 1.71.0 to 1.71.1. - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.71.0...1.71.1) --- updated-dependencies: - dependency-name: sass dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 615db5ef19c3..ac1f2b8abed1 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -89,7 +89,7 @@ "mini-css-extract-plugin": "^2.8.0", "postcss": "^8.4.35", "postcss-loader": "^8.1.0", - "sass": "^1.71.0", + "sass": "^1.71.1", "sass-loader": "^14.1.1", "style-loader": "^3.3.4", "webpack": "^5.90.3", @@ -15224,9 +15224,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.71.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.0.tgz", - "integrity": "sha512-HKKIKf49Vkxlrav3F/w6qRuPcmImGVbIXJ2I3Kg0VMA+3Bav+8yE9G5XmP5lMj6nl4OlqbPftGAscNaNu28b8w==", + "version": "1.71.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", + "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -29294,9 +29294,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass": { - "version": "1.71.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.0.tgz", - "integrity": "sha512-HKKIKf49Vkxlrav3F/w6qRuPcmImGVbIXJ2I3Kg0VMA+3Bav+8yE9G5XmP5lMj6nl4OlqbPftGAscNaNu28b8w==", + "version": "1.71.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", + "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index e7edf532c30c..e2efa472fa7b 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -101,7 +101,7 @@ "mini-css-extract-plugin": "^2.8.0", "postcss": "^8.4.35", "postcss-loader": "^8.1.0", - "sass": "^1.71.0", + "sass": "^1.71.1", "sass-loader": "^14.1.1", "style-loader": "^3.3.4", "webpack": "^5.90.3", From 58501a3bc24365409e46d4a5d5a19993768189e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 18:54:17 +0000 Subject: [PATCH 507/607] Bump @amplitude/analytics-browser in /apps/block_scout_web/assets Bumps [@amplitude/analytics-browser](https://github.com/amplitude/Amplitude-TypeScript) from 2.4.1 to 2.5.1. - [Release notes](https://github.com/amplitude/Amplitude-TypeScript/releases) - [Commits](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@2.4.1...@amplitude/analytics-browser@2.5.1) --- updated-dependencies: - dependency-name: "@amplitude/analytics-browser" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 130 +++++++++--------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 66 insertions(+), 66 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 615db5ef19c3..cafd155ad900 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -7,7 +7,7 @@ "name": "blockscout", "license": "GPL-3.0", "dependencies": { - "@amplitude/analytics-browser": "^2.4.1", + "@amplitude/analytics-browser": "^2.5.1", "@fortawesome/fontawesome-free": "^6.5.1", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", @@ -116,15 +116,15 @@ } }, "node_modules/@amplitude/analytics-browser": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.4.1.tgz", - "integrity": "sha512-sxudfDAOZcyTJOHUc66uAHgZbZY9PORh4CAX4V66ozW+0vneeYPJm3ECCJqIrdJ/JWceOe/ohCT8G06X9s/4yg==", - "dependencies": { - "@amplitude/analytics-client-common": "^2.0.11", - "@amplitude/analytics-core": "^2.2.0", - "@amplitude/analytics-types": "^2.4.0", - "@amplitude/plugin-page-view-tracking-browser": "^2.1.1", - "@amplitude/plugin-web-attribution-browser": "^2.1.1", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.5.1.tgz", + "integrity": "sha512-WH31CyL5qPUSs7BY+udxDwWXmlBYfNJzCTwquWt/zUmd4aLJ7ec21rdN04RO4TCh68KpTGSb3l8K85J69fiVGw==", + "dependencies": { + "@amplitude/analytics-client-common": "^2.1.0", + "@amplitude/analytics-core": "^2.2.1", + "@amplitude/analytics-types": "^2.5.0", + "@amplitude/plugin-page-view-tracking-browser": "^2.2.1", + "@amplitude/plugin-web-attribution-browser": "^2.1.3", "tslib": "^2.4.1" } }, @@ -134,13 +134,13 @@ "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" }, "node_modules/@amplitude/analytics-client-common": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.11.tgz", - "integrity": "sha512-f2zzo7Sk1hz8/WT7kPB6HZqAyHJrxMzw9nTqbLoRsaG9xm4YOJ0WCwtlu42RVRqIoWR89RTmIkqIjqimqMaHEQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.1.0.tgz", + "integrity": "sha512-wgZs7Orb3+ei7tBqxd5Ug3OzX19E0YpMkhHWFYesmpjBOW+Ng50rolyfB10rHjjeoMgiRc8eGDdnMH4+y7OQlA==", "dependencies": { "@amplitude/analytics-connector": "^1.4.8", - "@amplitude/analytics-core": "^2.2.0", - "@amplitude/analytics-types": "^2.4.0", + "@amplitude/analytics-core": "^2.2.1", + "@amplitude/analytics-types": "^2.5.0", "tslib": "^2.4.1" } }, @@ -155,11 +155,11 @@ "integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g==" }, "node_modules/@amplitude/analytics-core": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.2.0.tgz", - "integrity": "sha512-JVx1chKa/sfqpBNEZn6jaZZV3DW/6cOoJxx8b5oqHIn+5HXizEOV85aLFY0rrtZ/uR/HrdffxrMKzr/uBFlV+A==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.2.1.tgz", + "integrity": "sha512-F4IrNzealRXnC3cPjkOoOEd68xsxuDsTeEGErAdxqWXNqV1x9oy1WM1lIKw2yGOyf6OZHidP/Vbp/BJK6B0Fmg==", "dependencies": { - "@amplitude/analytics-types": "^2.4.0", + "@amplitude/analytics-types": "^2.5.0", "tslib": "^2.4.1" } }, @@ -169,17 +169,17 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@amplitude/analytics-types": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-2.4.0.tgz", - "integrity": "sha512-GVj9W4X3XMVyGfqXdES2vFU8pqTIHvihj/vNOjOrwYHVdia3GjlcGl77GXuERCIwr52MoiUVTGXmXn3adf+A+Q==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-2.5.0.tgz", + "integrity": "sha512-aY69WxUvVlaCU+9geShjTsAYdUTvegEXH9i4WK/97kNbNLl4/7qUuIPe4hNireDeKLuQA9SA3H7TKynuNomDxw==" }, "node_modules/@amplitude/plugin-page-view-tracking-browser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.1.1.tgz", - "integrity": "sha512-dZwA0kb/dvFTokF2yVklfjwl0Ben6g6Sukpr84tocpurJ7Ojhh1egISCZ4QWt2VgKKh7X4pyK73f5Fcp/1JcNw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.2.1.tgz", + "integrity": "sha512-KBB3nLFPns53haa4VaoWnomwxTX813mtoIYoq4pads+LjBvwUdgwZHsj/zEwYY7duolTUinxB9+Sx9lO6Ale2Q==", "dependencies": { - "@amplitude/analytics-client-common": "^2.0.11", - "@amplitude/analytics-types": "^2.4.0", + "@amplitude/analytics-client-common": "^2.1.0", + "@amplitude/analytics-types": "^2.5.0", "tslib": "^2.4.1" } }, @@ -189,13 +189,13 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@amplitude/plugin-web-attribution-browser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.1.1.tgz", - "integrity": "sha512-paWFk+uJeVlUF/HYNoczIxP3IJX/KR573B/E4Wp5bLB3XeJIhRbj3RQdJKhnKN5keg8+Vei/8uGdGQK4d3DYDg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.1.3.tgz", + "integrity": "sha512-CLTzgtewfgSLkwACkvPJ679Yr8HRiy1p8u9Nt5rvKlz3UeHXP7PqTQ/PC/nPp4AuaX30HB3xu4Eof8onWNm4nA==", "dependencies": { - "@amplitude/analytics-client-common": "^2.0.11", - "@amplitude/analytics-core": "^2.2.0", - "@amplitude/analytics-types": "^2.4.0", + "@amplitude/analytics-client-common": "^2.1.0", + "@amplitude/analytics-core": "^2.2.1", + "@amplitude/analytics-types": "^2.5.0", "tslib": "^2.4.1" } }, @@ -17907,15 +17907,15 @@ "dev": true }, "@amplitude/analytics-browser": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.4.1.tgz", - "integrity": "sha512-sxudfDAOZcyTJOHUc66uAHgZbZY9PORh4CAX4V66ozW+0vneeYPJm3ECCJqIrdJ/JWceOe/ohCT8G06X9s/4yg==", - "requires": { - "@amplitude/analytics-client-common": "^2.0.11", - "@amplitude/analytics-core": "^2.2.0", - "@amplitude/analytics-types": "^2.4.0", - "@amplitude/plugin-page-view-tracking-browser": "^2.1.1", - "@amplitude/plugin-web-attribution-browser": "^2.1.1", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.5.1.tgz", + "integrity": "sha512-WH31CyL5qPUSs7BY+udxDwWXmlBYfNJzCTwquWt/zUmd4aLJ7ec21rdN04RO4TCh68KpTGSb3l8K85J69fiVGw==", + "requires": { + "@amplitude/analytics-client-common": "^2.1.0", + "@amplitude/analytics-core": "^2.2.1", + "@amplitude/analytics-types": "^2.5.0", + "@amplitude/plugin-page-view-tracking-browser": "^2.2.1", + "@amplitude/plugin-web-attribution-browser": "^2.1.3", "tslib": "^2.4.1" }, "dependencies": { @@ -17927,13 +17927,13 @@ } }, "@amplitude/analytics-client-common": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.11.tgz", - "integrity": "sha512-f2zzo7Sk1hz8/WT7kPB6HZqAyHJrxMzw9nTqbLoRsaG9xm4YOJ0WCwtlu42RVRqIoWR89RTmIkqIjqimqMaHEQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.1.0.tgz", + "integrity": "sha512-wgZs7Orb3+ei7tBqxd5Ug3OzX19E0YpMkhHWFYesmpjBOW+Ng50rolyfB10rHjjeoMgiRc8eGDdnMH4+y7OQlA==", "requires": { "@amplitude/analytics-connector": "^1.4.8", - "@amplitude/analytics-core": "^2.2.0", - "@amplitude/analytics-types": "^2.4.0", + "@amplitude/analytics-core": "^2.2.1", + "@amplitude/analytics-types": "^2.5.0", "tslib": "^2.4.1" }, "dependencies": { @@ -17950,11 +17950,11 @@ "integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g==" }, "@amplitude/analytics-core": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.2.0.tgz", - "integrity": "sha512-JVx1chKa/sfqpBNEZn6jaZZV3DW/6cOoJxx8b5oqHIn+5HXizEOV85aLFY0rrtZ/uR/HrdffxrMKzr/uBFlV+A==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.2.1.tgz", + "integrity": "sha512-F4IrNzealRXnC3cPjkOoOEd68xsxuDsTeEGErAdxqWXNqV1x9oy1WM1lIKw2yGOyf6OZHidP/Vbp/BJK6B0Fmg==", "requires": { - "@amplitude/analytics-types": "^2.4.0", + "@amplitude/analytics-types": "^2.5.0", "tslib": "^2.4.1" }, "dependencies": { @@ -17966,17 +17966,17 @@ } }, "@amplitude/analytics-types": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-2.4.0.tgz", - "integrity": "sha512-GVj9W4X3XMVyGfqXdES2vFU8pqTIHvihj/vNOjOrwYHVdia3GjlcGl77GXuERCIwr52MoiUVTGXmXn3adf+A+Q==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-2.5.0.tgz", + "integrity": "sha512-aY69WxUvVlaCU+9geShjTsAYdUTvegEXH9i4WK/97kNbNLl4/7qUuIPe4hNireDeKLuQA9SA3H7TKynuNomDxw==" }, "@amplitude/plugin-page-view-tracking-browser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.1.1.tgz", - "integrity": "sha512-dZwA0kb/dvFTokF2yVklfjwl0Ben6g6Sukpr84tocpurJ7Ojhh1egISCZ4QWt2VgKKh7X4pyK73f5Fcp/1JcNw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.2.1.tgz", + "integrity": "sha512-KBB3nLFPns53haa4VaoWnomwxTX813mtoIYoq4pads+LjBvwUdgwZHsj/zEwYY7duolTUinxB9+Sx9lO6Ale2Q==", "requires": { - "@amplitude/analytics-client-common": "^2.0.11", - "@amplitude/analytics-types": "^2.4.0", + "@amplitude/analytics-client-common": "^2.1.0", + "@amplitude/analytics-types": "^2.5.0", "tslib": "^2.4.1" }, "dependencies": { @@ -17988,13 +17988,13 @@ } }, "@amplitude/plugin-web-attribution-browser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.1.1.tgz", - "integrity": "sha512-paWFk+uJeVlUF/HYNoczIxP3IJX/KR573B/E4Wp5bLB3XeJIhRbj3RQdJKhnKN5keg8+Vei/8uGdGQK4d3DYDg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.1.3.tgz", + "integrity": "sha512-CLTzgtewfgSLkwACkvPJ679Yr8HRiy1p8u9Nt5rvKlz3UeHXP7PqTQ/PC/nPp4AuaX30HB3xu4Eof8onWNm4nA==", "requires": { - "@amplitude/analytics-client-common": "^2.0.11", - "@amplitude/analytics-core": "^2.2.0", - "@amplitude/analytics-types": "^2.4.0", + "@amplitude/analytics-client-common": "^2.1.0", + "@amplitude/analytics-core": "^2.2.1", + "@amplitude/analytics-types": "^2.5.0", "tslib": "^2.4.1" }, "dependencies": { diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index e7edf532c30c..b006fdacca41 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^6.5.1", - "@amplitude/analytics-browser": "^2.4.1", + "@amplitude/analytics-browser": "^2.5.1", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", "assert": "^2.1.0", From 9e8bd5c56eb527f57803532959d56d21ac07f099 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 18:55:16 +0000 Subject: [PATCH 508/607] Bump eslint from 8.56.0 to 8.57.0 in /apps/block_scout_web/assets Bumps [eslint](https://github.com/eslint/eslint) from 8.56.0 to 8.57.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v8.56.0...v8.57.0) --- updated-dependencies: - dependency-name: eslint dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 66 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 615db5ef19c3..934a24f66656 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -78,7 +78,7 @@ "copy-webpack-plugin": "^12.0.2", "css-loader": "^6.10.0", "css-minimizer-webpack-plugin": "^6.0.0", - "eslint": "^8.56.0", + "eslint": "^8.57.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-node": "^11.1.0", @@ -2122,9 +2122,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2567,13 +2567,13 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -2594,9 +2594,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { @@ -7332,16 +7332,16 @@ } }, "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -19347,9 +19347,9 @@ } }, "@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true }, "@ethereumjs/common": { @@ -19598,13 +19598,13 @@ "integrity": "sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw==" }, "@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" } }, @@ -19615,9 +19615,9 @@ "dev": true }, "@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, "@istanbuljs/load-nyc-config": { @@ -23310,16 +23310,16 @@ } }, "eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index e7edf532c30c..9ad179b239fd 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -90,7 +90,7 @@ "copy-webpack-plugin": "^12.0.2", "css-loader": "^6.10.0", "css-minimizer-webpack-plugin": "^6.0.0", - "eslint": "^8.56.0", + "eslint": "^8.57.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-node": "^11.1.0", From f65fe3fb65ef2c8d3bcd739ccf2cdd8077c54587 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 19:01:15 +0000 Subject: [PATCH 509/607] Bump hammer from 6.2.0 to 6.2.1 Bumps [hammer](https://github.com/ExHammer/hammer) from 6.2.0 to 6.2.1. - [Changelog](https://github.com/ExHammer/hammer/blob/master/CHANGELOG.md) - [Commits](https://github.com/ExHammer/hammer/commits) --- updated-dependencies: - dependency-name: hammer dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 2ca4e78b1313..17bea5defe6a 100644 --- a/mix.lock +++ b/mix.lock @@ -65,7 +65,7 @@ "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~>2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, - "hammer": {:hex, :hammer, "6.2.0", "956e578f210ee67f7801caf7109b0e1145d2dad77ed5a0e5c0041a04739ede36", [:mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "1431a30e1f9c816e0fc58d2587de2d5f4c709b74bf81be77515dc902e35bb3a7"}, + "hammer": {:hex, :hammer, "6.2.1", "5ae9c33e3dceaeb42de0db46bf505bd9c35f259c8defb03390cd7556fea67ee2", [:mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b9476d0c13883d2dc0cc72e786bac6ac28911fba7cc2e04b70ce6a6d9c4b2bdc"}, "hammer_backend_redis": {:hex, :hammer_backend_redis, "6.1.2", "eb296bb4924928e24135308b2afc189201fd09411c870c6bbadea444a49b2f2c", [:mix], [{:hammer, "~> 6.0", [hex: :hammer, repo: "hexpm", optional: false]}, {:redix, "~> 1.1", [hex: :redix, repo: "hexpm", optional: false]}], "hexpm", "217ea066278910543a5e9b577d5bf2425419446b94fe76bdd9f255f39feec9fa"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"}, From 8673489a5fdd0c8134b9847b258e632f8ab0c619 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 19:07:06 +0000 Subject: [PATCH 510/607] Bump floki from 0.35.3 to 0.35.4 Bumps [floki](https://github.com/philss/floki) from 0.35.3 to 0.35.4. - [Release notes](https://github.com/philss/floki/releases) - [Changelog](https://github.com/philss/floki/blob/main/CHANGELOG.md) - [Commits](https://github.com/philss/floki/compare/v0.35.3...v0.35.4) --- updated-dependencies: - dependency-name: floki dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 2ca4e78b1313..754acd5568b6 100644 --- a/mix.lock +++ b/mix.lock @@ -60,7 +60,7 @@ "exvcr": {:hex, :exvcr, "0.15.1", "772db4d065f5136c6a984c302799a79e4ade3e52701c95425fa2229dd6426886", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.16", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "de4fc18b1d672d9b72bc7468735e19779aa50ea963a1f859ef82cd9e294b13e3"}, "file_info": {:hex, :file_info, "0.0.4", "2e0e77f211e833f38ead22cb29ce53761d457d80b3ffe0ffe0eb93880b0963b2", [:mix], [{:mimetype_parser, "~> 0.1.2", [hex: :mimetype_parser, repo: "hexpm", optional: false]}], "hexpm", "50e7ad01c2c8b9339010675fe4dc4a113b8d6ca7eddce24d1d74fd0e762781a5"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "floki": {:hex, :floki, "0.35.3", "0c8c6234aa71cb2b069cf801e8f8f30f8d096eb452c3dae2ccc409510ec32720", [:mix], [], "hexpm", "6d9f07f3fc76599f3b66c39f4a81ac62c8f4d9631140268db92aacad5d0e56d4"}, + "floki": {:hex, :floki, "0.35.4", "cc947b446024732c07274ac656600c5c4dc014caa1f8fb2dfff93d275b83890d", [:mix], [], "hexpm", "27fa185d3469bd8fc5947ef0f8d5c4e47f0af02eb6b070b63c868f69e3af0204"}, "flow": {:hex, :flow, "1.2.4", "1dd58918287eb286656008777cb32714b5123d3855956f29aa141ebae456922d", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "874adde96368e71870f3510b91e35bc31652291858c86c0e75359cbdd35eb211"}, "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, From 6d268c133f6c9997cf4eadcaf51b7e9cb8875df1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:43:01 +0000 Subject: [PATCH 511/607] Bump es5-ext from 0.10.62 to 0.10.64 in /apps/block_scout_web/assets Bumps [es5-ext](https://github.com/medikoo/es5-ext) from 0.10.62 to 0.10.64. - [Release notes](https://github.com/medikoo/es5-ext/releases) - [Changelog](https://github.com/medikoo/es5-ext/blob/main/CHANGELOG.md) - [Commits](https://github.com/medikoo/es5-ext/compare/v0.10.62...v0.10.64) --- updated-dependencies: - dependency-name: es5-ext dependency-type: indirect ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 69 +++++++++++++++++-- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 615db5ef19c3..3edad2858f35 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -7191,13 +7191,14 @@ } }, "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "hasInstallScript": true, "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", "next-tick": "^1.1.0" }, "engines": { @@ -7911,6 +7912,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esniff/node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -8501,6 +8521,15 @@ "npm": ">=3" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/eventemitter3": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", @@ -23201,12 +23230,13 @@ } }, "es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "requires": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, @@ -23709,6 +23739,24 @@ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true }, + "esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "dependencies": { + "type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + } + } + }, "espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -24272,6 +24320,15 @@ "strip-hex-prefix": "1.0.0" } }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "eventemitter3": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", From 6a73ca8e9c1430918c8f2126615a4732fa91b212 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 28 Feb 2024 19:37:23 +0300 Subject: [PATCH 512/607] CHAIN_TYPE=ethereum for ETH chains --- .github/workflows/publish-docker-image-for-eth-goerli.yml | 1 + .github/workflows/publish-docker-image-for-eth-sepolia.yml | 1 + .github/workflows/publish-docker-image-for-eth.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/publish-docker-image-for-eth-goerli.yml b/.github/workflows/publish-docker-image-for-eth-goerli.yml index 5fc153cf9e41..262802e27ec2 100644 --- a/.github/workflows/publish-docker-image-for-eth-goerli.yml +++ b/.github/workflows/publish-docker-image-for-eth-goerli.yml @@ -28,6 +28,7 @@ jobs: push: true tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:latest, blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} build-args: | + CHAIN_TYPE=ethereum CACHE_EXCHANGE_RATES_PERIOD= API_V1_READ_METHODS_DISABLED=false DISABLE_WEBAPP=false diff --git a/.github/workflows/publish-docker-image-for-eth-sepolia.yml b/.github/workflows/publish-docker-image-for-eth-sepolia.yml index e476ad5f47a4..d63d935df4a6 100644 --- a/.github/workflows/publish-docker-image-for-eth-sepolia.yml +++ b/.github/workflows/publish-docker-image-for-eth-sepolia.yml @@ -28,6 +28,7 @@ jobs: push: true tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:latest, blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} build-args: | + CHAIN_TYPE=ethereum CACHE_EXCHANGE_RATES_PERIOD= API_V1_READ_METHODS_DISABLED=false DISABLE_WEBAPP=false diff --git a/.github/workflows/publish-docker-image-for-eth.yml b/.github/workflows/publish-docker-image-for-eth.yml index 02635daa56c7..3e3bed50190d 100644 --- a/.github/workflows/publish-docker-image-for-eth.yml +++ b/.github/workflows/publish-docker-image-for-eth.yml @@ -28,6 +28,7 @@ jobs: push: true tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-experimental build-args: | + CHAIN_TYPE=ethereum CACHE_EXCHANGE_RATES_PERIOD= API_V1_READ_METHODS_DISABLED=false DISABLE_WEBAPP=false From a59e92b2c0e1375067586e76d71065d0fd0fbb5d Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 28 Feb 2024 19:46:28 +0300 Subject: [PATCH 513/607] Release workflow for ethereum chain type --- .github/workflows/release-eth.yml | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/release-eth.yml diff --git a/.github/workflows/release-eth.yml b/.github/workflows/release-eth.yml new file mode 100644 index 000000000000..1f10e2a85829 --- /dev/null +++ b/.github/workflows/release-eth.yml @@ -0,0 +1,45 @@ +name: Release additional + +on: + release: + types: [published] + +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Docker image for Ethereum + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-ethereum:latest, blockscout/blockscout-ethereum:${{ env.RELEASE_VERSION }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=ethereum \ No newline at end of file From 1e08a2d236fe9861e9f2b3f2b9e8c692c118af89 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 28 Feb 2024 21:43:46 +0300 Subject: [PATCH 514/607] workflows for zetachain --- .../publish-docker-image-for-zetachain.yml | 40 +++++++++++++++++ .github/workflows/release-eth.yml | 2 +- .github/workflows/release-zetachain.yml | 45 +++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/publish-docker-image-for-zetachain.yml create mode 100644 .github/workflows/release-zetachain.yml diff --git a/.github/workflows/publish-docker-image-for-zetachain.yml b/.github/workflows/publish-docker-image-for-zetachain.yml new file mode 100644 index 000000000000..0abd04fe2cca --- /dev/null +++ b/.github/workflows/publish-docker-image-for-zetachain.yml @@ -0,0 +1,40 @@ +name: Zetachain publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-zetachain +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + DOCKER_CHAIN_NAME: zetachain + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo-and-short-sha + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zetachain \ No newline at end of file diff --git a/.github/workflows/release-eth.yml b/.github/workflows/release-eth.yml index 1f10e2a85829..b6d1b6743e49 100644 --- a/.github/workflows/release-eth.yml +++ b/.github/workflows/release-eth.yml @@ -1,4 +1,4 @@ -name: Release additional +name: Release for Ethereum on: release: diff --git a/.github/workflows/release-zetachain.yml b/.github/workflows/release-zetachain.yml new file mode 100644 index 000000000000..731fa310e856 --- /dev/null +++ b/.github/workflows/release-zetachain.yml @@ -0,0 +1,45 @@ +name: Release for Zetachain + +on: + release: + types: [published] + +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Docker image for Ethereum + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zetachain:latest, blockscout/blockscout-zetachain:${{ env.RELEASE_VERSION }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zetachain \ No newline at end of file From d468ac8168f2051bdf5ce130bf201b91a56e6e0a Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Thu, 29 Feb 2024 10:17:55 +0400 Subject: [PATCH 515/607] Add batch_size and concurrency envs for tt token type migration --- CHANGELOG.md | 2 ++ config/runtime.exs | 4 ++++ docker-compose/envs/common-blockscout.env | 2 ++ 3 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94a70192b09e..2ce5b20857dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Fixes +- [#9502](https://github.com/blockscout/blockscout/pull/9502) - Add batch_size and concurrency envs for tt token type migration + ### Chore
diff --git a/config/runtime.exs b/config/runtime.exs index 729747fe2b4d..dae0d9293661 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -486,6 +486,10 @@ config :explorer, Explorer.Migrator.TransactionsDenormalization, batch_size: ConfigHelper.parse_integer_env_var("DENORMALIZATION_MIGRATION_BATCH_SIZE", 500), concurrency: ConfigHelper.parse_integer_env_var("DENORMALIZATION_MIGRATION_CONCURRENCY", 10) +config :explorer, Explorer.Migrator.TokenTransferTokenType, + batch_size: ConfigHelper.parse_integer_env_var("TOKEN_TRANSFER_TOKEN_TYPE_MIGRATION_BATCH_SIZE", 100), + concurrency: ConfigHelper.parse_integer_env_var("TOKEN_TRANSFER_TOKEN_TYPE_MIGRATION_CONCURRENCY", 1) + config :explorer, Explorer.Chain.BridgedToken, eth_omni_bridge_mediator: System.get_env("BRIDGED_TOKENS_ETH_OMNI_BRIDGE_MEDIATOR"), bsc_omni_bridge_mediator: System.get_env("BRIDGED_TOKENS_BSC_OMNI_BRIDGE_MEDIATOR"), diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 53ed9eed2f3b..f3442255d064 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -295,6 +295,8 @@ EIP_1559_ELASTICITY_MULTIPLIER=2 # ADDRESSES_TABS_COUNTERS_TTL=10m # DENORMALIZATION_MIGRATION_BATCH_SIZE= # DENORMALIZATION_MIGRATION_CONCURRENCY= +# TOKEN_TRANSFER_TOKEN_TYPE_MIGRATION_BATCH_SIZE= +# TOKEN_TRANSFER_TOKEN_TYPE_MIGRATION_CONCURRENCY= SOURCIFY_INTEGRATION_ENABLED=false SOURCIFY_SERVER_URL= SOURCIFY_REPO_URL= From 914d426b3e6a3ff7d2869beba4885441f37b59f5 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 29 Feb 2024 09:47:23 +0300 Subject: [PATCH 516/607] Configure CI for Filecoin --- .github/workflows/release-filecoin.yml | 45 +++++++++++++++++++++++++ .github/workflows/release-zetachain.yml | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release-filecoin.yml diff --git a/.github/workflows/release-filecoin.yml b/.github/workflows/release-filecoin.yml new file mode 100644 index 000000000000..d8b77901a75d --- /dev/null +++ b/.github/workflows/release-filecoin.yml @@ -0,0 +1,45 @@ +name: Release for Filecoin + +on: + release: + types: [published] + +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Docker image for Filecoin + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-filecoin:latest, blockscout/blockscout-filecoin:${{ env.RELEASE_VERSION }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=filecoin \ No newline at end of file diff --git a/.github/workflows/release-zetachain.yml b/.github/workflows/release-zetachain.yml index 731fa310e856..cd9c5abc14c9 100644 --- a/.github/workflows/release-zetachain.yml +++ b/.github/workflows/release-zetachain.yml @@ -22,7 +22,7 @@ jobs: docker-username: ${{ secrets.DOCKER_USERNAME }} docker-password: ${{ secrets.DOCKER_PASSWORD }} - - name: Build and push Docker image for Ethereum + - name: Build and push Docker image for Zetachain uses: docker/build-push-action@v5 with: context: . From 05d47d681ccb1c92d83a578f7f3e4556a168aa0a Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 27 Feb 2024 18:51:36 -0500 Subject: [PATCH 517/607] fix: not found page for unknown blobs --- .../controllers/api/v2/blob_controller.ex | 22 ++++++++++--------- .../block_scout_web/views/api/v2/blob_view.ex | 4 ---- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex index 724b4b8e9bbf..f72a6424a7c3 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex @@ -2,7 +2,7 @@ defmodule BlockScoutWeb.API.V2.BlobController do use BlockScoutWeb, :controller alias Explorer.Chain - alias Explorer.Chain.Beacon.Reader + alias Explorer.Chain.Beacon.{Blob, Reader} action_fallback(BlockScoutWeb.API.V2.FallbackController) @@ -14,16 +14,18 @@ defmodule BlockScoutWeb.API.V2.BlobController do with {:format, {:ok, blob_hash}} <- {:format, Chain.string_to_transaction_hash(blob_hash_string)} do transaction_hashes = Reader.blob_hash_to_transactions(blob_hash, api?: true) - case Reader.blob(blob_hash, true, api?: true) do - {:ok, blob} -> - conn - |> put_status(200) - |> render(:blob, %{blob: blob, transaction_hashes: transaction_hashes}) + {status, blob} = + case Reader.blob(blob_hash, true, api?: true) do + {:ok, blob} -> {:ok, blob} + {:error, :not_found} -> {:pending, %Blob{hash: blob_hash}} + end - {:error, :not_found} -> - conn - |> put_status(200) - |> render(:blob, %{transaction_hashes: transaction_hashes}) + if Enum.empty?(transaction_hashes) and status == :pending do + {:error, :not_found} + else + conn + |> put_status(200) + |> render(:blob, %{blob: blob, transaction_hashes: transaction_hashes}) end end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/blob_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/blob_view.ex index 680fa5f04604..fcb792ac095e 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/blob_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/blob_view.ex @@ -7,10 +7,6 @@ defmodule BlockScoutWeb.API.V2.BlobView do blob |> prepare_blob() |> Map.put("transaction_hashes", transaction_hashes) end - def render("blob.json", %{transaction_hashes: transaction_hashes}) do - %{"transaction_hashes" => transaction_hashes} - end - def render("blobs.json", %{blobs: blobs}) do %{"items" => Enum.map(blobs, &prepare_blob(&1))} end From 20f1f70395977699c1df35d96e1e1cc3a4e2ba42 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 27 Feb 2024 18:54:51 -0500 Subject: [PATCH 518/607] chore: changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ce5b20857dc..c07548d592fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Fixes - [#9502](https://github.com/blockscout/blockscout/pull/9502) - Add batch_size and concurrency envs for tt token type migration +- [#9493](https://github.com/blockscout/blockscout/pull/9493) - Fix API response for unknown blob hashes ### Chore From ca40f79afc0d677003696d3ba0b0921fc8b84d43 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Tue, 20 Feb 2024 14:46:18 +0300 Subject: [PATCH 519/607] Fix tabs counter cache bug --- .../lib/explorer/chain/address/counters.ex | 2 +- .../chain/cache/addresses_tabs_counters.ex | 30 ++++++++----------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/address/counters.ex b/apps/explorer/lib/explorer/chain/address/counters.ex index 9f0aea47d855..25cb152caf88 100644 --- a/apps/explorer/lib/explorer/chain/address/counters.ex +++ b/apps/explorer/lib/explorer/chain/address/counters.ex @@ -483,7 +483,7 @@ defmodule Explorer.Chain.Address.Counters do case res do {:ok, {txs_type, txs_hashes}} when txs_type in @txs_types -> acc - |> (&Map.put(&1, :txs_types, [txs_type | &1[:txs_types] || []])).() + |> (&Map.put(&1, :txs_types, [txs_type | &1[:txs_types]])).() |> (&Map.put(&1, :txs_hashes, &1[:txs_hashes] ++ txs_hashes)).() {:ok, {type, counter}} -> diff --git a/apps/explorer/lib/explorer/chain/cache/addresses_tabs_counters.ex b/apps/explorer/lib/explorer/chain/cache/addresses_tabs_counters.ex index 750617da62c9..20d30199629a 100644 --- a/apps/explorer/lib/explorer/chain/cache/addresses_tabs_counters.ex +++ b/apps/explorer/lib/explorer/chain/cache/addresses_tabs_counters.ex @@ -20,9 +20,8 @@ defmodule Explorer.Chain.Cache.AddressesTabsCounters do end @spec set_counter(counter_type, String.t(), non_neg_integer()) :: :ok - def set_counter(counter_type, address_hash, counter, need_to_modify_state? \\ true) do + def set_counter(counter_type, address_hash, counter) do :ets.insert(@cache_name, {cache_key(address_hash, counter_type), {DateTime.utc_now(), counter}}) - if need_to_modify_state?, do: ignore_txs(counter_type, address_hash) :ok end @@ -42,10 +41,6 @@ defmodule Explorer.Chain.Cache.AddressesTabsCounters do address_hash |> task_cache_key(counter_type) |> fetch_from_cache(@cache_name, nil) end - @spec ignore_txs(atom, String.t()) :: :ignore | :ok - def ignore_txs(:txs, address_hash), do: GenServer.cast(__MODULE__, {:ignore_txs, address_hash}) - def ignore_txs(_counter_type, _address_hash), do: :ignore - def save_txs_counter_progress(address_hash, results) do GenServer.cast(__MODULE__, {:set_txs_state, address_hash, results}) end @@ -67,11 +62,6 @@ defmodule Explorer.Chain.Cache.AddressesTabsCounters do {:ok, %{}} end - @impl true - def handle_cast({:ignore_txs, address_hash}, state) do - {:noreply, Map.put(state, lowercased_string(address_hash), {:updated, DateTime.utc_now()})} - end - @impl true def handle_cast({:set_txs_state, address_hash, %{txs_types: txs_types} = results}, state) do address_hash = lowercased_string(address_hash) @@ -95,16 +85,22 @@ defmodule Explorer.Chain.Cache.AddressesTabsCounters do |> Enum.count() |> min(Counters.counters_limit()) - if counter == Counters.counters_limit() || Enum.count(address_state[:txs_types]) == 3 do - set_counter(:txs, address_hash, counter, false) - {:noreply, Map.put(state, address_hash, {:updated, DateTime.utc_now()})} - else - {:noreply, Map.put(state, address_hash, address_state)} + cond do + Enum.count(address_state[:txs_types]) == 3 -> + set_counter(:txs, address_hash, counter) + {:noreply, Map.put(state, address_hash, nil)} + + counter == Counters.counters_limit() -> + set_counter(:txs, address_hash, counter) + {:noreply, Map.put(state, address_hash, :limit_value)} + + true -> + {:noreply, Map.put(state, address_hash, address_state)} end end end - defp ignored?({:updated, datetime}), do: up_to_date?(datetime, ttl()) + defp ignored?(:limit_value), do: true defp ignored?(_), do: false defp check_staleness(nil), do: nil From d69352919a61678b77f5969d306ded277ea73ec4 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Tue, 20 Feb 2024 14:48:06 +0300 Subject: [PATCH 520/607] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c07548d592fe..1ac9998300f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - [#9502](https://github.com/blockscout/blockscout/pull/9502) - Add batch_size and concurrency envs for tt token type migration - [#9493](https://github.com/blockscout/blockscout/pull/9493) - Fix API response for unknown blob hashes +- [#9426](https://github.com/blockscout/blockscout/pull/9426) - Fix tabs counter cache bug ### Chore From 5b3419a264d760cfa52a12aa5fee0b5456811b49 Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Thu, 29 Feb 2024 10:05:45 +0300 Subject: [PATCH 521/607] Fix no function clause matching in Integer.parse/2 (#9484) * Fix no function clause matching in Integer.parse/2 * Changelog --- CHANGELOG.md | 1 + apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ac9998300f6..f90e4ed2784f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - [#9502](https://github.com/blockscout/blockscout/pull/9502) - Add batch_size and concurrency envs for tt token type migration - [#9493](https://github.com/blockscout/blockscout/pull/9493) - Fix API response for unknown blob hashes +- [#9484](https://github.com/blockscout/blockscout/pull/9484) - Fix read contract error - [#9426](https://github.com/blockscout/blockscout/pull/9426) - Fix tabs counter cache bug ### Chore diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex index 729b218200ae..64ed49911d59 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex @@ -133,9 +133,13 @@ defmodule EthereumJSONRPC.Contract do defp convert_int_string_to_array_inner(arg) do arg - |> Enum.map(fn el -> - {int, _} = Integer.parse(el) - int + |> Enum.map(fn + el when is_integer(el) -> + el + + el -> + {int, _} = Integer.parse(el) + int end) end From 017456bed2bbe2ac1df1ec809a576c15834fbddb Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 29 Feb 2024 11:24:58 +0300 Subject: [PATCH 522/607] 6.2.1 --- CHANGELOG.md | 20 ++++++++++++++++++++ apps/block_scout_web/mix.exs | 2 +- apps/ethereum_jsonrpc/mix.exs | 2 +- apps/explorer/mix.exs | 2 +- apps/indexer/mix.exs | 2 +- docker-compose/docker-compose.yml | 2 +- docker/Makefile | 2 +- mix.exs | 2 +- rel/config.exs | 2 +- 9 files changed, 28 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f90e4ed2784f..394412412b35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ ### Fixes +### Chore + +
+ Dependencies version bumps + +
+ +## 6.2.1 + +### Features + +### Fixes + - [#9502](https://github.com/blockscout/blockscout/pull/9502) - Add batch_size and concurrency envs for tt token type migration - [#9493](https://github.com/blockscout/blockscout/pull/9493) - Fix API response for unknown blob hashes - [#9484](https://github.com/blockscout/blockscout/pull/9484) - Fix read contract error @@ -16,6 +29,13 @@
Dependencies version bumps +- [#9478](https://github.com/blockscout/blockscout/pull/9478) - Bump floki from 0.35.3 to 0.35.4 +- [#9477](https://github.com/blockscout/blockscout/pull/9477) - Bump hammer from 6.2.0 to 6.2.1 +- [#9476](https://github.com/blockscout/blockscout/pull/9476) - Bump eslint from 8.56.0 to 8.57.0 in /apps/block_scout_web/assets +- [#9475](https://github.com/blockscout/blockscout/pull/9475) - Bump @amplitude/analytics-browser from 2.4.1 to 2.5.1 in /apps/block_scout_web/assets +- [#9474](https://github.com/blockscout/blockscout/pull/9474) - Bump sass from 1.71.0 to 1.71.1 in /apps/block_scout_web/assets +- [#9492](https://github.com/blockscout/blockscout/pull/9492) - Bump es5-ext from 0.10.62 to 0.10.64 in /apps/block_scout_web/assets +
## 6.2.0 diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index 81be6caa8eb6..920fa886878e 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -23,7 +23,7 @@ defmodule BlockScoutWeb.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.2.0", + version: "6.2.1", xref: [exclude: [Explorer.Chain.PolygonZkevm.Reader, Explorer.Chain.Beacon.Reader]] ] end diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index c6dafe238226..ac1e13355320 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -23,7 +23,7 @@ defmodule EthereumJsonrpc.MixProject do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.2.0" + version: "6.2.1" ] end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index aad16f8467f0..f1631e319187 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -24,7 +24,7 @@ defmodule Explorer.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.2.0", + version: "6.2.1", xref: [exclude: [BlockScoutWeb.WebRouter.Helpers, Indexer.Helper]] ] end diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs index 0f1a39815391..afb7ebe20d0e 100644 --- a/apps/indexer/mix.exs +++ b/apps/indexer/mix.exs @@ -14,7 +14,7 @@ defmodule Indexer.MixProject do elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", start_permanent: Mix.env() == :prod, - version: "6.2.0" + version: "6.2.1" ] end diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index a62c1024b3de..30df6ef03622 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -34,7 +34,7 @@ services: CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED: "" CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL: "" ADMIN_PANEL_ENABLED: "" - RELEASE_VERSION: 6.2.0 + RELEASE_VERSION: 6.2.1 links: - db:database environment: diff --git a/docker/Makefile b/docker/Makefile index b7521d41ad9d..53f7a4e57ee1 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -10,7 +10,7 @@ STATS_CONTAINER_NAME := stats STATS_DB_CONTAINER_NAME := stats-db PROXY_CONTAINER_NAME := proxy PG_CONTAINER_NAME := postgres -RELEASE_VERSION ?= '6.2.0' +RELEASE_VERSION ?= '6.2.1' TAG := $(RELEASE_VERSION)-commit-$(shell git log -1 --pretty=format:"%h") STABLE_TAG := $(RELEASE_VERSION) diff --git a/mix.exs b/mix.exs index 8b59a29aeb0e..9b45a7d5ba1b 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule BlockScout.Mixfile do [ # app: :block_scout, # aliases: aliases(config_env()), - version: "6.2.0", + version: "6.2.1", apps_path: "apps", deps: deps(), dialyzer: dialyzer(), diff --git a/rel/config.exs b/rel/config.exs index ee8931e915d2..8c383ac2b7b6 100644 --- a/rel/config.exs +++ b/rel/config.exs @@ -71,7 +71,7 @@ end # will be used by default release :blockscout do - set version: "6.2.0-beta" + set version: "6.2.1-beta" set applications: [ :runtime_tools, block_scout_web: :permanent, From 4322e81d52d4a226ae02c5fbbeec93946fd83dc2 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Thu, 29 Feb 2024 16:09:32 +0400 Subject: [PATCH 523/607] Add env vars for NFT sanitize migration --- CHANGELOG.md | 2 ++ config/runtime.exs | 4 ++++ docker-compose/envs/common-blockscout.env | 2 ++ 3 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 394412412b35..395b577d79fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Fixes +- [#9505](https://github.com/blockscout/blockscout/pull/9505) - Add env vars for NFT sanitize migration + ### Chore
diff --git a/config/runtime.exs b/config/runtime.exs index dae0d9293661..3b7ec8e420a6 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -490,6 +490,10 @@ config :explorer, Explorer.Migrator.TokenTransferTokenType, batch_size: ConfigHelper.parse_integer_env_var("TOKEN_TRANSFER_TOKEN_TYPE_MIGRATION_BATCH_SIZE", 100), concurrency: ConfigHelper.parse_integer_env_var("TOKEN_TRANSFER_TOKEN_TYPE_MIGRATION_CONCURRENCY", 1) +config :explorer, Explorer.Migrator.SanitizeIncorrectNFTTokenTransfers, + batch_size: ConfigHelper.parse_integer_env_var("SANITIZE_INCORRECT_NFT_BATCH_SIZE", 100), + concurrency: ConfigHelper.parse_integer_env_var("SANITIZE_INCORRECT_NFT_CONCURRENCY", 1) + config :explorer, Explorer.Chain.BridgedToken, eth_omni_bridge_mediator: System.get_env("BRIDGED_TOKENS_ETH_OMNI_BRIDGE_MEDIATOR"), bsc_omni_bridge_mediator: System.get_env("BRIDGED_TOKENS_BSC_OMNI_BRIDGE_MEDIATOR"), diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index f3442255d064..dff3a43c5091 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -297,6 +297,8 @@ EIP_1559_ELASTICITY_MULTIPLIER=2 # DENORMALIZATION_MIGRATION_CONCURRENCY= # TOKEN_TRANSFER_TOKEN_TYPE_MIGRATION_BATCH_SIZE= # TOKEN_TRANSFER_TOKEN_TYPE_MIGRATION_CONCURRENCY= +# SANITIZE_INCORRECT_NFT_BATCH_SIZE= +# SANITIZE_INCORRECT_NFT_CONCURRENCY= SOURCIFY_INTEGRATION_ENABLED=false SOURCIFY_SERVER_URL= SOURCIFY_REPO_URL= From ee8268787e3d2564f92a4663b33cb481e333c9a6 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 29 Feb 2024 16:03:09 +0300 Subject: [PATCH 524/607] 6.2.2 --- CHANGELOG.md | 8 ++++++++ apps/block_scout_web/mix.exs | 2 +- apps/ethereum_jsonrpc/mix.exs | 2 +- apps/explorer/mix.exs | 2 +- apps/indexer/mix.exs | 2 +- docker-compose/docker-compose.yml | 2 +- docker/Makefile | 2 +- mix.exs | 2 +- rel/config.exs | 2 +- 9 files changed, 16 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 395b577d79fc..c01d44b81bcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ ### Fixes +### Chore + +## 6.2.2 + +### Features + +### Fixes + - [#9505](https://github.com/blockscout/blockscout/pull/9505) - Add env vars for NFT sanitize migration ### Chore diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index 920fa886878e..56d6d8b25a7a 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -23,7 +23,7 @@ defmodule BlockScoutWeb.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.2.1", + version: "6.2.2", xref: [exclude: [Explorer.Chain.PolygonZkevm.Reader, Explorer.Chain.Beacon.Reader]] ] end diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index ac1e13355320..48847ad2d33c 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -23,7 +23,7 @@ defmodule EthereumJsonrpc.MixProject do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.2.1" + version: "6.2.2" ] end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index f1631e319187..68edc0796dca 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -24,7 +24,7 @@ defmodule Explorer.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.2.1", + version: "6.2.2", xref: [exclude: [BlockScoutWeb.WebRouter.Helpers, Indexer.Helper]] ] end diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs index afb7ebe20d0e..ae4e6323837e 100644 --- a/apps/indexer/mix.exs +++ b/apps/indexer/mix.exs @@ -14,7 +14,7 @@ defmodule Indexer.MixProject do elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", start_permanent: Mix.env() == :prod, - version: "6.2.1" + version: "6.2.2" ] end diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 30df6ef03622..ce5b3edde5ad 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -34,7 +34,7 @@ services: CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED: "" CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL: "" ADMIN_PANEL_ENABLED: "" - RELEASE_VERSION: 6.2.1 + RELEASE_VERSION: 6.2.2 links: - db:database environment: diff --git a/docker/Makefile b/docker/Makefile index 53f7a4e57ee1..da8770a93451 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -10,7 +10,7 @@ STATS_CONTAINER_NAME := stats STATS_DB_CONTAINER_NAME := stats-db PROXY_CONTAINER_NAME := proxy PG_CONTAINER_NAME := postgres -RELEASE_VERSION ?= '6.2.1' +RELEASE_VERSION ?= '6.2.2' TAG := $(RELEASE_VERSION)-commit-$(shell git log -1 --pretty=format:"%h") STABLE_TAG := $(RELEASE_VERSION) diff --git a/mix.exs b/mix.exs index 9b45a7d5ba1b..70de7c9eab60 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule BlockScout.Mixfile do [ # app: :block_scout, # aliases: aliases(config_env()), - version: "6.2.1", + version: "6.2.2", apps_path: "apps", deps: deps(), dialyzer: dialyzer(), diff --git a/rel/config.exs b/rel/config.exs index 8c383ac2b7b6..866dbde4644b 100644 --- a/rel/config.exs +++ b/rel/config.exs @@ -71,7 +71,7 @@ end # will be used by default release :blockscout do - set version: "6.2.1-beta" + set version: "6.2.2-beta" set applications: [ :runtime_tools, block_scout_web: :permanent, From 2c52af23401ddff62126aa0859d0495ffc5fa9d7 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 1 Mar 2024 14:09:57 +0300 Subject: [PATCH 525/607] Docker-compose 2.24.6 compatibility --- CHANGELOG.md | 2 ++ docker-compose/docker-compose.yml | 7 ++++++- docker-compose/erigon.yml | 7 ++++++- docker-compose/external-backend.yml | 7 +++++++ docker-compose/external-db.yml | 4 +++- docker-compose/external-frontend.yml | 7 ++++++- docker-compose/ganache.yml | 7 ++++++- docker-compose/geth-clique-consensus.yml | 7 ++++++- docker-compose/geth.yml | 7 ++++++- docker-compose/hardhat-network.yml | 7 ++++++- docker-compose/microservices.yml | 4 ++++ docker-compose/services/db.yml | 3 --- docker-compose/services/stats.yml | 5 ----- 13 files changed, 58 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c01d44b81bcb..be667a9e5d40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Fixes +- [#9512](https://github.com/blockscout/blockscout/pull/9512) - Docker-compose 2.24.6 compatibility + ### Chore ## 6.2.2 diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index ce5b3edde5ad..32098e476758 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -12,6 +12,9 @@ services: service: db-init db: + depends_on: + db-init: + condition: service_completed_successfully extends: file: ./services/db.yml service: db @@ -67,7 +70,8 @@ services: stats-db: depends_on: - - backend + stats-db-init: + condition: service_completed_successfully extends: file: ./services/stats.yml service: stats-db @@ -75,6 +79,7 @@ services: stats: depends_on: - stats-db + - backend extends: file: ./services/stats.yml service: stats diff --git a/docker-compose/erigon.yml b/docker-compose/erigon.yml index dea8047f4666..231d2f92eb78 100644 --- a/docker-compose/erigon.yml +++ b/docker-compose/erigon.yml @@ -12,6 +12,9 @@ services: service: db-init db: + depends_on: + db-init: + condition: service_completed_successfully extends: file: ./services/db.yml service: db @@ -52,7 +55,8 @@ services: stats-db: depends_on: - - backend + stats-db-init: + condition: service_completed_successfully extends: file: ./services/stats.yml service: stats-db @@ -60,6 +64,7 @@ services: stats: depends_on: - stats-db + - backend extends: file: ./services/stats.yml service: stats diff --git a/docker-compose/external-backend.yml b/docker-compose/external-backend.yml index 37cd5783652b..cda9d8361fe3 100644 --- a/docker-compose/external-backend.yml +++ b/docker-compose/external-backend.yml @@ -12,6 +12,9 @@ services: service: db-init db: + depends_on: + db-init: + condition: service_completed_successfully extends: file: ./services/db.yml service: db @@ -37,6 +40,9 @@ services: service: stats-db-init stats-db: + depends_on: + stats-db-init: + condition: service_completed_successfully extends: file: ./services/stats.yml service: stats-db @@ -44,6 +50,7 @@ services: stats: depends_on: - stats-db + - backend extends: file: ./services/stats.yml service: stats diff --git a/docker-compose/external-db.yml b/docker-compose/external-db.yml index bd4ec6f069f0..f1172c29f83f 100644 --- a/docker-compose/external-db.yml +++ b/docker-compose/external-db.yml @@ -39,7 +39,8 @@ services: stats-db: depends_on: - - backend + stats-db-init: + condition: service_completed_successfully extends: file: ./services/stats.yml service: stats-db @@ -47,6 +48,7 @@ services: stats: depends_on: - stats-db + - backend extends: file: ./services/stats.yml service: stats diff --git a/docker-compose/external-frontend.yml b/docker-compose/external-frontend.yml index bad65c4afa8c..6f3fbee2910d 100644 --- a/docker-compose/external-frontend.yml +++ b/docker-compose/external-frontend.yml @@ -12,6 +12,9 @@ services: service: db-init db: + depends_on: + db-init: + condition: service_completed_successfully extends: file: ./services/db.yml service: db @@ -49,7 +52,8 @@ services: stats-db: depends_on: - - backend + stats-db-init: + condition: service_completed_successfully extends: file: ./services/stats.yml service: stats-db @@ -57,6 +61,7 @@ services: stats: depends_on: - stats-db + - backend extends: file: ./services/stats.yml service: stats diff --git a/docker-compose/ganache.yml b/docker-compose/ganache.yml index dce2aed9c1e6..136d5b25d2ad 100644 --- a/docker-compose/ganache.yml +++ b/docker-compose/ganache.yml @@ -12,6 +12,9 @@ services: service: db-init db: + depends_on: + db-init: + condition: service_completed_successfully extends: file: ./services/db.yml service: db @@ -59,7 +62,8 @@ services: stats-db: depends_on: - - backend + stats-db-init: + condition: service_completed_successfully extends: file: ./services/stats.yml service: stats-db @@ -67,6 +71,7 @@ services: stats: depends_on: - stats-db + - backend extends: file: ./services/stats.yml service: stats diff --git a/docker-compose/geth-clique-consensus.yml b/docker-compose/geth-clique-consensus.yml index 27fa83563529..8d42b273d2d0 100644 --- a/docker-compose/geth-clique-consensus.yml +++ b/docker-compose/geth-clique-consensus.yml @@ -12,6 +12,9 @@ services: service: db-init db: + depends_on: + db-init: + condition: service_completed_successfully extends: file: ./services/db.yml service: db @@ -53,7 +56,8 @@ services: stats-db: depends_on: - - backend + stats-db-init: + condition: service_completed_successfully extends: file: ./services/stats.yml service: stats-db @@ -61,6 +65,7 @@ services: stats: depends_on: - stats-db + - backend extends: file: ./services/stats.yml service: stats diff --git a/docker-compose/geth.yml b/docker-compose/geth.yml index 0e55eb33d742..366630ce5296 100644 --- a/docker-compose/geth.yml +++ b/docker-compose/geth.yml @@ -12,6 +12,9 @@ services: service: db-init db: + depends_on: + db-init: + condition: service_completed_successfully extends: file: ./services/db.yml service: db @@ -52,7 +55,8 @@ services: stats-db: depends_on: - - backend + stats-db-init: + condition: service_completed_successfully extends: file: ./services/stats.yml service: stats-db @@ -60,6 +64,7 @@ services: stats: depends_on: - stats-db + - backend extends: file: ./services/stats.yml service: stats diff --git a/docker-compose/hardhat-network.yml b/docker-compose/hardhat-network.yml index 74c29218f720..1a014c72f713 100644 --- a/docker-compose/hardhat-network.yml +++ b/docker-compose/hardhat-network.yml @@ -12,6 +12,9 @@ services: service: db-init db: + depends_on: + db-init: + condition: service_completed_successfully extends: file: ./services/db.yml service: db @@ -59,7 +62,8 @@ services: stats-db: depends_on: - - backend + stats-db-init: + condition: service_completed_successfully extends: file: ./services/stats.yml service: stats-db @@ -67,6 +71,7 @@ services: stats: depends_on: - stats-db + - backend extends: file: ./services/stats.yml service: stats diff --git a/docker-compose/microservices.yml b/docker-compose/microservices.yml index ee4a07bd6d1d..38735e73437a 100644 --- a/docker-compose/microservices.yml +++ b/docker-compose/microservices.yml @@ -27,6 +27,9 @@ services: service: stats-db-init stats-db: + depends_on: + stats-db-init: + condition: service_completed_successfully extends: file: ./services/stats.yml service: stats-db @@ -34,6 +37,7 @@ services: stats: depends_on: - stats-db + - backend extends: file: ./services/stats.yml service: stats diff --git a/docker-compose/services/db.yml b/docker-compose/services/db.yml index a7370d6b1531..430409bbecfe 100644 --- a/docker-compose/services/db.yml +++ b/docker-compose/services/db.yml @@ -12,9 +12,6 @@ services: chown -R 2000:2000 /var/lib/postgresql/data db: - depends_on: - db-init: - condition: service_completed_successfully image: postgres:15 user: 2000:2000 shm_size: 256m diff --git a/docker-compose/services/stats.yml b/docker-compose/services/stats.yml index b4c14aac2e1f..83c9ea02a22b 100644 --- a/docker-compose/services/stats.yml +++ b/docker-compose/services/stats.yml @@ -12,9 +12,6 @@ services: chown -R 2000:2000 /var/lib/postgresql/data stats-db: - depends_on: - stats-db-init: - condition: service_completed_successfully image: postgres:15 user: 2000:2000 shm_size: 256m @@ -43,8 +40,6 @@ services: platform: linux/amd64 restart: always container_name: 'stats' - depends_on: - - "stats-db" extra_hosts: - 'host.docker.internal:host-gateway' env_file: From 7467d4d075ad5f2b42fc8d8cc37f0fc5a2467d79 Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Fri, 1 Mar 2024 19:32:40 +0300 Subject: [PATCH 526/607] Add tsvector index on smart_contracts.name (#9487) * Add tsvector index on smart_contracts.name * Changelog --- CHANGELOG.md | 2 ++ ...27115149_add_smart_contracts_name_text_index.exs | 13 +++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 apps/explorer/priv/repo/migrations/20240227115149_add_smart_contracts_name_text_index.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6173e864d9e0..cad25b874abf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ ### Chore +- [#9487](https://github.com/blockscout/blockscout/pull/9487) - Add tsvector index on smart_contracts.name +
Dependencies version bumps diff --git a/apps/explorer/priv/repo/migrations/20240227115149_add_smart_contracts_name_text_index.exs b/apps/explorer/priv/repo/migrations/20240227115149_add_smart_contracts_name_text_index.exs new file mode 100644 index 000000000000..8cf53911574d --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240227115149_add_smart_contracts_name_text_index.exs @@ -0,0 +1,13 @@ +defmodule Explorer.Repo.Migrations.AddSmartContractsNameTextIndex do + use Ecto.Migration + + def up do + execute(""" + CREATE INDEX IF NOT EXISTS smart_contracts_trgm_idx ON smart_contracts USING GIN (to_tsvector('english', name)) + """) + end + + def down do + execute("DROP INDEX IF EXISTS smart_contracts_trgm_idx") + end +end From 9819522ea1bd8e25645fa1195838f188d2a45bfe Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Mon, 4 Mar 2024 13:45:13 +0300 Subject: [PATCH 527/607] Optimism chain type (#9460) * Transaction page L1 fields * Path fix * Reduce the number of files from 19 to 5 in logs rotate config * Customize optimism-goerli deployment * Optimism branding * Remove testnet logo text. OG uses customized label * Fix Circles theme * L1 tx fields fix for Optimism BedRock update * Remove redundant line * Add gas_price handling for elixir_to_params and change function ordering * Remove l1TxOrigin handling for another version of RPC * Add GA * Fix realtime fetcher test * Update Changelog * Fix internal transactions processing for non-consensus blocks * Lose consensus only for consensus=true blocks * Fix handling transaction RPC responses without some fields * Fix tests except for indexer module * Add Optimism BedRock support (Txn Batches, Output Roots, Deposits, Withdrawals) (#6980) * Add op_output_roots table * Add OptimismOutputRoots runner * Add initial code for output roots fetcher * Add checks to init function * Partially add logs and L1 reorgs handling * Add reorgs handling * Add RPC retries * Write output roots to database * Log output roots handling * Update indexer README * Add API v2 for Optimism Output Roots * Add op_withdrawals table * Add OptimismWithdrawals runner * Prepare realtime optimism withdrawals fetching * Add realtime optimism withdrawals fetching * Define checks in init function * log.first_topic can be nil * Show total count of output roots in API v2 * Add msg_nonce gaps filler * Refactoring * Intermediate refactoring * Add historical withdrawals handling and refactor * Finish op_withdrawals table filling * Small refactoring * Add op_withdrawal_events table * Add OptimismWithdrawalEvents runner * Add OptimismWithdrawalEvent fetcher * Update indexer README * Add API v2 for Optimism Withdrawals * Add env variables to common-blockscout.env and Makefile * Set `from` as address object instead of just address hash for withdrawal * mix format * Add op_transaction_batches table * Add OptimismTxnBatches runner * Add a draft for OptimismTxnBatch fetcher * Add a draft for OptimismTxnBatch * Extend a draft for OptimismTxnBatch * Extend OptimismTxnBatch * Finish OptimismTxnBatch (without reorgs handling yet) * Optimize OptimismTxnBatch fetcher * Remove duplicated txn batches * Add zlib_inflate_handler for empty case * Add reorgs handling for txn batches * Fix reorgs handling for txn batches * Small refactor * Finish Indexer.Fetcher.OptimismTxnBatch (without refactoring yet) * Apply new ex_rlp version * Add API v2 for Optimism Txn Batches * Add env variables to common-blockscout.env and Makefile * Refactor OptimismTxnBatch fetcher for mix credo * Replace binary_slice with binary_part function to run with Elixir 1.13 * Update changelog * Update indexer readme * Rename op_withdrawals.l2_tx_hash field to l2_transaction_hash * Rename l1_tx_hash fields to l1_transaction_hash * Rename *tx* fields to *transaction* fields * Rename env variables * Rename env variables * Add an indexer helper * Add an indexer helper * Small refactoring * Fix tx_count for txn batches view * Use EthereumJSONRPC.Block.ByHash instead of the raw call * Infinity timeout for blocks query * Small refactoring * Refactor init function for two modules * Small refactoring * Rename l1_transaction_timestamp field to l1_timestamp * Rename withdrawal_hash field to hash * Refactor for decode_data function * Refactor for mix credo * Add INDEXER_OPTIMISM_L1_BATCH_BLOCKS_CHUNK_SIZE env and small refactoring * Add INDEXER_OPTIMISM_L1_BATCH_BLOCKS_CHUNK_SIZE env to other files * Add an index for l1_block_number field * Add an index for l1_block_number field * Remove redundant :ok * Use faster way to count rows in a table * Refactor reorgs monitor functions * Clarify frame structure * Reduce storage consumption for optimism transaction batches * Reuse CacheHelper.estimated_count_from function * Bedrock optimism deposits (#6993) * Create `op_deposits` table * Add OptimismDeposit runner * WIP Fetcher * Finish fetcher * Integrate deposits into APIv2 * Add envs * Fix requests * Remove debug * Update envs names * Rename `tx` -> `transaction` * Reuse `decode_data/2` * Fix review * Add `uninstall_filter` * Fix formatting * Switch to realtime mode more carefully * Fix review Allow nil in timestamp Add progress logging Improve check_interval calculation * Fix logging and env * Fix Association.NotLoaded error * Replace switching to realtime mode log * Remove excess start_block * Fix reorg logging * Fix `from_block` > `to_block` and add realtime logging * Fix block boundaries --------- Co-authored-by: POA <33550681+poa@users.noreply.github.com> * mix format * Return total count of L2 entity by a separate API request * Filter by consensus blocks * Parallelize tx count operation and small refactoring * Use read replica for L2 entities in API * Parse block_number and tx_hash for Optimism Deposits module * Return page_size back to 50 * Small fixes and refactoring * Update apps/block_scout_web/lib/block_scout_web/api_router.ex Co-authored-by: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> * Small optimization * Use ecto association instead of explicit join for txn batches * Refactoring * Use Stream inspead of Enum * Small refactoring * Add assoc for transaction batches in OptimismFrameSequence * Use common reorg monitor for Optimism modules * Rename Explorer.Helpers to Explorer.Helper * Don't start an optimism module unless the main optimism module is not started * Don't start reorg monitor for optimism modules when it is not needed * Small refactoring * Remove debug broadcasting * Add Optimism BedRock Deposits to the main page in API (#7200) * Add Optimism BedRock Deposits to the main page in API * Update changelog * Pass the number of deposits instead of only one item per once --------- Co-authored-by: POA <33550681+poa@users.noreply.github.com> * Refactor for credo * Output L1 fields in API v2 for transaction page * Update changelog * Use helper * Refactor Indexer.Fetcher.Optimism * Fix l1_timestamp issue in OptimismTxnBatch fetcher * Reset Logger metadata before Indexer.Transform.OptimismWithdrawals.parse function finishes * Fix IDs ordering in remove_duplicates function of Indexer.Fetcher.OptimismTxnBatch * Consider rewriting of the first frame in Indexer.Fetcher.OptimismTxnBatch * Fix Indexer.Fetcher.OptimismTxnBatch (consider chunking) * Fix Indexer.Fetcher.OptimismTxnBatch * Fix handling invalid frame sequences in Indexer.Fetcher.OptimismTxnBatch * Read Optimism finalization period from a smart contract * Fixes for dialyzer * Fix for EthereumJSONRPC tests * Fixes for Explorer tests * Fixes for Explorer tests * Fix of block/realtime/fetcher_test.exs * mix format and small fixes for block_scout_web tests * Reset GA cache * Fix handling nil in PendingBlockOperation.estimated_count() --------- Co-authored-by: POA <33550681+poa@users.noreply.github.com> Co-authored-by: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> * Fix autocomplete * Fix merging conflicts * Add exit handler to Indexer.Fetcher.OptimismWithdrawal * Fix transactions ordering in Indexer.Fetcher.OptimismTxnBatch * Update changelog * Refactor to fix credo * Mix credo fix * Fix transaction batches module for L2 OP stack (#7827) * Fix mixed transactions handling in Indexer.Fetcher.OptimismTxnBatch * Ignore duplicated frame * Update changelog * Add sorting to the future frames list * Change list order --------- Co-authored-by: POA <33550681+poa@users.noreply.github.com> * Remove unused aliases * Ignore previously handled frame by OP transaction batches module (#8122) * Ignore duplicated frame * Update changelog --------- Co-authored-by: POA <33550681+poa@users.noreply.github.com> * Return alias for Explorer.Chain.Cache.Helper in chain.ex * Ignore invalid frame by OP transaction batches module (#8208) * Update changelog * Ignore invalid frame * Update changelog --------- Co-authored-by: POA <33550681+poa@users.noreply.github.com> * Fix Indexer.Fetcher.OptimismTxnBatch * Fix API v2 for OP Withdrawals * Refactor optimism fetchers init * Add log for switching from fallback url * Fix for Indexer.Fetcher.OptimismTxnBatch * Add OP withdrawal status to transaction page in API (#8702) * Add OP withdrawal status to transaction page in API * Update changelog * Small refactoring * Update .dialyzer-ignore --------- Co-authored-by: POA <33550681+poa@users.noreply.github.com> * Add start pause to `Indexer.Fetcher.OptimismTxnBatch` * Small refactor of `Indexer.Fetcher.OptimismTxnBatch` * Consider consensus block only when retrieving OP withdrawal transaction status (#8811) * Consider consensus block only when retrieving OP withdrawal transaction status * Update changelog * Clear GA cache --------- Co-authored-by: POA <33550681+poa@users.noreply.github.com> * Hotfix for optimism_withdrawal_transaction_status function * Return all OP Withdrawals bound to L2 transaction * Try to import config * Remove unused functions from Explorer.Chain * Refactor for mix credo * Fix order of proxy standards: 1167, 1967 * Fixes in Optimism due to changed log topics type * Fix for EthereumJSONRPC tests * Clear GA cache and update cspell.json * Fix indexer tests * Return current exchange rate in api/v2/stats * Fix log decoding bug * Temp disable build of image for arm64 * Rewrite Indexer.Fetcher.OptimismTxnBatch module * Add handling of span batches * Add support of latest block for Optimism modules * Update changelog and spelling * Rewrite Indexer.Fetcher.OptimismTxnBatch module * Add handling of span batches * Add support of latest block for Optimism modules * Refactoring * Partially add specs and docs for public functions * Refactoring * add an entry to CHANEGELOG.md * apply review (use origin entity instead of joined entity in with tx status) * Fixes after rebase * Remove old UI sustomizations * Optimism chain type * Change structure of folders * Fixes after review * Fix CHANGELOG * Fixes after 2nd review * Process 3d review: add tests for fee/2 function * Process 4th review * Review fix: move Op related functions from chain.ex * Review fix: make OptimismFinalizationPeriod configurable * Process review comment * System.get_env("CHAIN_TYPE") => Application.get_env(:explorer, :chain_type) --------- Co-authored-by: POA <33550681+poa@users.noreply.github.com> Co-authored-by: Qwerty5Uiop Co-authored-by: varasev <33550681+varasev@users.noreply.github.com> Co-authored-by: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Co-authored-by: rlgns98kr --- .github/workflows/config.yml | 2 +- .../publish-docker-image-for-optimism.yml | 3 +- .github/workflows/release-optimism.yml | 45 + CHANGELOG.md | 24 +- .../lib/block_scout_web/api_router.ex | 33 +- .../lib/block_scout_web/chain.ex | 43 + .../channels/optimism_deposit_channel.ex | 22 + .../block_scout_web/channels/user_socket.ex | 1 + .../channels/user_socket_v2.ex | 1 + .../api/v2/main_page_controller.ex | 16 +- .../controllers/api/v2/optimism_controller.ex | 111 +++ .../lib/block_scout_web/notifier.ex | 8 + .../block_scout_web/realtime_event_handler.ex | 1 + .../templates/block/_tile.html.eex | 2 +- .../templates/chain/show.html.eex | 32 +- .../internal_transaction/_tile.html.eex | 2 +- .../templates/layout/_topnav.html.eex | 20 +- .../transaction/_pending_tile.html.eex | 2 +- .../templates/transaction/_tile.html.eex | 4 +- .../templates/transaction/overview.html.eex | 95 +- .../views/api/v2/optimism_view.ex | 149 +++ .../views/api/v2/transaction_view.ex | 36 + .../views/cldr_helper/number.ex | 2 + .../block_scout_web/views/transaction_view.ex | 14 + apps/block_scout_web/mix.exs | 10 +- apps/block_scout_web/priv/gettext/default.pot | 351 +++++--- .../priv/gettext/en/LC_MESSAGES/default.po | 353 +++++--- apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex | 10 + .../lib/ethereum_jsonrpc/block/by_hash.ex | 6 +- .../lib/ethereum_jsonrpc/receipt.ex | 25 +- .../lib/ethereum_jsonrpc/receipts.ex | 6 + .../lib/ethereum_jsonrpc/transaction.ex | 103 ++- .../utility/endpoint_availability_checker.ex | 5 +- .../utility/endpoint_availability_observer.ex | 3 + .../lib/ethereum_jsonrpc/variant.ex | 1 + apps/explorer/config/dev.exs | 3 + apps/explorer/config/prod.exs | 4 + apps/explorer/config/test.exs | 1 + apps/explorer/lib/explorer/application.ex | 2 + apps/explorer/lib/explorer/chain.ex | 19 +- .../lib/explorer/chain/address/counters.ex | 2 +- .../lib/explorer/chain/cache/block.ex | 10 +- .../lib/explorer/chain/cache/helper.ex | 2 +- .../cache/optimism_finalization_period.ex | 54 ++ .../chain/cache/pending_block_operation.ex | 2 +- .../lib/explorer/chain/cache/transaction.ex | 2 +- .../lib/explorer/chain/events/publisher.ex | 2 +- .../lib/explorer/chain/events/subscriber.ex | 2 +- .../chain/import/runner/optimism/deposits.ex | 106 +++ .../import/runner/optimism/frame_sequences.ex | 102 +++ .../import/runner/optimism/output_roots.ex | 108 +++ .../import/runner/optimism/txn_batches.ex | 100 +++ .../runner/optimism/withdrawal_events.ex | 105 +++ .../import/runner/optimism/withdrawals.ex | 104 +++ .../chain/import/runner/transactions.ex | 406 +++++---- .../chain/import/stage/block_referencing.ex | 16 +- .../lib/explorer/chain/optimism/deposit.ex | 86 ++ .../explorer/chain/optimism/frame_sequence.ex | 33 + .../explorer/chain/optimism/output_root.ex | 67 ++ .../lib/explorer/chain/optimism/txn_batch.ex | 61 ++ .../lib/explorer/chain/optimism/withdrawal.ex | 163 ++++ .../chain/optimism/withdrawal_event.ex | 34 + .../lib/explorer/chain/transaction.ex | 78 +- apps/explorer/lib/explorer/helper.ex | 17 +- apps/explorer/lib/explorer/repo.ex | 10 + ...0243_transaction_columns_to_support_l2.exs | 14 + ...230131115105_add_op_output_roots_table.exs | 16 + ...0230206123308_add_op_withdrawals_table.exs | 14 + ...2162845_add_op_withdrawal_events_table.exs | 22 + ...35703_add_op_transaction_batches_table.exs | 14 + .../20230220202107_create_op_deposits.exs | 17 + .../20230301105051_rename_fields.exs | 12 + .../20230303125841_add_op_indexes.exs | 8 + ...307090655_add_op_frame_sequences_table.exs | 24 + ...3_modify_collated_gas_price_constraint.exs | 15 + ...20231025102325_add_op_withdrawal_index.exs | 7 + ...124124644_remove_op_epoch_number_field.exs | 9 + .../test/explorer/chain/transaction_test.exs | 46 + apps/indexer/README.md | 6 + apps/indexer/lib/indexer/block/fetcher.ex | 10 + .../lib/indexer/block/realtime/fetcher.ex | 12 + .../indexer/fetcher/coin_balance/realtime.ex | 1 - apps/indexer/lib/indexer/fetcher/optimism.ex | 406 +++++++++ .../lib/indexer/fetcher/optimism/deposit.ex | 565 ++++++++++++ .../indexer/fetcher/optimism/output_root.ex | 187 ++++ .../lib/indexer/fetcher/optimism/txn_batch.ex | 850 ++++++++++++++++++ .../indexer/fetcher/optimism/withdrawal.ex | 361 ++++++++ .../fetcher/optimism/withdrawal_event.ex | 226 +++++ .../lib/indexer/fetcher/polygon_edge.ex | 32 +- .../indexer/fetcher/polygon_edge/deposit.ex | 3 +- .../fetcher/polygon_edge/deposit_execute.ex | 7 +- .../fetcher/polygon_edge/withdrawal.ex | 7 +- .../fetcher/polygon_zkevm/bridge_l1.ex | 4 +- .../fetcher/polygon_zkevm/bridge_l2.ex | 4 +- .../lib/indexer/fetcher/shibarium/l1.ex | 21 +- .../lib/indexer/fetcher/shibarium/l2.ex | 10 +- .../lib/indexer/fetcher/transaction_action.ex | 7 +- apps/indexer/lib/indexer/helper.ex | 27 +- apps/indexer/lib/indexer/supervisor.ex | 12 + .../lib/indexer/transform/addresses.ex | 8 +- .../indexer/transform/optimism/withdrawals.ex | 49 + .../indexer/transform/polygon_zkevm/bridge.ex | 6 +- .../lib/indexer/transform/shibarium/bridge.ex | 6 +- .../lib/indexer/transform/token_transfers.ex | 15 +- apps/indexer/mix.exs | 15 +- config/config_helper.exs | 1 + config/runtime.exs | 35 + config/runtime/dev.exs | 7 + config/runtime/prod.exs | 6 + cspell.json | 5 + docker-compose/envs/common-blockscout.env | 14 + mix.lock | 1 + 112 files changed, 5754 insertions(+), 632 deletions(-) create mode 100644 .github/workflows/release-optimism.yml create mode 100644 apps/block_scout_web/lib/block_scout_web/channels/optimism_deposit_channel.ex create mode 100644 apps/block_scout_web/lib/block_scout_web/controllers/api/v2/optimism_controller.ex create mode 100644 apps/block_scout_web/lib/block_scout_web/views/api/v2/optimism_view.ex create mode 100644 apps/explorer/lib/explorer/chain/cache/optimism_finalization_period.ex create mode 100644 apps/explorer/lib/explorer/chain/import/runner/optimism/deposits.ex create mode 100644 apps/explorer/lib/explorer/chain/import/runner/optimism/frame_sequences.ex create mode 100644 apps/explorer/lib/explorer/chain/import/runner/optimism/output_roots.ex create mode 100644 apps/explorer/lib/explorer/chain/import/runner/optimism/txn_batches.ex create mode 100644 apps/explorer/lib/explorer/chain/import/runner/optimism/withdrawal_events.ex create mode 100644 apps/explorer/lib/explorer/chain/import/runner/optimism/withdrawals.ex create mode 100644 apps/explorer/lib/explorer/chain/optimism/deposit.ex create mode 100644 apps/explorer/lib/explorer/chain/optimism/frame_sequence.ex create mode 100644 apps/explorer/lib/explorer/chain/optimism/output_root.ex create mode 100644 apps/explorer/lib/explorer/chain/optimism/txn_batch.ex create mode 100644 apps/explorer/lib/explorer/chain/optimism/withdrawal.ex create mode 100644 apps/explorer/lib/explorer/chain/optimism/withdrawal_event.ex create mode 100644 apps/explorer/priv/optimism/migrations/20220204060243_transaction_columns_to_support_l2.exs create mode 100644 apps/explorer/priv/optimism/migrations/20230131115105_add_op_output_roots_table.exs create mode 100644 apps/explorer/priv/optimism/migrations/20230206123308_add_op_withdrawals_table.exs create mode 100644 apps/explorer/priv/optimism/migrations/20230212162845_add_op_withdrawal_events_table.exs create mode 100644 apps/explorer/priv/optimism/migrations/20230216135703_add_op_transaction_batches_table.exs create mode 100644 apps/explorer/priv/optimism/migrations/20230220202107_create_op_deposits.exs create mode 100644 apps/explorer/priv/optimism/migrations/20230301105051_rename_fields.exs create mode 100644 apps/explorer/priv/optimism/migrations/20230303125841_add_op_indexes.exs create mode 100644 apps/explorer/priv/optimism/migrations/20230307090655_add_op_frame_sequences_table.exs create mode 100644 apps/explorer/priv/optimism/migrations/20230731130103_modify_collated_gas_price_constraint.exs create mode 100644 apps/explorer/priv/optimism/migrations/20231025102325_add_op_withdrawal_index.exs create mode 100644 apps/explorer/priv/optimism/migrations/20240124124644_remove_op_epoch_number_field.exs create mode 100644 apps/indexer/lib/indexer/fetcher/optimism.ex create mode 100644 apps/indexer/lib/indexer/fetcher/optimism/deposit.ex create mode 100644 apps/indexer/lib/indexer/fetcher/optimism/output_root.ex create mode 100644 apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex create mode 100644 apps/indexer/lib/indexer/fetcher/optimism/withdrawal.ex create mode 100644 apps/indexer/lib/indexer/fetcher/optimism/withdrawal_event.ex create mode 100644 apps/indexer/lib/indexer/transform/optimism/withdrawals.ex diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 4f22355b41b0..f0dc78055cca 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -48,7 +48,7 @@ jobs: run: | echo "matrix=$matrixStringifiedObject" >> $GITHUB_OUTPUT env: - matrixStringifiedObject: '{"chain-type": ["ethereum", "polygon_edge", "polygon_zkevm", "rsk", "suave", "stability", "filecoin"]}' + matrixStringifiedObject: '{"chain-type": ["ethereum", "polygon_edge", "polygon_zkevm", "rsk", "suave", "stability", "filecoin", "optimism"]}' build-and-cache: name: Build and Cache deps diff --git a/.github/workflows/publish-docker-image-for-optimism.yml b/.github/workflows/publish-docker-image-for-optimism.yml index c47114afc547..13361808767f 100644 --- a/.github/workflows/publish-docker-image-for-optimism.yml +++ b/.github/workflows/publish-docker-image-for-optimism.yml @@ -39,4 +39,5 @@ jobs: ADMIN_PANEL_ENABLED=false CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} - RELEASE_VERSION=${{ env.RELEASE_VERSION }} \ No newline at end of file + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism \ No newline at end of file diff --git a/.github/workflows/release-optimism.yml b/.github/workflows/release-optimism.yml new file mode 100644 index 000000000000..ed99f437262f --- /dev/null +++ b/.github/workflows/release-optimism.yml @@ -0,0 +1,45 @@ +name: Release for Ethereum + +on: + release: + types: [published] + +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Docker image for Optimism + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-optimism:latest, blockscout/blockscout-optimism:${{ env.RELEASE_VERSION }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index cad25b874abf..0c4440da5a7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,32 @@ ### Features - [#9461](https://github.com/blockscout/blockscout/pull/9461) - Fetch blocks without internal transactions backwards +- [#9460](https://github.com/blockscout/blockscout/pull/9460) - Optimism chain type +- [#8702](https://github.com/blockscout/blockscout/pull/8702) - Add OP withdrawal status to transaction page in API +- [#7200](https://github.com/blockscout/blockscout/pull/7200) - Add Optimism BedRock Deposits to the main page in API +- [#6980](https://github.com/blockscout/blockscout/pull/6980) - Add Optimism BedRock support (Txn Batches, Output Roots, Deposits, Withdrawals) ### Fixes - [#9512](https://github.com/blockscout/blockscout/pull/9512) - Docker-compose 2.24.6 compatibility - -### Chore +- [#9262](https://github.com/blockscout/blockscout/pull/9262) - Fix withdrawal status +- [#9123](https://github.com/blockscout/blockscout/pull/9123) - Fixes in Optimism due to changed log topics type +- [#8831](https://github.com/blockscout/blockscout/pull/8831) - Return all OP Withdrawals bound to L2 transaction +- [#8822](https://github.com/blockscout/blockscout/pull/8822) - Hotfix for optimism_withdrawal_transaction_status function +- [#8811](https://github.com/blockscout/blockscout/pull/8811) - Consider consensus block only when retrieving OP withdrawal transaction status +- [#8364](https://github.com/blockscout/blockscout/pull/8364) - Fix API v2 for OP Withdrawals +- [#8229](https://github.com/blockscout/blockscout/pull/8229) - Fix Indexer.Fetcher.OptimismTxnBatch +- [#8208](https://github.com/blockscout/blockscout/pull/8208) - Ignore invalid frame by OP transaction batches module +- [#8122](https://github.com/blockscout/blockscout/pull/8122) - Ignore previously handled frame by OP transaction batches module +- [#7827](https://github.com/blockscout/blockscout/pull/7827) - Fix transaction batches module for L2 OP stack +- [#7776](https://github.com/blockscout/blockscout/pull/7776) - Fix transactions ordering in Indexer.Fetcher.OptimismTxnBatch +- [#7219](https://github.com/blockscout/blockscout/pull/7219) - Output L1 fields in API v2 for transaction page and fix transaction fee calculation +- [#6699](https://github.com/blockscout/blockscout/pull/6699) - L1 tx fields fix for Goerli Optimism BedRock update + +### Chore + +- [#9260](https://github.com/blockscout/blockscout/pull/9260) - Optimism Delta upgrade support by Indexer.Fetcher.OptimismTxnBatch module +- [#8740](https://github.com/blockscout/blockscout/pull/8740) - Add delay to Indexer.Fetcher.OptimismTxnBatch module initialization ## 6.2.2 diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 9b3e12dd76c5..403d78d3a48b 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -203,11 +203,11 @@ defmodule BlockScoutWeb.ApiRouter do get("/", V2.TransactionController, :transactions) get("/watchlist", V2.TransactionController, :watchlist_transactions) - if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do + if Application.compile_env(:explorer, :chain_type) == "polygon_zkevm" do get("/zkevm-batch/:batch_number", V2.TransactionController, :polygon_zkevm_batch) end - if System.get_env("CHAIN_TYPE") == "suave" do + if Application.compile_env(:explorer, :chain_type) == "suave" do get("/execution-node/:execution_node_hash_param", V2.TransactionController, :execution_node) end @@ -219,7 +219,7 @@ defmodule BlockScoutWeb.ApiRouter do get("/:transaction_hash_param/state-changes", V2.TransactionController, :state_changes) get("/:transaction_hash_param/summary", V2.TransactionController, :summary) - if System.get_env("CHAIN_TYPE") == "ethereum" do + if Application.compile_env(:explorer, :chain_type) == "ethereum" do get("/:transaction_hash_param/blobs", V2.TransactionController, :blobs) end end @@ -273,7 +273,11 @@ defmodule BlockScoutWeb.ApiRouter do get("/transactions/watchlist", V2.MainPageController, :watchlist_transactions) get("/indexing-status", V2.MainPageController, :indexing_status) - if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do + if Application.compile_env(:explorer, :chain_type) == "optimism" do + get("/optimism-deposits", V2.MainPageController, :optimism_deposits) + end + + if Application.compile_env(:explorer, :chain_type) == "polygon_zkevm" do get("/zkevm/batches/confirmed", V2.PolygonZkevmController, :batches_confirmed) get("/zkevm/batches/latest-number", V2.PolygonZkevmController, :batch_latest_number) end @@ -288,8 +292,21 @@ defmodule BlockScoutWeb.ApiRouter do end end + scope "/optimism" do + if Application.compile_env(:explorer, :chain_type) == "optimism" do + get("/txn-batches", V2.OptimismController, :txn_batches) + get("/txn-batches/count", V2.OptimismController, :txn_batches_count) + get("/output-roots", V2.OptimismController, :output_roots) + get("/output-roots/count", V2.OptimismController, :output_roots_count) + get("/deposits", V2.OptimismController, :deposits) + get("/deposits/count", V2.OptimismController, :deposits_count) + get("/withdrawals", V2.OptimismController, :withdrawals) + get("/withdrawals/count", V2.OptimismController, :withdrawals_count) + end + end + scope "/polygon-edge" do - if System.get_env("CHAIN_TYPE") == "polygon_edge" do + if Application.compile_env(:explorer, :chain_type) == "polygon_edge" do get("/deposits", V2.PolygonEdgeController, :deposits) get("/deposits/count", V2.PolygonEdgeController, :deposits_count) get("/withdrawals", V2.PolygonEdgeController, :withdrawals) @@ -298,7 +315,7 @@ defmodule BlockScoutWeb.ApiRouter do end scope "/shibarium" do - if System.get_env("CHAIN_TYPE") == "shibarium" do + if Application.compile_env(:explorer, :chain_type) == "shibarium" do get("/deposits", V2.ShibariumController, :deposits) get("/deposits/count", V2.ShibariumController, :deposits_count) get("/withdrawals", V2.ShibariumController, :withdrawals) @@ -312,7 +329,7 @@ defmodule BlockScoutWeb.ApiRouter do end scope "/zkevm" do - if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do + if Application.compile_env(:explorer, :chain_type) == "polygon_zkevm" do get("/batches", V2.PolygonZkevmController, :batches) get("/batches/count", V2.PolygonZkevmController, :batches_count) get("/batches/:batch_number", V2.PolygonZkevmController, :batch) @@ -348,7 +365,7 @@ defmodule BlockScoutWeb.ApiRouter do end scope "/blobs" do - if System.get_env("CHAIN_TYPE") == "ethereum" do + if Application.compile_env(:explorer, :chain_type) == "ethereum" do get("/:blob_hash_param", V2.BlobController, :blob) end end diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index 5dff1d691b5e..8db363d1352c 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -41,6 +41,9 @@ defmodule BlockScoutWeb.Chain do Wei } + alias Explorer.Chain.Optimism.Deposit, as: OptimismDeposit + alias Explorer.Chain.Optimism.OutputRoot, as: OptimismOutputRoot + alias Explorer.Chain.PolygonZkevm.TransactionBatch alias Explorer.PagingOptions @@ -337,6 +340,16 @@ defmodule BlockScoutWeb.Chain do [paging_options: %{@default_paging_options | key: {index}}] end + def paging_options(%{"nonce" => nonce_string}) when is_binary(nonce_string) do + case Integer.parse(nonce_string) do + {nonce, ""} -> + [paging_options: %{@default_paging_options | key: {nonce}}] + + _ -> + [paging_options: @default_paging_options] + end + end + def paging_options(%{"number" => number_string}) when is_binary(number_string) do case Integer.parse(number_string) do {number, ""} -> @@ -347,6 +360,10 @@ defmodule BlockScoutWeb.Chain do end end + def paging_options(%{"nonce" => nonce}) when is_integer(nonce) do + [paging_options: %{@default_paging_options | key: {nonce}}] + end + def paging_options(%{"number" => number}) when is_integer(number) do [paging_options: %{@default_paging_options | key: {number}}] end @@ -404,6 +421,16 @@ defmodule BlockScoutWeb.Chain do end end + def paging_options(%{"l1_block_number" => block_number, "tx_hash" => tx_hash}) do + with {block_number, ""} <- Integer.parse(block_number), + {:ok, tx_hash} <- string_to_transaction_hash(tx_hash) do + [paging_options: %{@default_paging_options | key: {block_number, tx_hash}}] + else + _ -> + [paging_options: @default_paging_options] + end + end + # clause for Polygon Edge Deposits and Withdrawals and for account's entities pagination def paging_options(%{"id" => id_string}) when is_binary(id_string) do case Integer.parse(id_string) do @@ -602,6 +629,14 @@ defmodule BlockScoutWeb.Chain do %{"smart_contract_id" => smart_contract.id} end + defp paging_params(%OptimismDeposit{l1_block_number: l1_block_number, l2_transaction_hash: l2_tx_hash}) do + %{"l1_block_number" => l1_block_number, "tx_hash" => l2_tx_hash} + end + + defp paging_params(%OptimismOutputRoot{l2_output_index: index}) do + %{"index" => index} + end + defp paging_params(%SmartContract{} = smart_contract) do %{ "smart_contract_id" => smart_contract.id, @@ -615,6 +650,14 @@ defmodule BlockScoutWeb.Chain do %{"index" => index} end + defp paging_params(%{msg_nonce: nonce}) do + %{"nonce" => nonce} + end + + defp paging_params(%{l2_block_number: block_number}) do + %{"block_number" => block_number} + end + # clause for zkEVM batches pagination defp paging_params(%TransactionBatch{number: number}) do %{"number" => number} diff --git a/apps/block_scout_web/lib/block_scout_web/channels/optimism_deposit_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/optimism_deposit_channel.ex new file mode 100644 index 000000000000..3f2c513f9b1c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/channels/optimism_deposit_channel.ex @@ -0,0 +1,22 @@ +defmodule BlockScoutWeb.OptimismDepositChannel do + @moduledoc """ + Establishes pub/sub channel for live updates of Optimism deposit events. + """ + use BlockScoutWeb, :channel + + intercept(["deposits"]) + + def join("optimism_deposits:new_deposits", _params, socket) do + {:ok, %{}, socket} + end + + def handle_out( + "deposits", + %{deposits: deposits}, + %Phoenix.Socket{handler: BlockScoutWeb.UserSocketV2} = socket + ) do + push(socket, "deposits", %{deposits: Enum.count(deposits)}) + + {:noreply, socket} + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/channels/user_socket.ex b/apps/block_scout_web/lib/block_scout_web/channels/user_socket.ex index 6060428a7981..e357674a3c11 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/user_socket.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/user_socket.ex @@ -5,6 +5,7 @@ defmodule BlockScoutWeb.UserSocket do channel("addresses:*", BlockScoutWeb.AddressChannel) channel("blocks:*", BlockScoutWeb.BlockChannel) channel("exchange_rate:*", BlockScoutWeb.ExchangeRateChannel) + channel("optimism_deposits:*", BlockScoutWeb.OptimismDepositChannel) channel("rewards:*", BlockScoutWeb.RewardChannel) channel("transactions:*", BlockScoutWeb.TransactionChannel) channel("tokens:*", BlockScoutWeb.TokenChannel) diff --git a/apps/block_scout_web/lib/block_scout_web/channels/user_socket_v2.ex b/apps/block_scout_web/lib/block_scout_web/channels/user_socket_v2.ex index ec3e5460ecc4..740b716dc322 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/user_socket_v2.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/user_socket_v2.ex @@ -7,6 +7,7 @@ defmodule BlockScoutWeb.UserSocketV2 do channel("addresses:*", BlockScoutWeb.AddressChannel) channel("blocks:*", BlockScoutWeb.BlockChannel) channel("exchange_rate:*", BlockScoutWeb.ExchangeRateChannel) + channel("optimism_deposits:*", BlockScoutWeb.OptimismDepositChannel) channel("rewards:*", BlockScoutWeb.RewardChannel) channel("transactions:*", BlockScoutWeb.TransactionChannel) channel("tokens:*", BlockScoutWeb.TokenChannel) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex index 0e06f6e058ca..e6fdbe0996cc 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex @@ -2,8 +2,9 @@ defmodule BlockScoutWeb.API.V2.MainPageController do use Phoenix.Controller alias Explorer.{Chain, PagingOptions} - alias BlockScoutWeb.API.V2.{BlockView, TransactionView} + alias BlockScoutWeb.API.V2.{BlockView, OptimismView, TransactionView} alias Explorer.{Chain, Repo} + alias Explorer.Chain.Optimism.Deposit import BlockScoutWeb.Account.AuthController, only: [current_user: 1] import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] @@ -36,6 +37,19 @@ defmodule BlockScoutWeb.API.V2.MainPageController do |> render(:blocks, %{blocks: blocks |> maybe_preload_ens()}) end + def optimism_deposits(conn, _params) do + recent_deposits = + Deposit.list( + paging_options: %PagingOptions{page_size: 6}, + api?: true + ) + + conn + |> put_status(200) + |> put_view(OptimismView) + |> render(:optimism_deposits, %{deposits: recent_deposits}) + end + def transactions(conn, _params) do recent_transactions = Chain.recent_collated_transactions(false, @transactions_options) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/optimism_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/optimism_controller.ex new file mode 100644 index 000000000000..ef3bfe0d688e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/optimism_controller.ex @@ -0,0 +1,111 @@ +defmodule BlockScoutWeb.API.V2.OptimismController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Chain, + only: [ + next_page_params: 3, + paging_options: 1, + split_list_by_page: 1 + ] + + alias Explorer.Chain + alias Explorer.Chain.Optimism.{Deposit, OutputRoot, TxnBatch, Withdrawal} + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + def txn_batches(conn, params) do + {batches, next_page} = + params + |> paging_options() + |> Keyword.put(:api?, true) + |> TxnBatch.list() + |> split_list_by_page() + + next_page_params = next_page_params(next_page, batches, params) + + conn + |> put_status(200) + |> render(:optimism_txn_batches, %{ + batches: batches, + next_page_params: next_page_params + }) + end + + def txn_batches_count(conn, _params) do + items_count(conn, TxnBatch) + end + + def output_roots(conn, params) do + {roots, next_page} = + params + |> paging_options() + |> Keyword.put(:api?, true) + |> OutputRoot.list() + |> split_list_by_page() + + next_page_params = next_page_params(next_page, roots, params) + + conn + |> put_status(200) + |> render(:optimism_output_roots, %{ + roots: roots, + next_page_params: next_page_params + }) + end + + def output_roots_count(conn, _params) do + items_count(conn, OutputRoot) + end + + def deposits(conn, params) do + {deposits, next_page} = + params + |> paging_options() + |> Keyword.put(:api?, true) + |> Deposit.list() + |> split_list_by_page() + + next_page_params = next_page_params(next_page, deposits, params) + + conn + |> put_status(200) + |> render(:optimism_deposits, %{ + deposits: deposits, + next_page_params: next_page_params + }) + end + + def deposits_count(conn, _params) do + items_count(conn, Deposit) + end + + def withdrawals(conn, params) do + {withdrawals, next_page} = + params + |> paging_options() + |> Keyword.put(:api?, true) + |> Withdrawal.list() + |> split_list_by_page() + + next_page_params = next_page_params(next_page, withdrawals, params) + + conn + |> put_status(200) + |> render(:optimism_withdrawals, %{ + withdrawals: withdrawals, + next_page_params: next_page_params + }) + end + + def withdrawals_count(conn, _params) do + items_count(conn, Withdrawal) + end + + defp items_count(conn, module) do + count = Chain.get_table_rows_total_count(module, api?: true) + + conn + |> put_status(200) + |> render(:optimism_items_count, %{count: count}) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/notifier.ex b/apps/block_scout_web/lib/block_scout_web/notifier.ex index b9e5df6ca61d..ca7027ae89aa 100644 --- a/apps/block_scout_web/lib/block_scout_web/notifier.ex +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -232,6 +232,10 @@ defmodule BlockScoutWeb.Notifier do Endpoint.broadcast("addresses:#{to_string(address_hash)}", "changed_bytecode", %{}) end + def handle_event({:chain_event, :optimism_deposits, :realtime, deposits}) do + broadcast_optimism_deposits(deposits, "optimism_deposits:new_deposits", "deposits") + end + def handle_event({:chain_event, :smart_contract_was_verified = event, :on_demand, [address_hash]}) do broadcast_automatic_verification_events(event, address_hash) end @@ -400,6 +404,10 @@ defmodule BlockScoutWeb.Notifier do end end + defp broadcast_optimism_deposits(deposits, deposit_channel, event) do + Endpoint.broadcast(deposit_channel, event, %{deposits: deposits}) + end + defp broadcast_transactions_websocket_v2(transactions) do pending_transactions = Enum.filter(transactions, fn diff --git a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex index 1fedc2dc3dba..b08f50e48b3e 100644 --- a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex +++ b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex @@ -19,6 +19,7 @@ defmodule BlockScoutWeb.RealtimeEventHandler do Subscriber.to(:block_rewards, :realtime) Subscriber.to(:internal_transactions, :realtime) Subscriber.to(:internal_transactions, :on_demand) + Subscriber.to(:optimism_deposits, :realtime) Subscriber.to(:token_transfers, :realtime) Subscriber.to(:addresses, :on_demand) Subscriber.to(:address_coin_balances, :on_demand) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex index 452c73a3feb0..419ac616db73 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex @@ -33,7 +33,7 @@ <%= Cldr.Unit.new!(:byte, @block.size) |> cldr_unit_to_string!() %> <% end %> - +
<%= if !Application.get_env(:block_scout_web, :hide_block_miner) do %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex index 33b1995cfa0f..9216229bc42b 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex @@ -92,18 +92,20 @@
- <%= case @average_block_time do %> - <% {:error, :disabled} -> %> - <%= nil %> - <% average_block_time -> %> -
- - <%= gettext "Average block time" %> - - - <%= Timex.format_duration(average_block_time, Explorer.Counters.AverageBlockTimeDurationFormat) %> - -
+ <%= unless Application.get_env(:explorer, :chain_type) == "optimism" do %> + <%= case @average_block_time do %> + <% {:error, :disabled} -> %> + <%= nil %> + <% average_block_time -> %> +
+ + <%= gettext "Average block time" %> + + + <%= Timex.format_duration(average_block_time, Explorer.Counters.AverageBlockTimeDurationFormat) %> + +
+ <% end %> <% end %>
@@ -172,7 +174,7 @@ diff --git a/apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex index 27ede917dd8a..3c23ba78c6fa 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex @@ -33,7 +33,7 @@ to: block_path(BlockScoutWeb.Endpoint, :show, @internal_transaction.block_number) ) %> - + <%= if assigns[:current_address] do %> <%= if assigns[:current_address].hash == @internal_transaction.from_address_hash do %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex index 1f8707d57f69..7df4369402ac 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex @@ -83,7 +83,7 @@ <%= gettext("Tokens") %> -
<% end %> + <%= if Application.get_env(:explorer, :chain_type) == "optimism" && @transaction.l1_block_number do %> + +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Block number containing the transaction on L1.") %> + <%= gettext "L1 Block" %>
+
+ <%= if block do %> + <%= link( + @transaction.l1_block_number, + class: "transaction__link", + to: "https://eth-goerli.blockscout.com/block/#{@transaction.l1_block_number}" + ) %> + <% else %> + <%= formatted_result(status) %> + <% end %> +
+
+ <% end %> <% %{transaction_actions: transaction_actions} = transaction_actions(@transaction) %> <%= if not Enum.empty?(transaction_actions) do %> @@ -371,12 +391,18 @@ <% end %> - +
- <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", - text: gettext("Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage.") %> - <%= gettext "Gas Price" %> + <%= if Application.get_env(:explorer, :chain_type) == "optimism" do %> + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Price per unit of gas specified by the sender on L2. Higher gas prices can prioritize transaction inclusion during times of high usage.") %> + <%= gettext "L2 Gas Price" %> + <% else %> + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage.") %> + <%= gettext "Gas Price" %> + <% end %>
<%= gas_price(@transaction, :gwei) %>
@@ -394,9 +420,15 @@
- <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", - text: gettext("Maximum gas amount approved for the transaction.") %> - <%= gettext "Gas Limit" %> + <%= if Application.get_env(:explorer, :chain_type) == "optimism" do %> + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Maximum gas amount approved for the transaction on L2.") %> + <%= gettext "L2 Gas Limit" %> + <% else %> + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Maximum gas amount approved for the transaction.") %> + <%= gettext "Gas Limit" %> + <% end %>
<%= format_gas_limit(@transaction.gas) %>
@@ -444,16 +476,57 @@ <% end %> - +
- <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", - text: gettext("Actual gas amount used by the transaction.") %> - <%= gettext "Gas Used by Transaction" %> + <%= if Application.get_env(:explorer, :chain_type) == "optimism" do %> + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Actual gas amount used by the transaction on L2.") %> + <%= gettext "L2 Gas Used by Transaction" %> + <% else %> + <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Actual gas amount used by the transaction.") %> + <%= gettext "Gas Used by Transaction" %> + <% end %>
<% gas_used_perc = gas_used_perc(@transaction) %>
<%= gas_used(@transaction) %> <%= if gas_used_perc, do: "| #{gas_used_perc}%" %>
+ <%= if Application.get_env(:explorer, :chain_type) == "optimism" do %> + <%= if @transaction.l1_gas_used do %> + +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("L1 Gas Used by Transaction") %> + <%= gettext "L1 Gas Used by Transaction" %> +
+
<%= l1_gas_used(@transaction) %>
+
+ <% end %> + <%= if @transaction.l1_gas_used do %> + +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("L1 Gas Price") %> + <%= gettext "L1 Gas Price" %> +
+
<%= l1_gas_price(@transaction, :gwei) %>
+
+ <% end %> + <%= if @transaction.l1_fee_scalar do %> + +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("L1 Fee Scalar") %> + <%= gettext "L1 Fee Scalar" %> +
+
<%= @transaction.l1_fee_scalar %>
+
+ <% end %> + <% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/optimism_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/optimism_view.ex new file mode 100644 index 000000000000..b1d003a1f109 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/optimism_view.ex @@ -0,0 +1,149 @@ +defmodule BlockScoutWeb.API.V2.OptimismView do + use BlockScoutWeb, :view + + import Ecto.Query, only: [from: 2] + + alias BlockScoutWeb.API.V2.Helper + alias Explorer.{Chain, Repo} + alias Explorer.Chain.{Block, Transaction} + alias Explorer.Chain.Optimism.Withdrawal + + def render("optimism_txn_batches.json", %{ + batches: batches, + next_page_params: next_page_params + }) do + items = + batches + |> Enum.map(fn batch -> + Task.async(fn -> + tx_count = + Repo.replica().aggregate( + from( + t in Transaction, + inner_join: b in Block, + on: b.hash == t.block_hash and b.consensus == true, + where: t.block_number == ^batch.l2_block_number + ), + :count, + timeout: :infinity + ) + + %{ + "l2_block_number" => batch.l2_block_number, + "tx_count" => tx_count, + "l1_tx_hashes" => batch.frame_sequence.l1_transaction_hashes, + "l1_timestamp" => batch.frame_sequence.l1_timestamp + } + end) + end) + |> Task.yield_many(:infinity) + |> Enum.map(fn {_task, {:ok, item}} -> item end) + + %{ + items: items, + next_page_params: next_page_params + } + end + + def render("optimism_output_roots.json", %{ + roots: roots, + next_page_params: next_page_params + }) do + %{ + items: + Enum.map(roots, fn r -> + %{ + "l2_output_index" => r.l2_output_index, + "l2_block_number" => r.l2_block_number, + "l1_tx_hash" => r.l1_transaction_hash, + "l1_timestamp" => r.l1_timestamp, + "l1_block_number" => r.l1_block_number, + "output_root" => r.output_root + } + end), + next_page_params: next_page_params + } + end + + def render("optimism_deposits.json", %{ + deposits: deposits, + next_page_params: next_page_params + }) do + %{ + items: + Enum.map(deposits, fn deposit -> + %{ + "l1_block_number" => deposit.l1_block_number, + "l2_tx_hash" => deposit.l2_transaction_hash, + "l1_block_timestamp" => deposit.l1_block_timestamp, + "l1_tx_hash" => deposit.l1_transaction_hash, + "l1_tx_origin" => deposit.l1_transaction_origin, + "l2_tx_gas_limit" => deposit.l2_transaction.gas + } + end), + next_page_params: next_page_params + } + end + + def render("optimism_deposits.json", %{deposits: deposits}) do + Enum.map(deposits, fn deposit -> + %{ + "l1_block_number" => deposit.l1_block_number, + "l1_block_timestamp" => deposit.l1_block_timestamp, + "l1_tx_hash" => deposit.l1_transaction_hash, + "l2_tx_hash" => deposit.l2_transaction_hash + } + end) + end + + def render("optimism_withdrawals.json", %{ + withdrawals: withdrawals, + next_page_params: next_page_params, + conn: conn + }) do + %{ + items: + Enum.map(withdrawals, fn w -> + msg_nonce = + Bitwise.band( + Decimal.to_integer(w.msg_nonce), + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + ) + + msg_nonce_version = Bitwise.bsr(Decimal.to_integer(w.msg_nonce), 240) + + {from_address, from_address_hash} = + with false <- is_nil(w.from), + {:ok, address} <- + Chain.hash_to_address( + w.from, + [necessity_by_association: %{:names => :optional, :smart_contract => :optional}, api?: true], + false + ) do + {address, address.hash} + else + _ -> {nil, nil} + end + + {status, challenge_period_end} = Withdrawal.status(w) + + %{ + "msg_nonce_raw" => Decimal.to_string(w.msg_nonce, :normal), + "msg_nonce" => msg_nonce, + "msg_nonce_version" => msg_nonce_version, + "from" => Helper.address_with_info(conn, from_address, from_address_hash, w.from), + "l2_tx_hash" => w.l2_transaction_hash, + "l2_timestamp" => w.l2_timestamp, + "status" => status, + "l1_tx_hash" => w.l1_transaction_hash, + "challenge_period_end" => challenge_period_end + } + end), + next_page_params: next_page_params + } + end + + def render("optimism_items_count.json", %{count: count}) do + count + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index 5eea21c5a697..a1d8c5008266 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -10,6 +10,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do alias Explorer.{Chain, Market} alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Token, Transaction, Wei} alias Explorer.Chain.Block.Reward + alias Explorer.Chain.Optimism.Withdrawal, as: OptimismWithdrawal alias Explorer.Chain.PolygonEdge.Reader alias Explorer.Chain.Transaction.StateChange alias Explorer.Counters.AverageBlockTime @@ -440,6 +441,14 @@ defmodule BlockScoutWeb.API.V2.TransactionView do |> maybe_put_stability_fee(transaction) end + defp add_optional_transaction_field(result, transaction, field) do + case Map.get(transaction, field) do + nil -> result + value -> Map.put(result, Atom.to_string(field), value) + end + end + + # credo:disable-for-next-line defp chain_type_fields(result, transaction, single_tx?, conn, watchlist_names) do case {single_tx?, Application.get_env(:explorer, :chain_type)} do {true, "polygon_edge"} -> @@ -456,6 +465,14 @@ defmodule BlockScoutWeb.API.V2.TransactionView do Map.put(extended_result, "zkevm_status", zkevm_status(extended_result)) + {true, "optimism"} -> + result + |> add_optional_transaction_field(transaction, :l1_fee) + |> add_optional_transaction_field(transaction, :l1_fee_scalar) + |> add_optional_transaction_field(transaction, :l1_gas_price) + |> add_optional_transaction_field(transaction, :l1_gas_used) + |> add_optimism_fields(transaction.hash, single_tx?) + {true, "suave"} -> suave_fields(transaction, result, single_tx?, conn, watchlist_names) @@ -564,6 +581,25 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end end + defp add_optimism_fields(result, transaction_hash, single_tx?) do + if Application.get_env(:explorer, :chain_type) == "optimism" && single_tx? do + withdrawals = + transaction_hash + |> OptimismWithdrawal.transaction_statuses() + |> Enum.map(fn {nonce, status, l1_transaction_hash} -> + %{ + "nonce" => nonce, + "status" => status, + "l1_transaction_hash" => l1_transaction_hash + } + end) + + Map.put(result, "op_withdrawals", withdrawals) + else + result + end + end + def token_transfers(_, _conn, false), do: nil def token_transfers(%NotLoaded{}, _conn, _), do: nil diff --git a/apps/block_scout_web/lib/block_scout_web/views/cldr_helper/number.ex b/apps/block_scout_web/lib/block_scout_web/views/cldr_helper/number.ex index fa3ad1f1c1f8..0d1c0e1e139f 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/cldr_helper/number.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/cldr_helper/number.ex @@ -17,6 +17,8 @@ defmodule BlockScoutWeb.CldrHelper.Number do end end + def to_string!(nil), do: "" + def to_string!(decimal) do # We do this to trick Dialyzer to not complain about non-local returns caused by bug in Cldr.Number.to_string! spec case :erlang.phash2(1, 1) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex index 4d94264b5ed9..c3d741741211 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex @@ -409,12 +409,26 @@ defmodule BlockScoutWeb.TransactionView do format_wei_value(gas_price, unit) end + def l1_gas_price(transaction, unit) when unit in ~w(wei gwei ether)a do + case Map.get(transaction, :l1_gas_price) do + nil -> nil + value -> format_wei_value(value, unit) + end + end + def gas_used(%Transaction{gas_used: nil}), do: gettext("Pending") def gas_used(%Transaction{gas_used: gas_used}) do Number.to_string!(gas_used) end + def l1_gas_used(transaction) do + case Map.get(transaction, :l1_gas_used) do + nil -> gettext("Pending") + value -> Number.to_string!(value) + end + end + def gas_used_perc(%Transaction{gas_used: nil}), do: nil def gas_used_perc(%Transaction{gas_used: gas_used, gas: gas}) do diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index 56d6d8b25a7a..21493948ed25 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -24,7 +24,15 @@ defmodule BlockScoutWeb.Mixfile do ], start_permanent: Mix.env() == :prod, version: "6.2.2", - xref: [exclude: [Explorer.Chain.PolygonZkevm.Reader, Explorer.Chain.Beacon.Reader]] + xref: [ + exclude: [ + Explorer.Chain.PolygonZkevm.Reader, + Explorer.Chain.Beacon.Reader, + Explorer.Chain.Cache.OptimismFinalizationPeriod, + Explorer.Chain.Optimism.OutputRoot, + Explorer.Chain.Optimism.WithdrawalEvent + ] + ] ] end diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 3912a1c094b7..a36415acff7a 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -199,11 +199,6 @@ msgstr "" msgid "Actions" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:451 -#, elixir-autogen, elixir-format -msgid "Actual gas amount used by the transaction." -msgstr "" - #: lib/block_scout_web/templates/account/api_key/form.html.eex:7 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 #: lib/block_scout_web/templates/layout/_add_chain_to_mm.html.eex:10 @@ -270,12 +265,12 @@ msgstr "" msgid "Address" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:243 +#: lib/block_scout_web/templates/transaction/overview.html.eex:263 #, elixir-autogen, elixir-format msgid "Address (external or contract) receiving the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:225 +#: lib/block_scout_web/templates/transaction/overview.html.eex:245 #, elixir-autogen, elixir-format msgid "Address (external or contract) sending the transaction." msgstr "" @@ -346,14 +341,7 @@ msgstr "" msgid "All tokens in the account and total value." msgstr "" -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:41 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:32 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:38 -#, elixir-autogen, elixir-format -msgid "Amount" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:437 +#: lib/block_scout_web/templates/transaction/overview.html.eex:469 #, elixir-autogen, elixir-format msgid "Amount of" msgstr "" @@ -383,11 +371,6 @@ msgstr "" msgid "Average" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:101 -#, elixir-autogen, elixir-format -msgid "Average block time" -msgstr "" - #: lib/block_scout_web/templates/account/api_key/form.html.eex:25 #, elixir-autogen, elixir-format msgid "Back to API keys (Cancel)" @@ -455,17 +438,7 @@ msgstr "" msgid "Base URL:" msgstr "" -#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:2 -#, elixir-autogen, elixir-format -msgid "Beacon chain withdrawals - %{subnetwork} Explorer" -msgstr "" - -#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Beacon chain, Withdrawals, %{subnetwork}, %{coin}" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:472 +#: lib/block_scout_web/templates/transaction/overview.html.eex:545 #, elixir-autogen, elixir-format msgid "Binary data included with the transaction. See input / logs below for additional info." msgstr "" @@ -541,7 +514,7 @@ msgstr "" msgid "Blockchain" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:154 +#: lib/block_scout_web/templates/chain/show.html.eex:156 #: lib/block_scout_web/templates/layout/_topnav.html.eex:34 #: lib/block_scout_web/templates/layout/_topnav.html.eex:38 #, elixir-autogen, elixir-format @@ -758,7 +731,7 @@ msgid "Constructor args" msgstr "" #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:52 -#: lib/block_scout_web/templates/transaction/overview.html.eex:253 +#: lib/block_scout_web/templates/transaction/overview.html.eex:273 #, elixir-autogen, elixir-format msgid "Contract" msgstr "" @@ -783,12 +756,12 @@ msgstr "" msgid "Contract Address Pending" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:483 +#: lib/block_scout_web/views/transaction_view.ex:497 #, elixir-autogen, elixir-format msgid "Contract Call" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:480 +#: lib/block_scout_web/views/transaction_view.ex:494 #, elixir-autogen, elixir-format msgid "Contract Creation" msgstr "" @@ -909,8 +882,8 @@ msgstr "" #: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 #: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:17 #: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:18 -#: lib/block_scout_web/templates/transaction/overview.html.eex:233 -#: lib/block_scout_web/templates/transaction/overview.html.eex:234 +#: lib/block_scout_web/templates/transaction/overview.html.eex:253 +#: lib/block_scout_web/templates/transaction/overview.html.eex:254 #, elixir-autogen, elixir-format msgid "Copy From Address" msgstr "" @@ -945,10 +918,10 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:34 #: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:35 -#: lib/block_scout_web/templates/transaction/overview.html.eex:260 -#: lib/block_scout_web/templates/transaction/overview.html.eex:261 -#: lib/block_scout_web/templates/transaction/overview.html.eex:268 -#: lib/block_scout_web/templates/transaction/overview.html.eex:269 +#: lib/block_scout_web/templates/transaction/overview.html.eex:280 +#: lib/block_scout_web/templates/transaction/overview.html.eex:281 +#: lib/block_scout_web/templates/transaction/overview.html.eex:288 +#: lib/block_scout_web/templates/transaction/overview.html.eex:289 #, elixir-autogen, elixir-format msgid "Copy To Address" msgstr "" @@ -969,20 +942,20 @@ msgstr "" msgid "Copy Txn Hash" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:498 +#: lib/block_scout_web/templates/transaction/overview.html.eex:571 #, elixir-autogen, elixir-format msgid "Copy Txn Hex Input" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:504 +#: lib/block_scout_web/templates/transaction/overview.html.eex:577 #, elixir-autogen, elixir-format msgid "Copy Txn UTF-8 Input" msgstr "" #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:20 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:41 -#: lib/block_scout_web/templates/transaction/overview.html.eex:497 -#: lib/block_scout_web/templates/transaction/overview.html.eex:503 +#: lib/block_scout_web/templates/transaction/overview.html.eex:570 +#: lib/block_scout_web/templates/transaction/overview.html.eex:576 #: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:8 #, elixir-autogen, elixir-format msgid "Copy Value" @@ -1411,7 +1384,7 @@ msgstr "" #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:38 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:40 #: lib/block_scout_web/templates/address_transaction/index.html.eex:34 -#: lib/block_scout_web/templates/transaction/overview.html.eex:226 +#: lib/block_scout_web/templates/transaction/overview.html.eex:246 #: lib/block_scout_web/views/address_internal_transaction_view.ex:10 #: lib/block_scout_web/views/address_token_transfer_view.ex:10 #: lib/block_scout_web/views/address_transaction_view.ex:10 @@ -1426,16 +1399,11 @@ msgstr "" #: lib/block_scout_web/templates/block/_tile.html.eex:67 #: lib/block_scout_web/templates/block/overview.html.eex:187 -#: lib/block_scout_web/templates/transaction/overview.html.eex:399 +#: lib/block_scout_web/templates/transaction/overview.html.eex:430 #, elixir-autogen, elixir-format msgid "Gas Limit" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:379 -#, elixir-autogen, elixir-format -msgid "Gas Price" -msgstr "" - #: lib/block_scout_web/templates/address/overview.html.eex:240 #: lib/block_scout_web/templates/block/_tile.html.eex:73 #: lib/block_scout_web/templates/block/overview.html.eex:178 @@ -1443,11 +1411,6 @@ msgstr "" msgid "Gas Used" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:452 -#, elixir-autogen, elixir-format -msgid "Gas Used by Transaction" -msgstr "" - #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:3 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:18 #, elixir-autogen, elixir-format @@ -1495,13 +1458,13 @@ msgstr "" msgid "Hash" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:480 -#: lib/block_scout_web/templates/transaction/overview.html.eex:484 +#: lib/block_scout_web/templates/transaction/overview.html.eex:553 +#: lib/block_scout_web/templates/transaction/overview.html.eex:557 #, elixir-autogen, elixir-format msgid "Hex (Default)" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:204 +#: lib/block_scout_web/templates/transaction/overview.html.eex:224 #, elixir-autogen, elixir-format msgid "Highlighted events of the transaction." msgstr "" @@ -1560,14 +1523,7 @@ msgstr "" msgid "Incoming" msgstr "" -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:29 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:23 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:23 -#, elixir-autogen, elixir-format -msgid "Index" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:464 +#: lib/block_scout_web/templates/transaction/overview.html.eex:537 #, elixir-autogen, elixir-format msgid "Index position of Transaction in the block." msgstr "" @@ -1587,7 +1543,7 @@ msgstr "" msgid "Input" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:245 +#: lib/block_scout_web/templates/transaction/overview.html.eex:265 #, elixir-autogen, elixir-format msgid "Interacted With (To)" msgstr "" @@ -1602,7 +1558,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:538 +#: lib/block_scout_web/views/transaction_view.ex:552 #, elixir-autogen, elixir-format msgid "Internal Transactions" msgstr "" @@ -1662,22 +1618,22 @@ msgstr "" msgid "License ID" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:331 +#: lib/block_scout_web/templates/transaction/overview.html.eex:351 #, elixir-autogen, elixir-format msgid "List of ERC-1155 tokens created in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:315 +#: lib/block_scout_web/templates/transaction/overview.html.eex:335 #, elixir-autogen, elixir-format msgid "List of token burnt in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:298 +#: lib/block_scout_web/templates/transaction/overview.html.eex:318 #, elixir-autogen, elixir-format msgid "List of token minted in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:282 +#: lib/block_scout_web/templates/transaction/overview.html.eex:302 #, elixir-autogen, elixir-format msgid "List of token transferred in the transaction." msgstr "" @@ -1719,7 +1675,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:539 +#: lib/block_scout_web/views/transaction_view.ex:553 #, elixir-autogen, elixir-format msgid "Logs" msgstr "" @@ -1742,12 +1698,12 @@ msgstr "" msgid "Market cap" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:408 +#: lib/block_scout_web/templates/transaction/overview.html.eex:440 #, elixir-autogen, elixir-format msgid "Max Fee per Gas" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:418 +#: lib/block_scout_web/templates/transaction/overview.html.eex:450 #, elixir-autogen, elixir-format msgid "Max Priority Fee per Gas" msgstr "" @@ -1757,12 +1713,7 @@ msgstr "" msgid "Max of" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:398 -#, elixir-autogen, elixir-format -msgid "Maximum gas amount approved for the transaction." -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:407 +#: lib/block_scout_web/templates/transaction/overview.html.eex:439 #, elixir-autogen, elixir-format msgid "Maximum total amount per unit of gas a user is willing to pay for a transaction, including base fee and priority fee." msgstr "" @@ -1824,7 +1775,7 @@ msgid "More internal transactions have come in" msgstr "" #: lib/block_scout_web/templates/address_transaction/index.html.eex:46 -#: lib/block_scout_web/templates/chain/show.html.eex:217 +#: lib/block_scout_web/templates/chain/show.html.eex:219 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:13 #: lib/block_scout_web/templates/transaction/index.html.eex:19 #, elixir-autogen, elixir-format @@ -1944,7 +1895,7 @@ msgid "No trace entries found." msgstr "" #: lib/block_scout_web/templates/block/overview.html.eex:196 -#: lib/block_scout_web/templates/transaction/overview.html.eex:462 +#: lib/block_scout_web/templates/transaction/overview.html.eex:535 #, elixir-autogen, elixir-format msgid "Nonce" msgstr "" @@ -2065,7 +2016,8 @@ msgstr "" #: lib/block_scout_web/templates/layout/_topnav.html.eex:63 #: lib/block_scout_web/views/transaction_view.ex:373 -#: lib/block_scout_web/views/transaction_view.ex:412 +#: lib/block_scout_web/views/transaction_view.ex:419 +#: lib/block_scout_web/views/transaction_view.ex:427 #, elixir-autogen, elixir-format msgid "Pending" msgstr "" @@ -2097,7 +2049,7 @@ msgstr "" msgid "Please select what types of notifications you will receive:" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:464 +#: lib/block_scout_web/templates/transaction/overview.html.eex:537 #, elixir-autogen, elixir-format msgid "Position" msgstr "" @@ -2134,13 +2086,8 @@ msgstr "" msgid "Price per token on the exchanges" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:378 -#, elixir-autogen, elixir-format -msgid "Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage." -msgstr "" - #: lib/block_scout_web/templates/block/overview.html.eex:225 -#: lib/block_scout_web/templates/transaction/overview.html.eex:428 +#: lib/block_scout_web/templates/transaction/overview.html.eex:460 #, elixir-autogen, elixir-format msgid "Priority Fee / Tip" msgstr "" @@ -2194,14 +2141,14 @@ msgstr "" msgid "RPC" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:473 +#: lib/block_scout_web/templates/transaction/overview.html.eex:546 #, elixir-autogen, elixir-format msgid "Raw Input" 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:540 +#: lib/block_scout_web/views/transaction_view.ex:554 #, elixir-autogen, elixir-format msgid "Raw Trace" msgstr "" @@ -2319,7 +2266,7 @@ msgstr "" msgid "Save" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:206 +#: lib/block_scout_web/templates/transaction/overview.html.eex:226 #, elixir-autogen, elixir-format msgid "Scroll to see more" msgstr "" @@ -2459,7 +2406,7 @@ msgstr "" #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:20 #: lib/block_scout_web/templates/block_transaction/index.html.eex:14 #: lib/block_scout_web/templates/block_withdrawal/index.html.eex:14 -#: lib/block_scout_web/templates/chain/show.html.eex:158 +#: lib/block_scout_web/templates/chain/show.html.eex:160 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:18 #: lib/block_scout_web/templates/tokens/holder/index.html.eex:24 #: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:23 @@ -2477,7 +2424,7 @@ msgstr "" msgid "Something went wrong, click to reload." msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:223 +#: lib/block_scout_web/templates/chain/show.html.eex:225 #, elixir-autogen, elixir-format msgid "Something went wrong, click to retry." msgstr "" @@ -2514,7 +2461,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:541 +#: lib/block_scout_web/views/transaction_view.ex:555 #, elixir-autogen, elixir-format msgid "State changes" msgstr "" @@ -2551,11 +2498,6 @@ msgstr "" msgid "TX Fee" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:31 -#, elixir-autogen, elixir-format -msgid "Telegram" -msgstr "" - #: lib/block_scout_web/templates/layout/_footer.html.eex:67 #, elixir-autogen, elixir-format msgid "Test Networks" @@ -2816,7 +2758,7 @@ msgstr "" #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34 #: lib/block_scout_web/templates/address_transaction/index.html.eex:28 #: lib/block_scout_web/templates/block_withdrawal/index.html.eex:29 -#: lib/block_scout_web/templates/transaction/overview.html.eex:247 +#: lib/block_scout_web/templates/transaction/overview.html.eex:267 #: lib/block_scout_web/templates/withdrawal/index.html.eex:32 #: lib/block_scout_web/views/address_internal_transaction_view.ex:9 #: lib/block_scout_web/views/address_token_transfer_view.ex:9 @@ -2849,13 +2791,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:474 +#: lib/block_scout_web/views/transaction_view.ex:488 #, 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:475 +#: lib/block_scout_web/views/transaction_view.ex:489 #, elixir-autogen, elixir-format msgid "Token Creation" msgstr "" @@ -2883,14 +2825,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:473 +#: lib/block_scout_web/views/transaction_view.ex:487 #, 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:476 +#: lib/block_scout_web/views/transaction_view.ex:490 #, elixir-autogen, elixir-format msgid "Token Transfer" msgstr "" @@ -2906,7 +2848,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:537 +#: lib/block_scout_web/views/transaction_view.ex:551 #, elixir-autogen, elixir-format msgid "Token Transfers" msgstr "" @@ -2932,22 +2874,22 @@ msgstr "" msgid "Tokens" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:316 +#: lib/block_scout_web/templates/transaction/overview.html.eex:336 #, elixir-autogen, elixir-format msgid "Tokens Burnt" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:332 +#: lib/block_scout_web/templates/transaction/overview.html.eex:352 #, elixir-autogen, elixir-format msgid "Tokens Created" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:299 +#: lib/block_scout_web/templates/transaction/overview.html.eex:319 #, elixir-autogen, elixir-format msgid "Tokens Minted" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:283 +#: lib/block_scout_web/templates/transaction/overview.html.eex:303 #, elixir-autogen, elixir-format msgid "Tokens Transferred" msgstr "" @@ -2989,7 +2931,7 @@ msgstr "" msgid "Total Supply * Price" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:131 +#: lib/block_scout_web/templates/chain/show.html.eex:133 #, elixir-autogen, elixir-format msgid "Total blocks" msgstr "" @@ -3009,12 +2951,12 @@ msgstr "" msgid "Total supply" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:363 +#: lib/block_scout_web/templates/transaction/overview.html.eex:383 #, elixir-autogen, elixir-format msgid "Total transaction fee." msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:110 +#: lib/block_scout_web/templates/chain/show.html.eex:112 #, elixir-autogen, elixir-format msgid "Total transactions" msgstr "" @@ -3022,7 +2964,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:486 +#: lib/block_scout_web/views/transaction_view.ex:500 #, elixir-autogen, elixir-format msgid "Transaction" msgstr "" @@ -3037,12 +2979,7 @@ msgstr "" msgid "Transaction %{transaction}, %{subnetwork} %{transaction}" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:205 -#, elixir-autogen, elixir-format -msgid "Transaction Action" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:438 +#: lib/block_scout_web/templates/transaction/overview.html.eex:470 #, elixir-autogen, elixir-format msgid "Transaction Burnt Fee" msgstr "" @@ -3052,7 +2989,7 @@ msgstr "" msgid "Transaction Details" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:364 +#: lib/block_scout_web/templates/transaction/overview.html.eex:384 #, elixir-autogen, elixir-format msgid "Transaction Fee" msgstr "" @@ -3075,17 +3012,17 @@ msgstr "" msgid "Transaction Tags" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:388 +#: lib/block_scout_web/templates/transaction/overview.html.eex:414 #, elixir-autogen, elixir-format msgid "Transaction Type" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:461 +#: lib/block_scout_web/templates/transaction/overview.html.eex:534 #, elixir-autogen, elixir-format msgid "Transaction number from the sending address. Each transaction sent from an address increments the nonce by 1." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:387 +#: lib/block_scout_web/templates/transaction/overview.html.eex:413 #, elixir-autogen, elixir-format msgid "Transaction type, introduced in EIP-2718." msgstr "" @@ -3098,7 +3035,7 @@ msgstr "" #: lib/block_scout_web/templates/address_transaction/index.html.eex:13 #: lib/block_scout_web/templates/block/_tabs.html.eex:4 #: lib/block_scout_web/templates/block/overview.html.eex:80 -#: lib/block_scout_web/templates/chain/show.html.eex:214 +#: lib/block_scout_web/templates/chain/show.html.eex:216 #: lib/block_scout_web/templates/layout/_topnav.html.eex:49 #: lib/block_scout_web/views/address_view.ex:377 #, elixir-autogen, elixir-format @@ -3161,7 +3098,7 @@ msgstr "" msgid "UML diagram" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:487 +#: lib/block_scout_web/templates/transaction/overview.html.eex:560 #, elixir-autogen, elixir-format msgid "UTF-8" msgstr "" @@ -3198,12 +3135,12 @@ msgstr "" msgid "Update" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:417 +#: lib/block_scout_web/templates/transaction/overview.html.eex:449 #, elixir-autogen, elixir-format msgid "User defined maximum fee (tip) per unit of gas paid to validator for transaction prioritization." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:427 +#: lib/block_scout_web/templates/transaction/overview.html.eex:459 #, elixir-autogen, elixir-format msgid "User-defined tip sent to validator for transaction priority/inclusion." msgstr "" @@ -3238,19 +3175,12 @@ msgstr "" msgid "Validator Name" msgstr "" -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:32 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:26 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:26 -#, elixir-autogen, elixir-format -msgid "Validator index" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:349 +#: lib/block_scout_web/templates/transaction/overview.html.eex:369 #, elixir-autogen, elixir-format msgid "Value" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:348 +#: lib/block_scout_web/templates/transaction/overview.html.eex:368 #, elixir-autogen, elixir-format msgid "Value sent in the native token (and USD) if applicable." msgstr "" @@ -3339,12 +3269,12 @@ msgstr "" msgid "Via multi-part files" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:153 +#: lib/block_scout_web/templates/chain/show.html.eex:155 #, elixir-autogen, elixir-format msgid "View All Blocks" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:213 +#: lib/block_scout_web/templates/chain/show.html.eex:215 #, elixir-autogen, elixir-format msgid "View All Transactions" msgstr "" @@ -3422,7 +3352,7 @@ msgstr "" msgid "Waiting for transaction's confirmation..." msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:139 +#: lib/block_scout_web/templates/chain/show.html.eex:141 #, elixir-autogen, elixir-format msgid "Wallet addresses" msgstr "" @@ -3546,7 +3476,7 @@ msgstr "" msgid "button" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:256 +#: lib/block_scout_web/templates/transaction/overview.html.eex:276 #, elixir-autogen, elixir-format msgid "created" msgstr "" @@ -3637,6 +3567,88 @@ msgstr "" msgid "truffle flattener" msgstr "" +#: lib/block_scout_web/templates/transaction/overview.html.eex:484 +#, elixir-autogen, elixir-format +msgid "Actual gas amount used by the transaction on L2." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:203 +#, elixir-autogen, elixir-format +msgid "Block number containing the transaction on L1." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:204 +#, elixir-autogen, elixir-format +msgid "L1 Block" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:523 +#: lib/block_scout_web/templates/transaction/overview.html.eex:524 +#, elixir-autogen, elixir-format +msgid "L1 Fee Scalar" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:512 +#: lib/block_scout_web/templates/transaction/overview.html.eex:513 +#, elixir-autogen, elixir-format +msgid "L1 Gas Price" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:501 +#: lib/block_scout_web/templates/transaction/overview.html.eex:502 +#, elixir-autogen, elixir-format +msgid "L1 Gas Used by Transaction" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:426 +#, elixir-autogen, elixir-format +msgid "L2 Gas Limit" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:400 +#, elixir-autogen, elixir-format +msgid "L2 Gas Price" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:485 +#, elixir-autogen, elixir-format +msgid "L2 Gas Used by Transaction" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:425 +#, elixir-autogen, elixir-format +msgid "Maximum gas amount approved for the transaction on L2." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:399 +#, elixir-autogen, elixir-format +msgid "Price per unit of gas specified by the sender on L2. Higher gas prices can prioritize transaction inclusion during times of high usage." +msgstr "" + +#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:41 +#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:32 +#: lib/block_scout_web/templates/withdrawal/index.html.eex:38 +#, elixir-autogen, elixir-format +msgid "Amount" +msgstr "" + +#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:2 +#, elixir-autogen, elixir-format +msgid "Beacon chain withdrawals - %{subnetwork} Explorer" +msgstr "" + +#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Beacon chain, Withdrawals, %{subnetwork}, %{coin}" +msgstr "" + +#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:29 +#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:23 +#: lib/block_scout_web/templates/withdrawal/index.html.eex:23 +#, elixir-autogen, elixir-format +msgid "Index" +msgstr "" + #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:9 #, elixir-autogen, elixir-format msgid "New Smart Contract Verification via Standard input JSON" @@ -3647,6 +3659,23 @@ msgstr "" msgid "New Smart Contract Verification via metadata JSON" msgstr "" +#: lib/block_scout_web/templates/layout/_footer.html.eex:31 +#, elixir-autogen, elixir-format +msgid "Telegram" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:225 +#, elixir-autogen, elixir-format +msgid "Transaction Action" +msgstr "" + +#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:32 +#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:26 +#: lib/block_scout_web/templates/withdrawal/index.html.eex:26 +#, elixir-autogen, elixir-format +msgid "Validator index" +msgstr "" + #: lib/block_scout_web/templates/withdrawal/index.html.eex:11 #, elixir-autogen, elixir-format msgid "%{withdrawals_count} withdrawals processed and %{withdrawals_sum} withdrawn." @@ -3673,7 +3702,7 @@ msgstr "" msgid "Yul" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:437 +#: lib/block_scout_web/templates/transaction/overview.html.eex:469 #, elixir-autogen, elixir-format msgid "burnt for this transaction. Equals Block Base Fee per Gas * Gas Used." msgstr "" @@ -3682,3 +3711,33 @@ msgstr "" #, elixir-autogen, elixir-format msgid "burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used)." msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:488 +#, elixir-autogen, elixir-format +msgid "Actual gas amount used by the transaction." +msgstr "" + +#: lib/block_scout_web/templates/chain/show.html.eex:102 +#, elixir-autogen, elixir-format +msgid "Average block time" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:404 +#, elixir-autogen, elixir-format +msgid "Gas Price" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:489 +#, elixir-autogen, elixir-format +msgid "Gas Used by Transaction" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:429 +#, elixir-autogen, elixir-format +msgid "Maximum gas amount approved for the transaction." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:403 +#, elixir-autogen, elixir-format +msgid "Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage." +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index c36ed9283cf0..47059226f782 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -199,11 +199,6 @@ msgstr "" msgid "Actions" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:451 -#, elixir-autogen, elixir-format -msgid "Actual gas amount used by the transaction." -msgstr "" - #: lib/block_scout_web/templates/account/api_key/form.html.eex:7 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 #: lib/block_scout_web/templates/layout/_add_chain_to_mm.html.eex:10 @@ -270,12 +265,12 @@ msgstr "" msgid "Address" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:243 +#: lib/block_scout_web/templates/transaction/overview.html.eex:263 #, elixir-autogen, elixir-format msgid "Address (external or contract) receiving the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:225 +#: lib/block_scout_web/templates/transaction/overview.html.eex:245 #, elixir-autogen, elixir-format msgid "Address (external or contract) sending the transaction." msgstr "" @@ -346,14 +341,7 @@ msgstr "" msgid "All tokens in the account and total value." msgstr "" -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:41 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:32 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:38 -#, elixir-autogen, elixir-format -msgid "Amount" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:437 +#: lib/block_scout_web/templates/transaction/overview.html.eex:469 #, elixir-autogen, elixir-format msgid "Amount of" msgstr "" @@ -383,11 +371,6 @@ msgstr "" msgid "Average" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:101 -#, elixir-autogen, elixir-format -msgid "Average block time" -msgstr "" - #: lib/block_scout_web/templates/account/api_key/form.html.eex:25 #, elixir-autogen, elixir-format msgid "Back to API keys (Cancel)" @@ -455,17 +438,7 @@ msgstr "" msgid "Base URL:" msgstr "" -#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:2 -#, elixir-autogen, elixir-format -msgid "Beacon chain withdrawals - %{subnetwork} Explorer" -msgstr "" - -#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Beacon chain, Withdrawals, %{subnetwork}, %{coin}" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:472 +#: lib/block_scout_web/templates/transaction/overview.html.eex:545 #, elixir-autogen, elixir-format msgid "Binary data included with the transaction. See input / logs below for additional info." msgstr "" @@ -541,7 +514,7 @@ msgstr "" msgid "Blockchain" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:154 +#: lib/block_scout_web/templates/chain/show.html.eex:156 #: lib/block_scout_web/templates/layout/_topnav.html.eex:34 #: lib/block_scout_web/templates/layout/_topnav.html.eex:38 #, elixir-autogen, elixir-format @@ -758,7 +731,7 @@ msgid "Constructor args" msgstr "" #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:52 -#: lib/block_scout_web/templates/transaction/overview.html.eex:253 +#: lib/block_scout_web/templates/transaction/overview.html.eex:273 #, elixir-autogen, elixir-format msgid "Contract" msgstr "" @@ -783,12 +756,12 @@ msgstr "" msgid "Contract Address Pending" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:483 +#: lib/block_scout_web/views/transaction_view.ex:497 #, elixir-autogen, elixir-format msgid "Contract Call" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:480 +#: lib/block_scout_web/views/transaction_view.ex:494 #, elixir-autogen, elixir-format msgid "Contract Creation" msgstr "" @@ -909,8 +882,8 @@ msgstr "" #: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 #: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:17 #: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:18 -#: lib/block_scout_web/templates/transaction/overview.html.eex:233 -#: lib/block_scout_web/templates/transaction/overview.html.eex:234 +#: lib/block_scout_web/templates/transaction/overview.html.eex:253 +#: lib/block_scout_web/templates/transaction/overview.html.eex:254 #, elixir-autogen, elixir-format msgid "Copy From Address" msgstr "" @@ -945,10 +918,10 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:34 #: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:35 -#: lib/block_scout_web/templates/transaction/overview.html.eex:260 -#: lib/block_scout_web/templates/transaction/overview.html.eex:261 -#: lib/block_scout_web/templates/transaction/overview.html.eex:268 -#: lib/block_scout_web/templates/transaction/overview.html.eex:269 +#: lib/block_scout_web/templates/transaction/overview.html.eex:280 +#: lib/block_scout_web/templates/transaction/overview.html.eex:281 +#: lib/block_scout_web/templates/transaction/overview.html.eex:288 +#: lib/block_scout_web/templates/transaction/overview.html.eex:289 #, elixir-autogen, elixir-format msgid "Copy To Address" msgstr "" @@ -969,20 +942,20 @@ msgstr "" msgid "Copy Txn Hash" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:498 +#: lib/block_scout_web/templates/transaction/overview.html.eex:571 #, elixir-autogen, elixir-format msgid "Copy Txn Hex Input" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:504 +#: lib/block_scout_web/templates/transaction/overview.html.eex:577 #, elixir-autogen, elixir-format msgid "Copy Txn UTF-8 Input" msgstr "" #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:20 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:41 -#: lib/block_scout_web/templates/transaction/overview.html.eex:497 -#: lib/block_scout_web/templates/transaction/overview.html.eex:503 +#: lib/block_scout_web/templates/transaction/overview.html.eex:570 +#: lib/block_scout_web/templates/transaction/overview.html.eex:576 #: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:8 #, elixir-autogen, elixir-format msgid "Copy Value" @@ -1411,7 +1384,7 @@ msgstr "" #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:38 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:40 #: lib/block_scout_web/templates/address_transaction/index.html.eex:34 -#: lib/block_scout_web/templates/transaction/overview.html.eex:226 +#: lib/block_scout_web/templates/transaction/overview.html.eex:246 #: lib/block_scout_web/views/address_internal_transaction_view.ex:10 #: lib/block_scout_web/views/address_token_transfer_view.ex:10 #: lib/block_scout_web/views/address_transaction_view.ex:10 @@ -1426,16 +1399,11 @@ msgstr "" #: lib/block_scout_web/templates/block/_tile.html.eex:67 #: lib/block_scout_web/templates/block/overview.html.eex:187 -#: lib/block_scout_web/templates/transaction/overview.html.eex:399 +#: lib/block_scout_web/templates/transaction/overview.html.eex:430 #, elixir-autogen, elixir-format msgid "Gas Limit" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:379 -#, elixir-autogen, elixir-format -msgid "Gas Price" -msgstr "" - #: lib/block_scout_web/templates/address/overview.html.eex:240 #: lib/block_scout_web/templates/block/_tile.html.eex:73 #: lib/block_scout_web/templates/block/overview.html.eex:178 @@ -1443,11 +1411,6 @@ msgstr "" msgid "Gas Used" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:452 -#, elixir-autogen, elixir-format -msgid "Gas Used by Transaction" -msgstr "" - #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:3 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:18 #, elixir-autogen, elixir-format @@ -1495,13 +1458,13 @@ msgstr "" msgid "Hash" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:480 -#: lib/block_scout_web/templates/transaction/overview.html.eex:484 +#: lib/block_scout_web/templates/transaction/overview.html.eex:553 +#: lib/block_scout_web/templates/transaction/overview.html.eex:557 #, elixir-autogen, elixir-format msgid "Hex (Default)" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:204 +#: lib/block_scout_web/templates/transaction/overview.html.eex:224 #, elixir-autogen, elixir-format msgid "Highlighted events of the transaction." msgstr "" @@ -1560,14 +1523,7 @@ msgstr "" msgid "Incoming" msgstr "" -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:29 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:23 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:23 -#, elixir-autogen, elixir-format -msgid "Index" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:464 +#: lib/block_scout_web/templates/transaction/overview.html.eex:537 #, elixir-autogen, elixir-format msgid "Index position of Transaction in the block." msgstr "" @@ -1587,7 +1543,7 @@ msgstr "" msgid "Input" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:245 +#: lib/block_scout_web/templates/transaction/overview.html.eex:265 #, elixir-autogen, elixir-format msgid "Interacted With (To)" msgstr "" @@ -1602,7 +1558,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:538 +#: lib/block_scout_web/views/transaction_view.ex:552 #, elixir-autogen, elixir-format msgid "Internal Transactions" msgstr "" @@ -1662,22 +1618,22 @@ msgstr "" msgid "License ID" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:331 +#: lib/block_scout_web/templates/transaction/overview.html.eex:351 #, elixir-autogen, elixir-format msgid "List of ERC-1155 tokens created in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:315 +#: lib/block_scout_web/templates/transaction/overview.html.eex:335 #, elixir-autogen, elixir-format msgid "List of token burnt in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:298 +#: lib/block_scout_web/templates/transaction/overview.html.eex:318 #, elixir-autogen, elixir-format msgid "List of token minted in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:282 +#: lib/block_scout_web/templates/transaction/overview.html.eex:302 #, elixir-autogen, elixir-format msgid "List of token transferred in the transaction." msgstr "" @@ -1719,7 +1675,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:539 +#: lib/block_scout_web/views/transaction_view.ex:553 #, elixir-autogen, elixir-format msgid "Logs" msgstr "" @@ -1742,12 +1698,12 @@ msgstr "" msgid "Market cap" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:408 +#: lib/block_scout_web/templates/transaction/overview.html.eex:440 #, elixir-autogen, elixir-format msgid "Max Fee per Gas" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:418 +#: lib/block_scout_web/templates/transaction/overview.html.eex:450 #, elixir-autogen, elixir-format msgid "Max Priority Fee per Gas" msgstr "" @@ -1757,12 +1713,7 @@ msgstr "" msgid "Max of" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:398 -#, elixir-autogen, elixir-format -msgid "Maximum gas amount approved for the transaction." -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:407 +#: lib/block_scout_web/templates/transaction/overview.html.eex:439 #, elixir-autogen, elixir-format msgid "Maximum total amount per unit of gas a user is willing to pay for a transaction, including base fee and priority fee." msgstr "" @@ -1824,7 +1775,7 @@ msgid "More internal transactions have come in" msgstr "" #: lib/block_scout_web/templates/address_transaction/index.html.eex:46 -#: lib/block_scout_web/templates/chain/show.html.eex:217 +#: lib/block_scout_web/templates/chain/show.html.eex:219 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:13 #: lib/block_scout_web/templates/transaction/index.html.eex:19 #, elixir-autogen, elixir-format @@ -1944,7 +1895,7 @@ msgid "No trace entries found." msgstr "" #: lib/block_scout_web/templates/block/overview.html.eex:196 -#: lib/block_scout_web/templates/transaction/overview.html.eex:462 +#: lib/block_scout_web/templates/transaction/overview.html.eex:535 #, elixir-autogen, elixir-format msgid "Nonce" msgstr "" @@ -2065,7 +2016,8 @@ msgstr "" #: lib/block_scout_web/templates/layout/_topnav.html.eex:63 #: lib/block_scout_web/views/transaction_view.ex:373 -#: lib/block_scout_web/views/transaction_view.ex:412 +#: lib/block_scout_web/views/transaction_view.ex:419 +#: lib/block_scout_web/views/transaction_view.ex:427 #, elixir-autogen, elixir-format msgid "Pending" msgstr "" @@ -2097,7 +2049,7 @@ msgstr "" msgid "Please select what types of notifications you will receive:" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:464 +#: lib/block_scout_web/templates/transaction/overview.html.eex:537 #, elixir-autogen, elixir-format msgid "Position" msgstr "" @@ -2134,13 +2086,8 @@ msgstr "" msgid "Price per token on the exchanges" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:378 -#, elixir-autogen, elixir-format -msgid "Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage." -msgstr "" - #: lib/block_scout_web/templates/block/overview.html.eex:225 -#: lib/block_scout_web/templates/transaction/overview.html.eex:428 +#: lib/block_scout_web/templates/transaction/overview.html.eex:460 #, elixir-autogen, elixir-format msgid "Priority Fee / Tip" msgstr "" @@ -2194,14 +2141,14 @@ msgstr "" msgid "RPC" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:473 +#: lib/block_scout_web/templates/transaction/overview.html.eex:546 #, elixir-autogen, elixir-format msgid "Raw Input" 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:540 +#: lib/block_scout_web/views/transaction_view.ex:554 #, elixir-autogen, elixir-format msgid "Raw Trace" msgstr "" @@ -2319,7 +2266,7 @@ msgstr "" msgid "Save" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:206 +#: lib/block_scout_web/templates/transaction/overview.html.eex:226 #, elixir-autogen, elixir-format msgid "Scroll to see more" msgstr "" @@ -2459,7 +2406,7 @@ msgstr "" #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:20 #: lib/block_scout_web/templates/block_transaction/index.html.eex:14 #: lib/block_scout_web/templates/block_withdrawal/index.html.eex:14 -#: lib/block_scout_web/templates/chain/show.html.eex:158 +#: lib/block_scout_web/templates/chain/show.html.eex:160 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:18 #: lib/block_scout_web/templates/tokens/holder/index.html.eex:24 #: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:23 @@ -2477,7 +2424,7 @@ msgstr "" msgid "Something went wrong, click to reload." msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:223 +#: lib/block_scout_web/templates/chain/show.html.eex:225 #, elixir-autogen, elixir-format msgid "Something went wrong, click to retry." msgstr "" @@ -2514,7 +2461,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:541 +#: lib/block_scout_web/views/transaction_view.ex:555 #, elixir-autogen, elixir-format msgid "State changes" msgstr "" @@ -2551,11 +2498,6 @@ msgstr "" msgid "TX Fee" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:31 -#, elixir-autogen, elixir-format -msgid "Telegram" -msgstr "" - #: lib/block_scout_web/templates/layout/_footer.html.eex:67 #, elixir-autogen, elixir-format msgid "Test Networks" @@ -2816,7 +2758,7 @@ msgstr "" #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34 #: lib/block_scout_web/templates/address_transaction/index.html.eex:28 #: lib/block_scout_web/templates/block_withdrawal/index.html.eex:29 -#: lib/block_scout_web/templates/transaction/overview.html.eex:247 +#: lib/block_scout_web/templates/transaction/overview.html.eex:267 #: lib/block_scout_web/templates/withdrawal/index.html.eex:32 #: lib/block_scout_web/views/address_internal_transaction_view.ex:9 #: lib/block_scout_web/views/address_token_transfer_view.ex:9 @@ -2849,13 +2791,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:474 +#: lib/block_scout_web/views/transaction_view.ex:488 #, 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:475 +#: lib/block_scout_web/views/transaction_view.ex:489 #, elixir-autogen, elixir-format msgid "Token Creation" msgstr "" @@ -2883,14 +2825,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:473 +#: lib/block_scout_web/views/transaction_view.ex:487 #, 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:476 +#: lib/block_scout_web/views/transaction_view.ex:490 #, elixir-autogen, elixir-format msgid "Token Transfer" msgstr "" @@ -2906,7 +2848,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:537 +#: lib/block_scout_web/views/transaction_view.ex:551 #, elixir-autogen, elixir-format msgid "Token Transfers" msgstr "" @@ -2932,22 +2874,22 @@ msgstr "" msgid "Tokens" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:316 +#: lib/block_scout_web/templates/transaction/overview.html.eex:336 #, elixir-autogen, elixir-format msgid "Tokens Burnt" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:332 +#: lib/block_scout_web/templates/transaction/overview.html.eex:352 #, elixir-autogen, elixir-format msgid "Tokens Created" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:299 +#: lib/block_scout_web/templates/transaction/overview.html.eex:319 #, elixir-autogen, elixir-format msgid "Tokens Minted" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:283 +#: lib/block_scout_web/templates/transaction/overview.html.eex:303 #, elixir-autogen, elixir-format msgid "Tokens Transferred" msgstr "" @@ -2989,7 +2931,7 @@ msgstr "" msgid "Total Supply * Price" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:131 +#: lib/block_scout_web/templates/chain/show.html.eex:133 #, elixir-autogen, elixir-format msgid "Total blocks" msgstr "" @@ -3009,12 +2951,12 @@ msgstr "" msgid "Total supply" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:363 +#: lib/block_scout_web/templates/transaction/overview.html.eex:383 #, elixir-autogen, elixir-format msgid "Total transaction fee." msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:110 +#: lib/block_scout_web/templates/chain/show.html.eex:112 #, elixir-autogen, elixir-format msgid "Total transactions" msgstr "" @@ -3022,7 +2964,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:486 +#: lib/block_scout_web/views/transaction_view.ex:500 #, elixir-autogen, elixir-format msgid "Transaction" msgstr "" @@ -3037,12 +2979,7 @@ msgstr "" msgid "Transaction %{transaction}, %{subnetwork} %{transaction}" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:205 -#, elixir-autogen, elixir-format -msgid "Transaction Action" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:438 +#: lib/block_scout_web/templates/transaction/overview.html.eex:470 #, elixir-autogen, elixir-format msgid "Transaction Burnt Fee" msgstr "" @@ -3052,7 +2989,7 @@ msgstr "" msgid "Transaction Details" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:364 +#: lib/block_scout_web/templates/transaction/overview.html.eex:384 #, elixir-autogen, elixir-format msgid "Transaction Fee" msgstr "" @@ -3075,17 +3012,17 @@ msgstr "" msgid "Transaction Tags" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:388 +#: lib/block_scout_web/templates/transaction/overview.html.eex:414 #, elixir-autogen, elixir-format msgid "Transaction Type" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:461 +#: lib/block_scout_web/templates/transaction/overview.html.eex:534 #, elixir-autogen, elixir-format msgid "Transaction number from the sending address. Each transaction sent from an address increments the nonce by 1." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:387 +#: lib/block_scout_web/templates/transaction/overview.html.eex:413 #, elixir-autogen, elixir-format msgid "Transaction type, introduced in EIP-2718." msgstr "" @@ -3098,7 +3035,7 @@ msgstr "" #: lib/block_scout_web/templates/address_transaction/index.html.eex:13 #: lib/block_scout_web/templates/block/_tabs.html.eex:4 #: lib/block_scout_web/templates/block/overview.html.eex:80 -#: lib/block_scout_web/templates/chain/show.html.eex:214 +#: lib/block_scout_web/templates/chain/show.html.eex:216 #: lib/block_scout_web/templates/layout/_topnav.html.eex:49 #: lib/block_scout_web/views/address_view.ex:377 #, elixir-autogen, elixir-format @@ -3161,7 +3098,7 @@ msgstr "" msgid "UML diagram" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:487 +#: lib/block_scout_web/templates/transaction/overview.html.eex:560 #, elixir-autogen, elixir-format msgid "UTF-8" msgstr "" @@ -3198,12 +3135,12 @@ msgstr "" msgid "Update" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:417 +#: lib/block_scout_web/templates/transaction/overview.html.eex:449 #, elixir-autogen, elixir-format msgid "User defined maximum fee (tip) per unit of gas paid to validator for transaction prioritization." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:427 +#: lib/block_scout_web/templates/transaction/overview.html.eex:459 #, elixir-autogen, elixir-format msgid "User-defined tip sent to validator for transaction priority/inclusion." msgstr "" @@ -3238,19 +3175,12 @@ msgstr "" msgid "Validator Name" msgstr "" -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:32 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:26 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:26 -#, elixir-autogen, elixir-format -msgid "Validator index" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:349 +#: lib/block_scout_web/templates/transaction/overview.html.eex:369 #, elixir-autogen, elixir-format msgid "Value" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:348 +#: lib/block_scout_web/templates/transaction/overview.html.eex:368 #, elixir-autogen, elixir-format msgid "Value sent in the native token (and USD) if applicable." msgstr "" @@ -3339,12 +3269,12 @@ msgstr "" msgid "Via multi-part files" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:153 +#: lib/block_scout_web/templates/chain/show.html.eex:155 #, elixir-autogen, elixir-format msgid "View All Blocks" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:213 +#: lib/block_scout_web/templates/chain/show.html.eex:215 #, elixir-autogen, elixir-format msgid "View All Transactions" msgstr "" @@ -3422,7 +3352,7 @@ msgstr "" msgid "Waiting for transaction's confirmation..." msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:139 +#: lib/block_scout_web/templates/chain/show.html.eex:141 #, elixir-autogen, elixir-format msgid "Wallet addresses" msgstr "" @@ -3546,7 +3476,7 @@ msgstr "" msgid "button" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:256 +#: lib/block_scout_web/templates/transaction/overview.html.eex:276 #, elixir-autogen, elixir-format msgid "created" msgstr "" @@ -3637,6 +3567,88 @@ msgstr "" msgid "truffle flattener" msgstr "" +#: lib/block_scout_web/templates/transaction/overview.html.eex:484 +#, elixir-autogen, elixir-format +msgid "Actual gas amount used by the transaction on L2." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:203 +#, elixir-autogen, elixir-format, fuzzy +msgid "Block number containing the transaction on L1." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:204 +#, elixir-autogen, elixir-format, fuzzy +msgid "L1 Block" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:523 +#: lib/block_scout_web/templates/transaction/overview.html.eex:524 +#, elixir-autogen, elixir-format +msgid "L1 Fee Scalar" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:512 +#: lib/block_scout_web/templates/transaction/overview.html.eex:513 +#, elixir-autogen, elixir-format, fuzzy +msgid "L1 Gas Price" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:501 +#: lib/block_scout_web/templates/transaction/overview.html.eex:502 +#, elixir-autogen, elixir-format +msgid "L1 Gas Used by Transaction" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:426 +#, elixir-autogen, elixir-format, fuzzy +msgid "L2 Gas Limit" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:400 +#, elixir-autogen, elixir-format, fuzzy +msgid "L2 Gas Price" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:485 +#, elixir-autogen, elixir-format +msgid "L2 Gas Used by Transaction" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:425 +#, elixir-autogen, elixir-format +msgid "Maximum gas amount approved for the transaction on L2." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:399 +#, elixir-autogen, elixir-format +msgid "Price per unit of gas specified by the sender on L2. Higher gas prices can prioritize transaction inclusion during times of high usage." +msgstr "" + +#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:41 +#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:32 +#: lib/block_scout_web/templates/withdrawal/index.html.eex:38 +#, elixir-autogen, elixir-format, fuzzy +msgid "Amount" +msgstr "" + +#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:2 +#, elixir-autogen, elixir-format +msgid "Beacon chain withdrawals - %{subnetwork} Explorer" +msgstr "" + +#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:7 +#, elixir-autogen, elixir-format +msgid "Beacon chain, Withdrawals, %{subnetwork}, %{coin}" +msgstr "" + +#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:29 +#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:23 +#: lib/block_scout_web/templates/withdrawal/index.html.eex:23 +#, elixir-autogen, elixir-format, fuzzy +msgid "Index" +msgstr "" + #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:9 #, elixir-autogen, elixir-format, fuzzy msgid "New Smart Contract Verification via Standard input JSON" @@ -3647,8 +3659,25 @@ msgstr "" msgid "New Smart Contract Verification via metadata JSON" msgstr "" -#: lib/block_scout_web/templates/withdrawal/index.html.eex:11 +#: lib/block_scout_web/templates/layout/_footer.html.eex:31 +#, elixir-autogen, elixir-format +msgid "Telegram" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:225 +#, elixir-autogen, elixir-format, fuzzy +msgid "Transaction Action" +msgstr "" + +#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:32 +#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:26 +#: lib/block_scout_web/templates/withdrawal/index.html.eex:26 #, elixir-autogen, elixir-format, fuzzy +msgid "Validator index" +msgstr "" + +#: lib/block_scout_web/templates/withdrawal/index.html.eex:11 +#, elixir-autogen, elixir-format msgid "%{withdrawals_count} withdrawals processed and %{withdrawals_sum} withdrawn." msgstr "" @@ -3673,7 +3702,7 @@ msgstr "" msgid "Yul" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:437 +#: lib/block_scout_web/templates/transaction/overview.html.eex:469 #, elixir-autogen, elixir-format, fuzzy msgid "burnt for this transaction. Equals Block Base Fee per Gas * Gas Used." msgstr "" @@ -3682,3 +3711,33 @@ msgstr "" #, elixir-autogen, elixir-format, fuzzy msgid "burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used)." msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:488 +#, elixir-autogen, elixir-format, fuzzy +msgid "Actual gas amount used by the transaction." +msgstr "" + +#: lib/block_scout_web/templates/chain/show.html.eex:102 +#, elixir-autogen, elixir-format +msgid "Average block time" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:404 +#, elixir-autogen, elixir-format, fuzzy +msgid "Gas Price" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:489 +#, elixir-autogen, elixir-format, fuzzy +msgid "Gas Used by Transaction" +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:429 +#, elixir-autogen, elixir-format, fuzzy +msgid "Maximum gas amount approved for the transaction." +msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:403 +#, elixir-autogen, elixir-format, fuzzy +msgid "Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage." +msgstr "" diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex index 3636d4ff4680..cd155ed3d231 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex @@ -329,6 +329,16 @@ defmodule EthereumJSONRPC do * `{:error, reason}` - other JSONRPC error. """ + @spec fetch_block_number_by_tag_op_version(tag(), json_rpc_named_arguments) :: + {:ok, non_neg_integer()} | {:error, reason :: :invalid_tag | :not_found | term()} + def fetch_block_number_by_tag_op_version(tag, json_rpc_named_arguments) + when tag in ~w(earliest latest pending safe) do + %{id: 0, tag: tag} + |> Block.ByTag.request() + |> json_rpc(json_rpc_named_arguments) + |> Block.ByTag.number_from_result() + end + @spec fetch_block_number_by_tag(tag(), json_rpc_named_arguments) :: {:ok, non_neg_integer()} | {:error, reason :: :invalid_tag | :not_found | term()} def fetch_block_number_by_tag(tag, json_rpc_named_arguments) when tag in ~w(earliest latest pending safe) do diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_hash.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_hash.ex index 07d1e48b4d1e..5a923a3a95b1 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_hash.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_hash.ex @@ -3,9 +3,7 @@ defmodule EthereumJSONRPC.Block.ByHash do Block format as returned by [`eth_getBlockByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash) """ - @include_transactions true - - def request(%{id: id, hash: hash}) do - EthereumJSONRPC.request(%{id: id, method: "eth_getBlockByHash", params: [hash, @include_transactions]}) + def request(%{id: id, hash: hash}, hydrated \\ true) do + EthereumJSONRPC.request(%{id: id, method: "eth_getBlockByHash", params: [hash, hydrated]}) end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex index f82762f547a0..f4913495582a 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex @@ -80,6 +80,12 @@ defmodule EthereumJSONRPC.Receipt do blob_gas_price: 0,\ blob_gas_used: 0\ """ + "optimism" -> """ + l1_fee: 0,\ + l1_fee_scalar: 0,\ + l1_gas_price: 0,\ + l1_gas_used: 0\ + """ _ -> "" end} } @@ -120,6 +126,12 @@ defmodule EthereumJSONRPC.Receipt do blob_gas_price: 0,\ blob_gas_used: 0\ """ + "optimism" -> """ + l1_fee: 0,\ + l1_fee_scalar: 0,\ + l1_gas_price: 0,\ + l1_gas_used: 0\ + """ _ -> "" end} } @@ -169,6 +181,15 @@ defmodule EthereumJSONRPC.Receipt do blob_gas_used: Map.get(elixir, "blobGasUsed", 0) }) + "optimism" -> + params + |> Map.merge(%{ + l1_fee: Map.get(elixir, "l1Fee", 0), + l1_fee_scalar: Map.get(elixir, "l1FeeScalar", 0), + l1_gas_price: Map.get(elixir, "l1GasPrice", 0), + l1_gas_used: Map.get(elixir, "l1GasUsed", 0) + }) + _ -> params end @@ -287,11 +308,11 @@ defmodule EthereumJSONRPC.Receipt do # hash format # gas is passed in from the `t:EthereumJSONRPC.Transaction.params/0` to allow pre-Byzantium status to be derived defp entry_to_elixir({key, _} = entry) - when key in ~w(blockHash contractAddress from gas logsBloom root to transactionHash revertReason type effectiveGasPrice), + when key in ~w(blockHash contractAddress from gas logsBloom root to transactionHash revertReason type effectiveGasPrice l1FeeScalar), do: {:ok, entry} defp entry_to_elixir({key, quantity}) - when key in ~w(blockNumber cumulativeGasUsed gasUsed transactionIndex blobGasUsed blobGasPrice) do + when key in ~w(blockNumber cumulativeGasUsed gasUsed transactionIndex blobGasUsed blobGasPrice l1Fee l1GasPrice l1GasUsed) do result = if is_nil(quantity) do nil diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex index 7ceb79b872ed..e897b8502075 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex @@ -105,6 +105,12 @@ defmodule EthereumJSONRPC.Receipts do blob_gas_price: 0,\ blob_gas_used: 0\ """ + "optimism" -> """ + l1_fee: 0,\ + l1_fee_scalar: 0,\ + l1_gas_price: 0,\ + l1_gas_used: 0\ + """ _ -> "" end} } diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex index 03cd3a52b8ce..67f172c1a695 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex @@ -20,6 +20,14 @@ defmodule EthereumJSONRPC.Transaction do ] ) + "optimism" -> + @chain_type_fields quote( + do: [ + l1_tx_origin: EthereumJSONRPC.hash(), + l1_block_number: non_neg_integer() + ] + ) + "suave" -> @chain_type_fields quote( do: [ @@ -80,6 +88,10 @@ defmodule EthereumJSONRPC.Transaction do * `"maxFeePerBlobGas"` - `t:EthereumJSONRPC.quantity/0` of wei to denote max fee per unit of blob gas used. Introduced in [EIP-4844](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4844.md) * `"blobVersionedHashes"` - `t:list/0` of `t:EthereumJSONRPC.hash/0` of included data blobs hashes. Introduced in [EIP-4844](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4844.md) """ + "optimism" -> """ + * `"l1TxOrigin"` - . + * `"l1BlockNumber"` - . + """ "suave" -> """ * `"executionNode"` - `t:EthereumJSONRPC.address/0` of execution node (used by Suave). * `"requestRecord"` - map of wrapped transaction data (used by Suave). @@ -131,6 +143,7 @@ defmodule EthereumJSONRPC.Transaction do ...> "s" => 31606574786494953692291101914709926755545765281581808821704454381804773090106, ...> "to" => "0x5df9b87991262f6ba471f09758cde1c0fc1de734", ...> "transactionIndex" => 0, + ...> "type" => 2, ...> "v" => 28, ...> "value" => 31337 ...> } @@ -148,11 +161,49 @@ defmodule EthereumJSONRPC.Transaction do r: 61965845294689009770156372156374760022787886965323743865986648153755601564112, s: 31606574786494953692291101914709926755545765281581808821704454381804773090106, to_address_hash: "0x5df9b87991262f6ba471f09758cde1c0fc1de734", + type: 2, v: 28, value: 31337, transaction_index: 0 } + iex> EthereumJSONRPC.Transaction.elixir_to_params( + ...> %{ + ...> "blockHash" => "0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd", + ...> "blockNumber" => 46147, + ...> "from" => "0xa1e4380a3b1f749673e270229993ee55f35663b4", + ...> "gas" => 21000, + ...> "hash" => "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060", + ...> "input" => "0x", + ...> "nonce" => 0, + ...> "r" => 61965845294689009770156372156374760022787886965323743865986648153755601564112, + ...> "s" => 31606574786494953692291101914709926755545765281581808821704454381804773090106, + ...> "to" => "0x5df9b87991262f6ba471f09758cde1c0fc1de734", + ...> "transactionIndex" => 0, + ...> "type" => 2, + ...> "v" => 28, + ...> "value" => 31337 + ...> } + ...> ) + %{ + block_hash: "0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd", + block_number: 46147, + from_address_hash: "0xa1e4380a3b1f749673e270229993ee55f35663b4", + gas: 21000, + hash: "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060", + index: 0, + input: "0x", + nonce: 0, + r: 61965845294689009770156372156374760022787886965323743865986648153755601564112, + s: 31606574786494953692291101914709926755545765281581808821704454381804773090106, + to_address_hash: "0x5df9b87991262f6ba471f09758cde1c0fc1de734", + type: 2, + v: 28, + value: 31337, + transaction_index: 0, + gas_price: 0 + } + Erigon `elixir` from txpool_content method can be converted to `params`. iex> EthereumJSONRPC.Transaction.elixir_to_params( @@ -386,6 +437,49 @@ defmodule EthereumJSONRPC.Transaction do ]) end + def do_elixir_to_params( + %{ + "blockHash" => block_hash, + "blockNumber" => block_number, + "from" => from_address_hash, + "gas" => gas, + "hash" => hash, + "input" => input, + "nonce" => nonce, + "r" => r, + "s" => s, + "to" => to_address_hash, + "transactionIndex" => index, + "type" => type, + "v" => v, + "value" => value + } = transaction + ) do + result = %{ + block_hash: block_hash, + block_number: block_number, + from_address_hash: from_address_hash, + gas: gas, + gas_price: 0, + hash: hash, + index: index, + input: input, + nonce: nonce, + r: r, + s: s, + to_address_hash: to_address_hash, + v: v, + value: value, + transaction_index: index, + type: type + } + + put_if_present(transaction, result, [ + {"creates", :created_contract_address_hash}, + {"block_timestamp", :block_timestamp} + ]) + end + defp chain_type_fields(params, elixir) do case Application.get_env(:explorer, :chain_type) do "ethereum" -> @@ -394,6 +488,12 @@ defmodule EthereumJSONRPC.Transaction do {"maxFeePerBlobGas", :max_fee_per_blob_gas} ]) + "optimism" -> + put_if_present(elixir, params, [ + {"l1TxOrigin", :l1_tx_origin}, + {"l1BlockNumber", :l1_block_number} + ]) + "suave" -> wrapped = Map.get(elixir, "requestRecord") @@ -552,7 +652,8 @@ defmodule EthereumJSONRPC.Transaction do end # quantity or nil for pending - defp entry_to_elixir({key, quantity_or_nil}) when key in ~w(blockNumber transactionIndex) do + defp entry_to_elixir({key, quantity_or_nil}) + when key in ~w(blockNumber transactionIndex l1TxOrigin l1BlockNumber) do elixir = case quantity_or_nil do nil -> nil diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/endpoint_availability_checker.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/endpoint_availability_checker.ex index 527f8a0c35f7..27cb30921e9a 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/endpoint_availability_checker.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/endpoint_availability_checker.ex @@ -4,6 +4,7 @@ defmodule EthereumJSONRPC.Utility.EndpointAvailabilityChecker do """ use GenServer + require Logger alias EthereumJSONRPC.Utility.EndpointAvailabilityObserver @@ -32,7 +33,9 @@ defmodule EthereumJSONRPC.Utility.EndpointAvailabilityChecker do Enum.reduce(unavailable_endpoints_arguments, [], fn json_rpc_named_arguments, acc -> case fetch_latest_block_number(json_rpc_named_arguments) do {:ok, _number} -> - EndpointAvailabilityObserver.enable_endpoint(json_rpc_named_arguments[:transport_options][:url]) + url = json_rpc_named_arguments[:transport_options][:url] + EndpointAvailabilityObserver.enable_endpoint(url) + Logger.info("URL #{inspect(url)} is available now, switching back to it") acc _ -> diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/endpoint_availability_observer.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/endpoint_availability_observer.ex index ae4d2d1f2bbe..833b2b67d055 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/endpoint_availability_observer.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/endpoint_availability_observer.ex @@ -5,6 +5,8 @@ defmodule EthereumJSONRPC.Utility.EndpointAvailabilityObserver do use GenServer + require Logger + alias EthereumJSONRPC.Utility.EndpointAvailabilityChecker @max_error_count 3 @@ -60,6 +62,7 @@ defmodule EthereumJSONRPC.Utility.EndpointAvailabilityObserver do current_count + 1 >= @max_error_count -> EndpointAvailabilityChecker.add_endpoint(put_in(json_rpc_named_arguments[:transport_options][:url], url)) + Logger.warning("URL #{inspect(url)} is unavailable, switching to fallback url") %{state | error_counts: Map.delete(error_counts, url), unavailable_endpoints: [url | unavailable_endpoints]} true -> diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex index a88216876cda..bfdf5c624f3b 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex @@ -118,6 +118,7 @@ defmodule EthereumJSONRPC.Variant do # credo:disable-for-next-line defp get_default_variant do case Application.get_env(:explorer, :chain_type) do + "optimism" -> "geth" "polygon_zkevm" -> "geth" "zetachain" -> "geth" "shibarium" -> "geth" diff --git a/apps/explorer/config/dev.exs b/apps/explorer/config/dev.exs index a56a52620b6e..85e792bd277d 100644 --- a/apps/explorer/config/dev.exs +++ b/apps/explorer/config/dev.exs @@ -11,6 +11,9 @@ config :explorer, Explorer.Repo.Replica1, timeout: :timer.seconds(80) # Configure Account database config :explorer, Explorer.Repo.Account, timeout: :timer.seconds(80) +# Configure Optimism database +config :explorer, Explorer.Repo.Optimism, timeout: :timer.seconds(80) + # Configure Polygon Edge database config :explorer, Explorer.Repo.PolygonEdge, timeout: :timer.seconds(80) diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs index 68279e1bac3a..2b74469e83e1 100644 --- a/apps/explorer/config/prod.exs +++ b/apps/explorer/config/prod.exs @@ -16,6 +16,10 @@ config :explorer, Explorer.Repo.Account, prepare: :unnamed, timeout: :timer.seconds(60) +config :explorer, Explorer.Repo.Optimism, + prepare: :unnamed, + timeout: :timer.seconds(60) + config :explorer, Explorer.Repo.PolygonEdge, prepare: :unnamed, timeout: :timer.seconds(60) diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index b1e58b464bb7..e815f2082917 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -45,6 +45,7 @@ config :explorer, Explorer.Repo.Account, for repo <- [ Explorer.Repo.Beacon, + Explorer.Repo.Optimism, Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 713b1f51d98b..aac514bed79c 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -126,6 +126,7 @@ defmodule Explorer.Application do configure(Explorer.TokenInstanceOwnerAddressMigration.Supervisor), sc_microservice_configure(Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand), configure(Explorer.Chain.Cache.RootstockLockedBTC), + configure(Explorer.Chain.Cache.OptimismFinalizationPeriod), configure(Explorer.Migrator.TransactionsDenormalization), configure(Explorer.Migrator.AddressCurrentTokenBalanceTokenType), configure(Explorer.Migrator.AddressTokenBalanceTokenType), @@ -142,6 +143,7 @@ defmodule Explorer.Application do if Mix.env() == :test do [ Explorer.Repo.Beacon, + Explorer.Repo.Optimism, Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 625025bba177..3c08bb6a36f8 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -80,6 +80,7 @@ defmodule Explorer.Chain do } alias Explorer.Chain.Cache.Block, as: BlockCache + alias Explorer.Chain.Cache.Helper, as: CacheHelper alias Explorer.Chain.Cache.PendingBlockOperation, as: PendingBlockOperationCache alias Explorer.Chain.Fetcher.{CheckBytecodeMatchingOnDemand, LookUpSmartContractSourcesOnDemand} alias Explorer.Chain.Import.Runner @@ -1658,6 +1659,18 @@ defmodule Explorer.Chain do |> Enum.into(%{}) end + def get_table_rows_total_count(module, options) do + table_name = module.__schema__(:source) + + count = CacheHelper.estimated_count_from(table_name, options) + + if is_nil(count) do + select_repo(options).aggregate(module, :count, timeout: :infinity) + else + count + end + end + @doc """ Calls `reducer` on a stream of `t:Explorer.Chain.Block.t/0` without `t:Explorer.Chain.Block.Reward.t/0`. """ @@ -3214,8 +3227,8 @@ defmodule Explorer.Chain do def limit_showing_transactions, do: @limit_showing_transactions - defp join_association(query, [{association, nested_preload}], necessity) - when is_atom(association) and is_atom(nested_preload) do + def join_association(query, [{association, nested_preload}], necessity) + when is_atom(association) and is_atom(nested_preload) do case necessity do :optional -> preload(query, [{^association, ^nested_preload}]) @@ -3231,7 +3244,7 @@ defmodule Explorer.Chain do end end - defp join_association(query, association, necessity) do + def join_association(query, association, necessity) do case necessity do :optional -> preload(query, ^association) diff --git a/apps/explorer/lib/explorer/chain/address/counters.ex b/apps/explorer/lib/explorer/chain/address/counters.ex index 25cb152caf88..bdd08ecb72c9 100644 --- a/apps/explorer/lib/explorer/chain/address/counters.ex +++ b/apps/explorer/lib/explorer/chain/address/counters.ex @@ -92,7 +92,7 @@ defmodule Explorer.Chain.Address.Counters do if is_nil(cached_value) || cached_value == 0 do count = CacheHelper.estimated_count_from("addresses", options) - max(count, 0) + if is_nil(count), do: 0, else: max(count, 0) else cached_value end diff --git a/apps/explorer/lib/explorer/chain/cache/block.ex b/apps/explorer/lib/explorer/chain/cache/block.ex index e5d7b9ee37ec..0dd216a71bee 100644 --- a/apps/explorer/lib/explorer/chain/cache/block.ex +++ b/apps/explorer/lib/explorer/chain/cache/block.ex @@ -40,9 +40,7 @@ defmodule Explorer.Chain.Cache.Block do |> Decimal.to_integer() if cached_value_from_db === 0 do - count = Helper.estimated_count_from("blocks") - - trunc(count * 0.90) + estimated_count_from_blocks() else cached_value_from_db end @@ -51,6 +49,12 @@ defmodule Explorer.Chain.Cache.Block do end end + defp estimated_count_from_blocks do + count = Helper.estimated_count_from("blocks") + + if is_nil(count), do: 0, else: trunc(count * 0.90) + end + defp handle_fallback(:count) do # This will get the task PID if one exists and launch a new task if not # See next `handle_fallback` definition diff --git a/apps/explorer/lib/explorer/chain/cache/helper.ex b/apps/explorer/lib/explorer/chain/cache/helper.ex index 61047c5f8e06..4e9bd92bd05f 100644 --- a/apps/explorer/lib/explorer/chain/cache/helper.ex +++ b/apps/explorer/lib/explorer/chain/cache/helper.ex @@ -7,7 +7,7 @@ defmodule Explorer.Chain.Cache.Helper do def estimated_count_from(table_name, options \\ []) do %Postgrex.Result{rows: [[count]]} = Chain.select_repo(options).query!( - "SELECT reltuples::BIGINT AS estimate FROM pg_class WHERE relname = '#{table_name}';" + "SELECT (CASE WHEN c.reltuples < 0 THEN NULL WHEN c.relpages = 0 THEN float8 '0' ELSE c.reltuples / c.relpages END * (pg_catalog.pg_relation_size(c.oid) / pg_catalog.current_setting('block_size')::int))::bigint FROM pg_catalog.pg_class c WHERE c.oid = '#{table_name}'::regclass" ) count diff --git a/apps/explorer/lib/explorer/chain/cache/optimism_finalization_period.ex b/apps/explorer/lib/explorer/chain/cache/optimism_finalization_period.ex new file mode 100644 index 000000000000..aa09cf2148ce --- /dev/null +++ b/apps/explorer/lib/explorer/chain/cache/optimism_finalization_period.ex @@ -0,0 +1,54 @@ +defmodule Explorer.Chain.Cache.OptimismFinalizationPeriod do + @moduledoc """ + Caches Optimism Finalization period. + """ + + require Logger + + use Explorer.Chain.MapCache, + name: :optimism_finalization_period, + key: :period + + import EthereumJSONRPC, only: [json_rpc: 2, quantity_to_integer: 1] + + alias EthereumJSONRPC.Contract + alias Indexer.Fetcher.Optimism + alias Indexer.Fetcher.Optimism.OutputRoot + + defp handle_fallback(:period) do + optimism_l1_rpc = Application.get_all_env(:indexer)[Optimism][:optimism_l1_rpc] + output_oracle = Application.get_all_env(:indexer)[OutputRoot][:output_oracle] + + # call FINALIZATION_PERIOD_SECONDS() public getter of L2OutputOracle contract on L1 + request = Contract.eth_call_request("0xf4daa291", output_oracle, 0, nil, nil) + + case json_rpc(request, json_rpc_named_arguments(optimism_l1_rpc)) do + {:ok, value} -> + {:update, quantity_to_integer(value)} + + {:error, reason} -> + Logger.debug([ + "Couldn't fetch Optimism finalization period, reason: #{inspect(reason)}" + ]) + + {:return, nil} + end + end + + defp handle_fallback(_key), do: {:return, nil} + + defp json_rpc_named_arguments(optimism_l1_rpc) do + [ + transport: EthereumJSONRPC.HTTP, + transport_options: [ + http: EthereumJSONRPC.HTTP.HTTPoison, + url: optimism_l1_rpc, + http_options: [ + recv_timeout: :timer.minutes(10), + timeout: :timer.minutes(10), + hackney: [pool: :ethereum_jsonrpc] + ] + ] + ] + end +end diff --git a/apps/explorer/lib/explorer/chain/cache/pending_block_operation.ex b/apps/explorer/lib/explorer/chain/cache/pending_block_operation.ex index dc0f01e59cbe..6980087afa79 100644 --- a/apps/explorer/lib/explorer/chain/cache/pending_block_operation.ex +++ b/apps/explorer/lib/explorer/chain/cache/pending_block_operation.ex @@ -28,7 +28,7 @@ defmodule Explorer.Chain.Cache.PendingBlockOperation do if is_nil(cached_value) do count = Helper.estimated_count_from("pending_block_operations") - max(count, 0) + if is_nil(count), do: 0, else: max(count, 0) else cached_value end diff --git a/apps/explorer/lib/explorer/chain/cache/transaction.ex b/apps/explorer/lib/explorer/chain/cache/transaction.ex index 299294b1534c..dd909502a4d3 100644 --- a/apps/explorer/lib/explorer/chain/cache/transaction.ex +++ b/apps/explorer/lib/explorer/chain/cache/transaction.ex @@ -29,7 +29,7 @@ defmodule Explorer.Chain.Cache.Transaction do if is_nil(cached_value) do count = Helper.estimated_count_from("transactions") - max(count, 0) + if is_nil(count), do: 0, else: count else cached_value end diff --git a/apps/explorer/lib/explorer/chain/events/publisher.ex b/apps/explorer/lib/explorer/chain/events/publisher.ex index 87da3c8e9175..1b45c84c940f 100644 --- a/apps/explorer/lib/explorer/chain/events/publisher.ex +++ b/apps/explorer/lib/explorer/chain/events/publisher.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.Events.Publisher do Publishes events related to the Chain context. """ - @allowed_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches eth_bytecode_db_lookup_started smart_contract_was_not_verified)a + @allowed_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number optimism_deposits optimism_reorg_block token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches eth_bytecode_db_lookup_started smart_contract_was_not_verified)a def broadcast(_data, false), do: :ok diff --git a/apps/explorer/lib/explorer/chain/events/subscriber.ex b/apps/explorer/lib/explorer/chain/events/subscriber.ex index f073afb8eb29..c161bb124a52 100644 --- a/apps/explorer/lib/explorer/chain/events/subscriber.ex +++ b/apps/explorer/lib/explorer/chain/events/subscriber.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.Events.Subscriber do Subscribes to events related to the Chain context. """ - @allowed_broadcast_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches eth_bytecode_db_lookup_started smart_contract_was_not_verified)a + @allowed_broadcast_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number optimism_deposits optimism_reorg_block token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches eth_bytecode_db_lookup_started smart_contract_was_not_verified)a @allowed_broadcast_types ~w(catchup realtime on_demand contract_verification_result)a diff --git a/apps/explorer/lib/explorer/chain/import/runner/optimism/deposits.ex b/apps/explorer/lib/explorer/chain/import/runner/optimism/deposits.ex new file mode 100644 index 000000000000..0422a625f35a --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/optimism/deposits.ex @@ -0,0 +1,106 @@ +defmodule Explorer.Chain.Import.Runner.Optimism.Deposits do + @moduledoc """ + Bulk imports `t:Explorer.Chain.Deposit.t/0`. + """ + + require Ecto.Query + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.Optimism.Deposit + alias Explorer.Prometheus.Instrumenter + + import Ecto.Query, only: [from: 2] + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [Deposit.t()] + + @impl Import.Runner + def ecto_schema_module, do: Deposit + + @impl Import.Runner + def option_key, do: :optimism_deposits + + @impl Import.Runner + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + Multi.run(multi, :insert_optimism_deposits, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :optimism_deposits, + :optimism_deposits + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [Deposit.t()]} + | {:error, [Changeset.t()]} + def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) + + # Enforce Deposit ShareLocks order (see docs: sharelock.md) + ordered_changes_list = Enum.sort_by(changes_list, & &1.l2_transaction_hash) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + for: Deposit, + returning: true, + timeout: timeout, + timestamps: timestamps, + conflict_target: :l2_transaction_hash, + on_conflict: on_conflict + ) + + {:ok, inserted} + end + + defp default_on_conflict do + from( + deposit in Deposit, + update: [ + set: [ + # don't update `l2_transaction_hash` as it is a primary key and used for the conflict target + l1_block_number: fragment("EXCLUDED.l1_block_number"), + l1_block_timestamp: fragment("EXCLUDED.l1_block_timestamp"), + l1_transaction_hash: fragment("EXCLUDED.l1_transaction_hash"), + l1_transaction_origin: fragment("EXCLUDED.l1_transaction_origin"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", deposit.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", deposit.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.l1_block_number, EXCLUDED.l1_block_timestamp, EXCLUDED.l1_transaction_hash, EXCLUDED.l1_transaction_origin) IS DISTINCT FROM (?, ?, ?, ?)", + deposit.l1_block_number, + deposit.l1_block_timestamp, + deposit.l1_transaction_hash, + deposit.l1_transaction_origin + ) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/optimism/frame_sequences.ex b/apps/explorer/lib/explorer/chain/import/runner/optimism/frame_sequences.ex new file mode 100644 index 000000000000..bf45f5354d71 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/optimism/frame_sequences.ex @@ -0,0 +1,102 @@ +defmodule Explorer.Chain.Import.Runner.Optimism.FrameSequences do + @moduledoc """ + Bulk imports `t:Explorer.Chain.Optimism.FrameSequence.t/0`. + """ + + require Ecto.Query + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.Optimism.FrameSequence + alias Explorer.Prometheus.Instrumenter + + import Ecto.Query, only: [from: 2] + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [FrameSequence.t()] + + @impl Import.Runner + def ecto_schema_module, do: FrameSequence + + @impl Import.Runner + def option_key, do: :optimism_frame_sequences + + @impl Import.Runner + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + Multi.run(multi, :insert_frame_sequences, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :optimism_frame_sequences, + :optimism_frame_sequences + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [FrameSequence.t()]} + | {:error, [Changeset.t()]} + def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) + + # Enforce FrameSequence ShareLocks order (see docs: sharelock.md) + ordered_changes_list = Enum.sort_by(changes_list, & &1.id) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + for: FrameSequence, + returning: true, + timeout: timeout, + timestamps: timestamps, + conflict_target: :id, + on_conflict: on_conflict + ) + + {:ok, inserted} + end + + defp default_on_conflict do + from( + fs in FrameSequence, + update: [ + set: [ + # don't update `id` as it is a primary key and used for the conflict target + l1_transaction_hashes: fragment("EXCLUDED.l1_transaction_hashes"), + l1_timestamp: fragment("EXCLUDED.l1_timestamp"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", fs.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", fs.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.l1_transaction_hashes, EXCLUDED.l1_timestamp) IS DISTINCT FROM (?, ?)", + fs.l1_transaction_hashes, + fs.l1_timestamp + ) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/optimism/output_roots.ex b/apps/explorer/lib/explorer/chain/import/runner/optimism/output_roots.ex new file mode 100644 index 000000000000..d0027bc483f6 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/optimism/output_roots.ex @@ -0,0 +1,108 @@ +defmodule Explorer.Chain.Import.Runner.Optimism.OutputRoots do + @moduledoc """ + Bulk imports `t:Explorer.Chain.Optimism.OutputRoot.t/0`. + """ + + require Ecto.Query + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.Optimism.OutputRoot + alias Explorer.Prometheus.Instrumenter + + import Ecto.Query, only: [from: 2] + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [OutputRoot.t()] + + @impl Import.Runner + def ecto_schema_module, do: OutputRoot + + @impl Import.Runner + def option_key, do: :optimism_output_roots + + @impl Import.Runner + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + Multi.run(multi, :insert_output_roots, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :optimism_output_roots, + :optimism_output_roots + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [OutputRoot.t()]} + | {:error, [Changeset.t()]} + def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) + + # Enforce OutputRoot ShareLocks order (see docs: sharelock.md) + ordered_changes_list = Enum.sort_by(changes_list, & &1.l2_output_index) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + for: OutputRoot, + returning: true, + timeout: timeout, + timestamps: timestamps, + conflict_target: :l2_output_index, + on_conflict: on_conflict + ) + + {:ok, inserted} + end + + defp default_on_conflict do + from( + root in OutputRoot, + update: [ + set: [ + # don't update `l2_output_index` as it is a primary key and used for the conflict target + l2_block_number: fragment("EXCLUDED.l2_block_number"), + l1_transaction_hash: fragment("EXCLUDED.l1_transaction_hash"), + l1_timestamp: fragment("EXCLUDED.l1_timestamp"), + l1_block_number: fragment("EXCLUDED.l1_block_number"), + output_root: fragment("EXCLUDED.output_root"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", root.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", root.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.l2_block_number, EXCLUDED.l1_transaction_hash, EXCLUDED.l1_timestamp, EXCLUDED.l1_block_number, EXCLUDED.output_root) IS DISTINCT FROM (?, ?, ?, ?, ?)", + root.l2_block_number, + root.l1_transaction_hash, + root.l1_timestamp, + root.l1_block_number, + root.output_root + ) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/optimism/txn_batches.ex b/apps/explorer/lib/explorer/chain/import/runner/optimism/txn_batches.ex new file mode 100644 index 000000000000..5b84ef3755e3 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/optimism/txn_batches.ex @@ -0,0 +1,100 @@ +defmodule Explorer.Chain.Import.Runner.Optimism.TxnBatches do + @moduledoc """ + Bulk imports `t:Explorer.Chain.Optimism.TxnBatch.t/0`. + """ + + require Ecto.Query + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.Optimism.TxnBatch + alias Explorer.Prometheus.Instrumenter + + import Ecto.Query, only: [from: 2] + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [TxnBatch.t()] + + @impl Import.Runner + def ecto_schema_module, do: TxnBatch + + @impl Import.Runner + def option_key, do: :optimism_txn_batches + + @impl Import.Runner + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + Multi.run(multi, :insert_txn_batches, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :optimism_txn_batches, + :optimism_txn_batches + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [TxnBatch.t()]} + | {:error, [Changeset.t()]} + def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) + + # Enforce TxnBatch ShareLocks order (see docs: sharelock.md) + ordered_changes_list = Enum.sort_by(changes_list, & &1.l2_block_number) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + for: TxnBatch, + returning: true, + timeout: timeout, + timestamps: timestamps, + conflict_target: :l2_block_number, + on_conflict: on_conflict + ) + + {:ok, inserted} + end + + defp default_on_conflict do + from( + tb in TxnBatch, + update: [ + set: [ + # don't update `l2_block_number` as it is a primary key and used for the conflict target + frame_sequence_id: fragment("EXCLUDED.frame_sequence_id"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", tb.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", tb.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.frame_sequence_id) IS DISTINCT FROM (?)", + tb.frame_sequence_id + ) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/optimism/withdrawal_events.ex b/apps/explorer/lib/explorer/chain/import/runner/optimism/withdrawal_events.ex new file mode 100644 index 000000000000..95869f543505 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/optimism/withdrawal_events.ex @@ -0,0 +1,105 @@ +defmodule Explorer.Chain.Import.Runner.Optimism.WithdrawalEvents do + @moduledoc """ + Bulk imports `t:Explorer.Chain.Optimism.WithdrawalEvent.t/0`. + """ + + require Ecto.Query + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.Optimism.WithdrawalEvent + alias Explorer.Prometheus.Instrumenter + + import Ecto.Query, only: [from: 2] + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [WithdrawalEvent.t()] + + @impl Import.Runner + def ecto_schema_module, do: WithdrawalEvent + + @impl Import.Runner + def option_key, do: :optimism_withdrawal_events + + @impl Import.Runner + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + Multi.run(multi, :insert_withdrawal_events, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :optimism_withdrawal_events, + :optimism_withdrawal_events + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [WithdrawalEvent.t()]} + | {:error, [Changeset.t()]} + def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) + + # Enforce WithdrawalEvent ShareLocks order (see docs: sharelock.md) + ordered_changes_list = Enum.sort_by(changes_list, &{&1.withdrawal_hash, &1.l1_event_type}) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + for: WithdrawalEvent, + returning: true, + timeout: timeout, + timestamps: timestamps, + conflict_target: [:withdrawal_hash, :l1_event_type], + on_conflict: on_conflict + ) + + {:ok, inserted} + end + + defp default_on_conflict do + from( + we in WithdrawalEvent, + update: [ + set: [ + # don't update `withdrawal_hash` as it is a part of the composite primary key and used for the conflict target + # don't update `l1_event_type` as it is a part of the composite primary key and used for the conflict target + l1_timestamp: fragment("EXCLUDED.l1_timestamp"), + l1_transaction_hash: fragment("EXCLUDED.l1_transaction_hash"), + l1_block_number: fragment("EXCLUDED.l1_block_number"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", we.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", we.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.l1_timestamp, EXCLUDED.l1_transaction_hash, EXCLUDED.l1_block_number) IS DISTINCT FROM (?, ?, ?)", + we.l1_timestamp, + we.l1_transaction_hash, + we.l1_block_number + ) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/optimism/withdrawals.ex b/apps/explorer/lib/explorer/chain/import/runner/optimism/withdrawals.ex new file mode 100644 index 000000000000..450c97b07017 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/optimism/withdrawals.ex @@ -0,0 +1,104 @@ +defmodule Explorer.Chain.Import.Runner.Optimism.Withdrawals do + @moduledoc """ + Bulk imports `t:Explorer.Chain.OptimismWithdrawal.t/0`. + """ + + require Ecto.Query + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.Optimism.Withdrawal, as: OptimismWithdrawal + alias Explorer.Prometheus.Instrumenter + + import Ecto.Query, only: [from: 2] + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [OptimismWithdrawal.t()] + + @impl Import.Runner + def ecto_schema_module, do: OptimismWithdrawal + + @impl Import.Runner + def option_key, do: :optimism_withdrawals + + @impl Import.Runner + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + Multi.run(multi, :insert_withdrawals, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :optimism_withdrawals, + :optimism_withdrawals + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [OptimismWithdrawal.t()]} + | {:error, [Changeset.t()]} + def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) + + # Enforce OptimismWithdrawal ShareLocks order (see docs: sharelock.md) + ordered_changes_list = Enum.sort_by(changes_list, & &1.msg_nonce) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + for: OptimismWithdrawal, + returning: true, + timeout: timeout, + timestamps: timestamps, + conflict_target: :msg_nonce, + on_conflict: on_conflict + ) + + {:ok, inserted} + end + + defp default_on_conflict do + from( + withdrawal in OptimismWithdrawal, + update: [ + set: [ + # don't update `msg_nonce` as it is a primary key and used for the conflict target + hash: fragment("EXCLUDED.hash"), + l2_transaction_hash: fragment("EXCLUDED.l2_transaction_hash"), + l2_block_number: fragment("EXCLUDED.l2_block_number"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", withdrawal.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", withdrawal.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.hash, EXCLUDED.l2_transaction_hash, EXCLUDED.l2_block_number) IS DISTINCT FROM (?, ?, ?)", + withdrawal.hash, + withdrawal.l2_transaction_hash, + withdrawal.l2_block_number + ) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex index 5e8e124e9173..1f0098afc2f3 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex @@ -108,168 +108,250 @@ defmodule Explorer.Chain.Import.Runner.Transactions do end defp default_on_conflict do - if System.get_env("CHAIN_TYPE") == "suave" do - from( - transaction in Transaction, - update: [ - set: [ - 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"), - error: fragment("EXCLUDED.error"), - from_address_hash: fragment("EXCLUDED.from_address_hash"), - gas: fragment("EXCLUDED.gas"), - gas_price: fragment("EXCLUDED.gas_price"), - gas_used: fragment("EXCLUDED.gas_used"), - index: fragment("EXCLUDED.index"), - input: fragment("EXCLUDED.input"), - nonce: fragment("EXCLUDED.nonce"), - r: fragment("EXCLUDED.r"), - s: fragment("EXCLUDED.s"), - status: fragment("EXCLUDED.status"), - to_address_hash: fragment("EXCLUDED.to_address_hash"), - v: fragment("EXCLUDED.v"), - value: fragment("EXCLUDED.value"), - earliest_processing_start: fragment("EXCLUDED.earliest_processing_start"), - revert_reason: fragment("EXCLUDED.revert_reason"), - max_priority_fee_per_gas: fragment("EXCLUDED.max_priority_fee_per_gas"), - max_fee_per_gas: fragment("EXCLUDED.max_fee_per_gas"), - type: fragment("EXCLUDED.type"), - execution_node_hash: fragment("EXCLUDED.execution_node_hash"), - wrapped_type: fragment("EXCLUDED.wrapped_type"), - wrapped_nonce: fragment("EXCLUDED.wrapped_nonce"), - wrapped_to_address_hash: fragment("EXCLUDED.wrapped_to_address_hash"), - wrapped_gas: fragment("EXCLUDED.wrapped_gas"), - wrapped_gas_price: fragment("EXCLUDED.wrapped_gas_price"), - wrapped_max_priority_fee_per_gas: fragment("EXCLUDED.wrapped_max_priority_fee_per_gas"), - wrapped_max_fee_per_gas: fragment("EXCLUDED.wrapped_max_fee_per_gas"), - wrapped_value: fragment("EXCLUDED.wrapped_value"), - wrapped_input: fragment("EXCLUDED.wrapped_input"), - wrapped_v: fragment("EXCLUDED.wrapped_v"), - wrapped_r: fragment("EXCLUDED.wrapped_r"), - wrapped_s: fragment("EXCLUDED.wrapped_s"), - wrapped_hash: fragment("EXCLUDED.wrapped_hash"), - # Don't update `hash` as it is part of the primary key and used for the conflict target - inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", transaction.inserted_at), - updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", transaction.updated_at) - ] - ], - where: - fragment( - "(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, - transaction.from_address_hash, - transaction.gas, - transaction.gas_price, - transaction.gas_used, - transaction.index, - transaction.input, - transaction.nonce, - transaction.r, - transaction.s, - transaction.status, - transaction.to_address_hash, - transaction.v, - transaction.value, - transaction.earliest_processing_start, - transaction.revert_reason, - transaction.max_priority_fee_per_gas, - transaction.max_fee_per_gas, - transaction.type, - transaction.execution_node_hash, - transaction.wrapped_type, - transaction.wrapped_nonce, - transaction.wrapped_to_address_hash, - transaction.wrapped_gas, - transaction.wrapped_gas_price, - transaction.wrapped_max_priority_fee_per_gas, - transaction.wrapped_max_fee_per_gas, - transaction.wrapped_value, - transaction.wrapped_input, - transaction.wrapped_v, - transaction.wrapped_r, - transaction.wrapped_s, - transaction.wrapped_hash - ) - ) - else - from( - transaction in Transaction, - update: [ - set: [ - 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"), - error: fragment("EXCLUDED.error"), - from_address_hash: fragment("EXCLUDED.from_address_hash"), - gas: fragment("EXCLUDED.gas"), - gas_price: fragment("EXCLUDED.gas_price"), - gas_used: fragment("EXCLUDED.gas_used"), - index: fragment("EXCLUDED.index"), - input: fragment("EXCLUDED.input"), - nonce: fragment("EXCLUDED.nonce"), - r: fragment("EXCLUDED.r"), - s: fragment("EXCLUDED.s"), - status: fragment("EXCLUDED.status"), - to_address_hash: fragment("EXCLUDED.to_address_hash"), - v: fragment("EXCLUDED.v"), - value: fragment("EXCLUDED.value"), - earliest_processing_start: fragment("EXCLUDED.earliest_processing_start"), - revert_reason: fragment("EXCLUDED.revert_reason"), - max_priority_fee_per_gas: fragment("EXCLUDED.max_priority_fee_per_gas"), - max_fee_per_gas: fragment("EXCLUDED.max_fee_per_gas"), - type: fragment("EXCLUDED.type"), - # Don't update `hash` as it is part of the primary key and used for the conflict target - inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", transaction.inserted_at), - updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", transaction.updated_at) - ] - ], - where: - fragment( - "(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, - transaction.from_address_hash, - transaction.gas, - transaction.gas_price, - transaction.gas_used, - transaction.index, - transaction.input, - transaction.nonce, - transaction.r, - transaction.s, - transaction.status, - transaction.to_address_hash, - transaction.v, - transaction.value, - transaction.earliest_processing_start, - transaction.revert_reason, - transaction.max_priority_fee_per_gas, - transaction.max_fee_per_gas, - transaction.type - ) - ) + case Application.get_env(:explorer, :chain_type) do + "suave" -> + from( + transaction in Transaction, + update: [ + set: [ + 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"), + error: fragment("EXCLUDED.error"), + from_address_hash: fragment("EXCLUDED.from_address_hash"), + gas: fragment("EXCLUDED.gas"), + gas_price: fragment("EXCLUDED.gas_price"), + gas_used: fragment("EXCLUDED.gas_used"), + index: fragment("EXCLUDED.index"), + input: fragment("EXCLUDED.input"), + nonce: fragment("EXCLUDED.nonce"), + r: fragment("EXCLUDED.r"), + s: fragment("EXCLUDED.s"), + status: fragment("EXCLUDED.status"), + to_address_hash: fragment("EXCLUDED.to_address_hash"), + v: fragment("EXCLUDED.v"), + value: fragment("EXCLUDED.value"), + earliest_processing_start: fragment("EXCLUDED.earliest_processing_start"), + revert_reason: fragment("EXCLUDED.revert_reason"), + max_priority_fee_per_gas: fragment("EXCLUDED.max_priority_fee_per_gas"), + max_fee_per_gas: fragment("EXCLUDED.max_fee_per_gas"), + type: fragment("EXCLUDED.type"), + execution_node_hash: fragment("EXCLUDED.execution_node_hash"), + wrapped_type: fragment("EXCLUDED.wrapped_type"), + wrapped_nonce: fragment("EXCLUDED.wrapped_nonce"), + wrapped_to_address_hash: fragment("EXCLUDED.wrapped_to_address_hash"), + wrapped_gas: fragment("EXCLUDED.wrapped_gas"), + wrapped_gas_price: fragment("EXCLUDED.wrapped_gas_price"), + wrapped_max_priority_fee_per_gas: fragment("EXCLUDED.wrapped_max_priority_fee_per_gas"), + wrapped_max_fee_per_gas: fragment("EXCLUDED.wrapped_max_fee_per_gas"), + wrapped_value: fragment("EXCLUDED.wrapped_value"), + wrapped_input: fragment("EXCLUDED.wrapped_input"), + wrapped_v: fragment("EXCLUDED.wrapped_v"), + wrapped_r: fragment("EXCLUDED.wrapped_r"), + wrapped_s: fragment("EXCLUDED.wrapped_s"), + wrapped_hash: fragment("EXCLUDED.wrapped_hash"), + # Don't update `hash` as it is part of the primary key and used for the conflict target + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", transaction.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", transaction.updated_at) + ] + ], + where: + fragment( + "(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, + transaction.from_address_hash, + transaction.gas, + transaction.gas_price, + transaction.gas_used, + transaction.index, + transaction.input, + transaction.nonce, + transaction.r, + transaction.s, + transaction.status, + transaction.to_address_hash, + transaction.v, + transaction.value, + transaction.earliest_processing_start, + transaction.revert_reason, + transaction.max_priority_fee_per_gas, + transaction.max_fee_per_gas, + transaction.type, + transaction.execution_node_hash, + transaction.wrapped_type, + transaction.wrapped_nonce, + transaction.wrapped_to_address_hash, + transaction.wrapped_gas, + transaction.wrapped_gas_price, + transaction.wrapped_max_priority_fee_per_gas, + transaction.wrapped_max_fee_per_gas, + transaction.wrapped_value, + transaction.wrapped_input, + transaction.wrapped_v, + transaction.wrapped_r, + transaction.wrapped_s, + transaction.wrapped_hash + ) + ) + + "optimism" -> + from( + transaction in Transaction, + update: [ + set: [ + 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"), + error: fragment("EXCLUDED.error"), + from_address_hash: fragment("EXCLUDED.from_address_hash"), + gas: fragment("EXCLUDED.gas"), + gas_price: fragment("EXCLUDED.gas_price"), + gas_used: fragment("EXCLUDED.gas_used"), + index: fragment("EXCLUDED.index"), + input: fragment("EXCLUDED.input"), + nonce: fragment("EXCLUDED.nonce"), + r: fragment("EXCLUDED.r"), + s: fragment("EXCLUDED.s"), + status: fragment("EXCLUDED.status"), + to_address_hash: fragment("EXCLUDED.to_address_hash"), + v: fragment("EXCLUDED.v"), + value: fragment("EXCLUDED.value"), + earliest_processing_start: fragment("EXCLUDED.earliest_processing_start"), + revert_reason: fragment("EXCLUDED.revert_reason"), + max_priority_fee_per_gas: fragment("EXCLUDED.max_priority_fee_per_gas"), + max_fee_per_gas: fragment("EXCLUDED.max_fee_per_gas"), + type: fragment("EXCLUDED.type"), + l1_fee: fragment("EXCLUDED.l1_fee"), + l1_fee_scalar: fragment("EXCLUDED.l1_fee_scalar"), + l1_gas_price: fragment("EXCLUDED.l1_gas_price"), + l1_gas_used: fragment("EXCLUDED.l1_gas_used"), + l1_tx_origin: fragment("EXCLUDED.l1_tx_origin"), + l1_block_number: fragment("EXCLUDED.l1_block_number"), + # Don't update `hash` as it is part of the primary key and used for the conflict target + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", transaction.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", transaction.updated_at) + ] + ], + where: + fragment( + "(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.l1_fee, EXCLUDED.l1_fee_scalar, EXCLUDED.l1_gas_price, EXCLUDED.l1_gas_used, EXCLUDED.l1_tx_origin, EXCLUDED.l1_block_number) 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, + transaction.from_address_hash, + transaction.gas, + transaction.gas_price, + transaction.gas_used, + transaction.index, + transaction.input, + transaction.nonce, + transaction.r, + transaction.s, + transaction.status, + transaction.to_address_hash, + transaction.v, + transaction.value, + transaction.earliest_processing_start, + transaction.revert_reason, + transaction.max_priority_fee_per_gas, + transaction.max_fee_per_gas, + transaction.type, + transaction.l1_fee, + transaction.l1_fee_scalar, + transaction.l1_gas_price, + transaction.l1_gas_used, + transaction.l1_tx_origin, + transaction.l1_block_number + ) + ) + + _ -> + from( + transaction in Transaction, + update: [ + set: [ + 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"), + error: fragment("EXCLUDED.error"), + from_address_hash: fragment("EXCLUDED.from_address_hash"), + gas: fragment("EXCLUDED.gas"), + gas_price: fragment("EXCLUDED.gas_price"), + gas_used: fragment("EXCLUDED.gas_used"), + index: fragment("EXCLUDED.index"), + input: fragment("EXCLUDED.input"), + nonce: fragment("EXCLUDED.nonce"), + r: fragment("EXCLUDED.r"), + s: fragment("EXCLUDED.s"), + status: fragment("EXCLUDED.status"), + to_address_hash: fragment("EXCLUDED.to_address_hash"), + v: fragment("EXCLUDED.v"), + value: fragment("EXCLUDED.value"), + earliest_processing_start: fragment("EXCLUDED.earliest_processing_start"), + revert_reason: fragment("EXCLUDED.revert_reason"), + max_priority_fee_per_gas: fragment("EXCLUDED.max_priority_fee_per_gas"), + max_fee_per_gas: fragment("EXCLUDED.max_fee_per_gas"), + type: fragment("EXCLUDED.type"), + # Don't update `hash` as it is part of the primary key and used for the conflict target + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", transaction.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", transaction.updated_at) + ] + ], + where: + fragment( + "(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, + transaction.from_address_hash, + transaction.gas, + transaction.gas_price, + transaction.gas_used, + transaction.index, + transaction.input, + transaction.nonce, + transaction.r, + transaction.s, + transaction.status, + transaction.to_address_hash, + transaction.v, + transaction.value, + transaction.earliest_processing_start, + transaction.revert_reason, + transaction.max_priority_fee_per_gas, + transaction.max_fee_per_gas, + transaction.type + ) + ) end end diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex index 9a6c68224766..da6b01fd4d47 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex @@ -19,6 +19,15 @@ defmodule Explorer.Chain.Import.Stage.BlockReferencing do Runner.Withdrawals ] + @optimism_runners [ + Runner.Optimism.FrameSequences, + Runner.Optimism.TxnBatches, + Runner.Optimism.OutputRoots, + Runner.Optimism.Deposits, + Runner.Optimism.Withdrawals, + Runner.Optimism.WithdrawalEvents + ] + @polygon_edge_runners [ Runner.PolygonEdge.Deposits, Runner.PolygonEdge.DepositExecutes, @@ -44,7 +53,10 @@ defmodule Explorer.Chain.Import.Stage.BlockReferencing do @impl Stage def runners do - case System.get_env("CHAIN_TYPE") do + case Application.get_env(:explorer, :chain_type) do + "optimism" -> + @default_runners ++ @optimism_runners + "polygon_edge" -> @default_runners ++ @polygon_edge_runners @@ -64,7 +76,7 @@ defmodule Explorer.Chain.Import.Stage.BlockReferencing do @impl Stage def all_runners do - @default_runners ++ @polygon_edge_runners ++ @polygon_zkevm_runners ++ @shibarium_runners + @default_runners ++ @optimism_runners ++ @polygon_edge_runners ++ @polygon_zkevm_runners ++ @shibarium_runners end @impl Stage diff --git a/apps/explorer/lib/explorer/chain/optimism/deposit.ex b/apps/explorer/lib/explorer/chain/optimism/deposit.ex new file mode 100644 index 000000000000..7bf87b619886 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/optimism/deposit.ex @@ -0,0 +1,86 @@ +defmodule Explorer.Chain.Optimism.Deposit do + @moduledoc "Models a deposit for Optimism." + + use Explorer.Schema + + import Explorer.Chain, only: [join_association: 3, select_repo: 1] + + alias Explorer.Chain.{Hash, Transaction} + alias Explorer.PagingOptions + + @default_paging_options %PagingOptions{page_size: 50} + + @required_attrs ~w(l1_block_number l1_transaction_hash l1_transaction_origin l2_transaction_hash)a + @optional_attrs ~w(l1_block_timestamp)a + @allowed_attrs @required_attrs ++ @optional_attrs + + @type t :: %__MODULE__{ + l1_block_number: non_neg_integer(), + l1_block_timestamp: DateTime.t(), + l1_transaction_hash: Hash.t(), + l1_transaction_origin: Hash.t(), + l2_transaction_hash: Hash.t(), + l2_transaction: %Ecto.Association.NotLoaded{} | Transaction.t() + } + + @primary_key false + schema "op_deposits" do + field(:l1_block_number, :integer) + field(:l1_block_timestamp, :utc_datetime_usec) + field(:l1_transaction_hash, Hash.Full) + field(:l1_transaction_origin, Hash.Address) + + belongs_to(:l2_transaction, Transaction, + foreign_key: :l2_transaction_hash, + primary_key: true, + references: :hash, + type: Hash.Full + ) + + timestamps() + end + + def changeset(%__MODULE__{} = deposit, attrs \\ %{}) do + deposit + |> cast(attrs, @allowed_attrs) + |> validate_required(@required_attrs) + |> foreign_key_constraint(:l2_transaction_hash) + end + + def last_deposit_l1_block_number_query do + from(d in __MODULE__, + select: {d.l1_block_number, d.l1_transaction_hash}, + order_by: [desc: d.l1_block_number], + limit: 1 + ) + end + + @doc """ + Lists `t:Explorer.Chain.Optimism.Deposit.t/0`'s' in descending order based on l1_block_number and l2_transaction_hash. + + """ + @spec list :: [__MODULE__.t()] + def list(options \\ []) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + + base_query = + from(d in __MODULE__, + order_by: [desc: d.l1_block_number, desc: d.l2_transaction_hash] + ) + + base_query + |> join_association(:l2_transaction, :required) + |> page_deposits(paging_options) + |> limit(^paging_options.page_size) + |> select_repo(options).all() + end + + defp page_deposits(query, %PagingOptions{key: nil}), do: query + + defp page_deposits(query, %PagingOptions{key: {block_number, l2_tx_hash}}) do + from(d in query, + where: d.l1_block_number < ^block_number, + or_where: d.l1_block_number == ^block_number and d.l2_transaction_hash < ^l2_tx_hash + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/optimism/frame_sequence.ex b/apps/explorer/lib/explorer/chain/optimism/frame_sequence.ex new file mode 100644 index 000000000000..49aceda7a4f1 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/optimism/frame_sequence.ex @@ -0,0 +1,33 @@ +defmodule Explorer.Chain.Optimism.FrameSequence do + @moduledoc "Models a frame sequence for Optimism." + + use Explorer.Schema + + alias Explorer.Chain.Hash + alias Explorer.Chain.Optimism.TxnBatch + + @required_attrs ~w(id l1_transaction_hashes l1_timestamp)a + + @type t :: %__MODULE__{ + l1_transaction_hashes: [Hash.t()], + l1_timestamp: DateTime.t(), + transaction_batches: %Ecto.Association.NotLoaded{} | [TxnBatch.t()] + } + + @primary_key {:id, :integer, autogenerate: false} + schema "op_frame_sequences" do + field(:l1_transaction_hashes, {:array, Hash.Full}) + field(:l1_timestamp, :utc_datetime_usec) + + has_many(:transaction_batches, TxnBatch, foreign_key: :frame_sequence_id) + + timestamps() + end + + def changeset(%__MODULE__{} = sequences, attrs \\ %{}) do + sequences + |> cast(attrs, @required_attrs) + |> validate_required(@required_attrs) + |> unique_constraint(:id) + end +end diff --git a/apps/explorer/lib/explorer/chain/optimism/output_root.ex b/apps/explorer/lib/explorer/chain/optimism/output_root.ex new file mode 100644 index 000000000000..99f69edb251e --- /dev/null +++ b/apps/explorer/lib/explorer/chain/optimism/output_root.ex @@ -0,0 +1,67 @@ +defmodule Explorer.Chain.Optimism.OutputRoot do + @moduledoc "Models an output root for Optimism." + + use Explorer.Schema + + import Explorer.Chain, only: [select_repo: 1] + + alias Explorer.Chain.Hash + alias Explorer.PagingOptions + + @default_paging_options %PagingOptions{page_size: 50} + + @required_attrs ~w(l2_output_index l2_block_number l1_transaction_hash l1_timestamp l1_block_number output_root)a + + @type t :: %__MODULE__{ + l2_output_index: non_neg_integer(), + l2_block_number: non_neg_integer(), + l1_transaction_hash: Hash.t(), + l1_timestamp: DateTime.t(), + l1_block_number: non_neg_integer(), + output_root: Hash.t() + } + + @primary_key false + schema "op_output_roots" do + field(:l2_output_index, :integer, primary_key: true) + field(:l2_block_number, :integer) + field(:l1_transaction_hash, Hash.Full) + field(:l1_timestamp, :utc_datetime_usec) + field(:l1_block_number, :integer) + field(:output_root, Hash.Full) + + timestamps() + end + + def changeset(%__MODULE__{} = output_roots, attrs \\ %{}) do + output_roots + |> cast(attrs, @required_attrs) + |> validate_required(@required_attrs) + end + + @doc """ + Lists `t:Explorer.Chain.Optimism.OutputRoot.t/0`'s' in descending order based on output root index. + + """ + @spec list :: [__MODULE__.t()] + def list(options \\ []) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + + base_query = + from(r in __MODULE__, + order_by: [desc: r.l2_output_index], + select: r + ) + + base_query + |> page_output_roots(paging_options) + |> limit(^paging_options.page_size) + |> select_repo(options).all() + end + + defp page_output_roots(query, %PagingOptions{key: nil}), do: query + + defp page_output_roots(query, %PagingOptions{key: {index}}) do + from(r in query, where: r.l2_output_index < ^index) + end +end diff --git a/apps/explorer/lib/explorer/chain/optimism/txn_batch.ex b/apps/explorer/lib/explorer/chain/optimism/txn_batch.ex new file mode 100644 index 000000000000..fea454afd34e --- /dev/null +++ b/apps/explorer/lib/explorer/chain/optimism/txn_batch.ex @@ -0,0 +1,61 @@ +defmodule Explorer.Chain.Optimism.TxnBatch do + @moduledoc "Models a batch of transactions for Optimism." + + use Explorer.Schema + + import Explorer.Chain, only: [join_association: 3, select_repo: 1] + + alias Explorer.Chain.Optimism.FrameSequence + alias Explorer.PagingOptions + + @default_paging_options %PagingOptions{page_size: 50} + + @required_attrs ~w(l2_block_number frame_sequence_id)a + + @type t :: %__MODULE__{ + l2_block_number: non_neg_integer(), + frame_sequence_id: non_neg_integer(), + frame_sequence: %Ecto.Association.NotLoaded{} | FrameSequence.t() + } + + @primary_key false + schema "op_transaction_batches" do + field(:l2_block_number, :integer, primary_key: true) + belongs_to(:frame_sequence, FrameSequence, foreign_key: :frame_sequence_id, references: :id, type: :integer) + + timestamps() + end + + def changeset(%__MODULE__{} = batches, attrs \\ %{}) do + batches + |> cast(attrs, @required_attrs) + |> validate_required(@required_attrs) + |> foreign_key_constraint(:frame_sequence_id) + end + + @doc """ + Lists `t:Explorer.Chain.Optimism.TxnBatch.t/0`'s' in descending order based on l2_block_number. + + """ + @spec list :: [__MODULE__.t()] + def list(options \\ []) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + + base_query = + from(tb in __MODULE__, + order_by: [desc: tb.l2_block_number] + ) + + base_query + |> join_association(:frame_sequence, :required) + |> page_txn_batches(paging_options) + |> limit(^paging_options.page_size) + |> select_repo(options).all() + end + + defp page_txn_batches(query, %PagingOptions{key: nil}), do: query + + defp page_txn_batches(query, %PagingOptions{key: {block_number}}) do + from(tb in query, where: tb.l2_block_number < ^block_number) + end +end diff --git a/apps/explorer/lib/explorer/chain/optimism/withdrawal.ex b/apps/explorer/lib/explorer/chain/optimism/withdrawal.ex new file mode 100644 index 000000000000..af7c062a47cd --- /dev/null +++ b/apps/explorer/lib/explorer/chain/optimism/withdrawal.ex @@ -0,0 +1,163 @@ +defmodule Explorer.Chain.Optimism.Withdrawal do + @moduledoc "Models Optimism withdrawal." + + use Explorer.Schema + + import Explorer.Chain, only: [select_repo: 1] + + alias Explorer.Chain.{Block, Hash, Transaction} + alias Explorer.Chain.Cache.OptimismFinalizationPeriod + alias Explorer.Chain.Optimism.{OutputRoot, WithdrawalEvent} + alias Explorer.{PagingOptions, Repo} + + @default_paging_options %PagingOptions{page_size: 50} + + @required_attrs ~w(msg_nonce hash l2_transaction_hash l2_block_number)a + + @type t :: %__MODULE__{ + msg_nonce: Decimal.t(), + hash: Hash.t(), + l2_transaction_hash: Hash.t(), + l2_block_number: non_neg_integer() + } + + @primary_key false + schema "op_withdrawals" do + field(:msg_nonce, :decimal, primary_key: true) + field(:hash, Hash.Full) + field(:l2_transaction_hash, Hash.Full) + field(:l2_block_number, :integer) + + timestamps() + end + + def changeset(%__MODULE__{} = withdrawals, attrs \\ %{}) do + withdrawals + |> cast(attrs, @required_attrs) + |> validate_required(@required_attrs) + end + + @doc """ + Lists `t:Explorer.Chain.Optimism.Withdrawal.t/0`'s' in descending order based on message nonce. + + """ + @spec list :: [__MODULE__.t()] + def list(options \\ []) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + + base_query = + from(w in __MODULE__, + order_by: [desc: w.msg_nonce], + left_join: l2_tx in Transaction, + on: w.l2_transaction_hash == l2_tx.hash, + left_join: l2_block in Block, + on: w.l2_block_number == l2_block.number, + left_join: we in WithdrawalEvent, + on: we.withdrawal_hash == w.hash and we.l1_event_type == :WithdrawalFinalized, + select: %{ + msg_nonce: w.msg_nonce, + hash: w.hash, + l2_block_number: w.l2_block_number, + l2_timestamp: l2_block.timestamp, + l2_transaction_hash: w.l2_transaction_hash, + l1_transaction_hash: we.l1_transaction_hash, + from: l2_tx.from_address_hash + } + ) + + base_query + |> page_optimism_withdrawals(paging_options) + |> limit(^paging_options.page_size) + |> select_repo(options).all() + end + + defp page_optimism_withdrawals(query, %PagingOptions{key: nil}), do: query + + defp page_optimism_withdrawals(query, %PagingOptions{key: {nonce}}) do + from(w in query, where: w.msg_nonce < ^nonce) + end + + @doc """ + Gets withdrawal statuses for Optimism Withdrawal transaction. + For each withdrawal associated with this transaction, + returns the status and the corresponding L1 transaction hash if the status is `Relayed`. + """ + @spec transaction_statuses(Hash.t()) :: [{non_neg_integer(), String.t(), Hash.t() | nil}] + def transaction_statuses(l2_transaction_hash) do + query = + from(w in __MODULE__, + where: w.l2_transaction_hash == ^l2_transaction_hash, + left_join: l2_block in Block, + on: w.l2_block_number == l2_block.number and l2_block.consensus == true, + left_join: we in WithdrawalEvent, + on: we.withdrawal_hash == w.hash and we.l1_event_type == :WithdrawalFinalized, + select: %{ + hash: w.hash, + l2_block_number: w.l2_block_number, + l1_transaction_hash: we.l1_transaction_hash, + msg_nonce: w.msg_nonce + } + ) + + query + |> Repo.replica().all() + |> Enum.map(fn w -> + msg_nonce = + Bitwise.band( + Decimal.to_integer(w.msg_nonce), + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + ) + + {status, _} = status(w) + {msg_nonce, status, w.l1_transaction_hash} + end) + end + + @doc """ + Gets Optimism Withdrawal status and remaining time to unlock (when the status is `In challenge period`). + """ + @spec status(map()) :: {String.t(), DateTime.t() | nil} + def status(w) when is_nil(w.l1_transaction_hash) do + l1_timestamp = + Repo.replica().one( + from( + we in WithdrawalEvent, + select: we.l1_timestamp, + where: we.withdrawal_hash == ^w.hash and we.l1_event_type == :WithdrawalProven + ) + ) + + if is_nil(l1_timestamp) do + last_root_l2_block_number = + Repo.replica().one( + from(root in OutputRoot, + select: root.l2_block_number, + order_by: [desc: root.l2_output_index], + limit: 1 + ) + ) || 0 + + if w.l2_block_number > last_root_l2_block_number do + {"Waiting for state root", nil} + else + {"Ready to prove", nil} + end + else + challenge_period = + case OptimismFinalizationPeriod.get_period() do + nil -> 604_800 + period -> period + end + + if DateTime.compare(l1_timestamp, DateTime.add(DateTime.utc_now(), -challenge_period, :second)) == :lt do + {"Ready for relay", nil} + else + {"In challenge period", DateTime.add(l1_timestamp, challenge_period, :second)} + end + end + end + + def status(_w) do + {"Relayed", nil} + end +end diff --git a/apps/explorer/lib/explorer/chain/optimism/withdrawal_event.ex b/apps/explorer/lib/explorer/chain/optimism/withdrawal_event.ex new file mode 100644 index 000000000000..3cdecc12cb93 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/optimism/withdrawal_event.ex @@ -0,0 +1,34 @@ +defmodule Explorer.Chain.Optimism.WithdrawalEvent do + @moduledoc "Models Optimism withdrawal event." + + use Explorer.Schema + + alias Explorer.Chain.Hash + + @required_attrs ~w(withdrawal_hash l1_event_type l1_timestamp l1_transaction_hash l1_block_number)a + + @type t :: %__MODULE__{ + withdrawal_hash: Hash.t(), + l1_event_type: String.t(), + l1_timestamp: DateTime.t(), + l1_transaction_hash: Hash.t(), + l1_block_number: non_neg_integer() + } + + @primary_key false + schema "op_withdrawal_events" do + field(:withdrawal_hash, Hash.Full, primary_key: true) + field(:l1_event_type, Ecto.Enum, values: [:WithdrawalProven, :WithdrawalFinalized], primary_key: true) + field(:l1_timestamp, :utc_datetime_usec) + field(:l1_transaction_hash, Hash.Full) + field(:l1_block_number, :integer) + + timestamps() + end + + def changeset(%__MODULE__{} = withdrawal_events, attrs \\ %{}) do + withdrawal_events + |> cast(attrs, @required_attrs) + |> validate_required(@required_attrs) + end +end diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 752b7e56d717..cbd4e261ad67 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -26,6 +26,19 @@ defmodule Explorer.Chain.Transaction.Schema do ] end + "optimism" -> + elem( + quote do + field(:l1_fee, Wei) + field(:l1_fee_scalar, :decimal) + field(:l1_gas_price, Wei) + field(:l1_gas_used, :decimal) + field(:l1_tx_origin, Hash.Full) + field(:l1_block_number, :integer) + end, + 2 + ) + "suave" -> elem( quote do @@ -202,8 +215,10 @@ defmodule Explorer.Chain.Transaction do alias Explorer.SmartContract.SigProviderInterface @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 + error gas_price gas_used index created_contract_code_indexed_at status + to_address_hash revert_reason type has_error_in_internal_txs)a + @optimism_optional_attrs ~w(l1_fee l1_fee_scalar l1_gas_price l1_gas_used l1_tx_origin l1_block_number)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 @required_attrs ~w(from_address_hash gas hash input nonce r s v value)a @@ -527,7 +542,7 @@ defmodule Explorer.Chain.Transaction do attrs_to_cast = @required_attrs ++ @optional_attrs ++ - if Application.get_env(:explorer, :chain_type) == "suave", do: @suave_optional_attrs, else: @empty_attrs + custom_optional_attrs() transaction |> cast(attrs, attrs_to_cast) @@ -542,6 +557,14 @@ defmodule Explorer.Chain.Transaction do |> unique_constraint(:hash) end + defp custom_optional_attrs do + case Application.get_env(:explorer, :chain_type) do + "suave" -> @suave_optional_attrs + "optimism" -> @optimism_optional_attrs + _ -> @empty_attrs + end + 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 @@ -1740,38 +1763,43 @@ defmodule Explorer.Chain.Transaction do """ @spec fee(Transaction.t(), :ether | :gwei | :wei) :: {:maximum, Decimal.t()} | {:actual, Decimal.t() | nil} - def fee(%Transaction{gas: gas, gas_price: nil, gas_used: nil} = transaction, unit) do - gas_price = effective_gas_price(transaction) + def fee(%Transaction{gas: _gas, gas_price: nil, gas_used: nil}, _unit), do: {:maximum, nil} - {:maximum, gas_price && gas_price |> Wei.to(unit) |> Decimal.mult(gas)} + def fee(%Transaction{gas: gas, gas_price: gas_price, gas_used: nil} = tx, unit) do + {:maximum, fee(tx, gas_price, gas, unit)} end - def fee(%Transaction{gas: gas, gas_price: gas_price, gas_used: nil}, unit) do - fee = - gas_price - |> Wei.to(unit) - |> Decimal.mult(gas) + def fee(%Transaction{gas_price: nil, gas_used: gas_used} = transaction, unit) do + if Application.get_env(:explorer, :chain_type) == "optimism" do + {:actual, nil} + else + gas_price = effective_gas_price(transaction) - {:maximum, fee} + {:actual, + gas_price && + gas_price + |> Wei.to(unit) + |> Decimal.mult(gas_used)} + end end - def fee(%Transaction{gas_price: nil, gas_used: gas_used} = transaction, unit) do - gas_price = effective_gas_price(transaction) - - {:actual, - gas_price && - gas_price - |> Wei.to(unit) - |> Decimal.mult(gas_used)} + def fee(%Transaction{gas_price: gas_price, gas_used: gas_used} = tx, unit) do + {:actual, fee(tx, gas_price, gas_used, unit)} end - def fee(%Transaction{gas_price: gas_price, gas_used: gas_used}, unit) do - fee = - gas_price - |> Wei.to(unit) - |> Decimal.mult(gas_used) + defp fee(tx, gas_price, gas, unit) do + l1_fee = + case Map.get(tx, :l1_fee) do + nil -> Wei.from(Decimal.new(0), :wei) + value -> value + end - {:actual, fee} + gas_price + |> Wei.to(unit) + |> Decimal.mult(gas) + |> Wei.from(unit) + |> Wei.sum(l1_fee) + |> Wei.to(unit) end @doc """ diff --git a/apps/explorer/lib/explorer/helper.ex b/apps/explorer/lib/explorer/helper.ex index a3161b4b462a..a8791eb365d5 100644 --- a/apps/explorer/lib/explorer/helper.ex +++ b/apps/explorer/lib/explorer/helper.ex @@ -1,6 +1,6 @@ defmodule Explorer.Helper do @moduledoc """ - Common explorer helper + Auxiliary common functions. """ alias ABI.TypeDecoder @@ -30,16 +30,19 @@ defmodule Explorer.Helper do |> TypeDecoder.decode_raw(types) end - @spec parse_integer(binary() | nil) :: integer() | nil - def parse_integer(nil), do: nil - - def parse_integer(string) do - case Integer.parse(string) do - {number, ""} -> number + def parse_integer(integer_string) when is_binary(integer_string) do + case Integer.parse(integer_string) do + {integer, ""} -> integer _ -> nil end end + def parse_integer(value) when is_integer(value) do + value + end + + def parse_integer(_integer_string), do: nil + @doc """ Parses number from hex string or decimal number string """ diff --git a/apps/explorer/lib/explorer/repo.ex b/apps/explorer/lib/explorer/repo.ex index 3bf981b31908..2e457379d8b5 100644 --- a/apps/explorer/lib/explorer/repo.ex +++ b/apps/explorer/lib/explorer/repo.ex @@ -151,6 +151,16 @@ defmodule Explorer.Repo do end end + defmodule Optimism do + use Ecto.Repo, + otp_app: :explorer, + adapter: Ecto.Adapters.Postgres + + def init(_, opts) do + ConfigHelper.init_repo_module(__MODULE__, opts) + end + end + defmodule PolygonEdge do use Ecto.Repo, otp_app: :explorer, diff --git a/apps/explorer/priv/optimism/migrations/20220204060243_transaction_columns_to_support_l2.exs b/apps/explorer/priv/optimism/migrations/20220204060243_transaction_columns_to_support_l2.exs new file mode 100644 index 000000000000..36a7902cfdf5 --- /dev/null +++ b/apps/explorer/priv/optimism/migrations/20220204060243_transaction_columns_to_support_l2.exs @@ -0,0 +1,14 @@ +defmodule Explorer.Repo.Migrations.TransactionColumnsToSupportL2 do + use Ecto.Migration + + def change do + alter table(:transactions) do + add(:l1_fee, :numeric, precision: 100, null: true) + add(:l1_fee_scalar, :decimal, null: true) + add(:l1_gas_price, :numeric, precision: 100, null: true) + add(:l1_gas_used, :numeric, precision: 100, null: true) + add(:l1_tx_origin, :bytea, null: true) + add(:l1_block_number, :integer, null: true) + end + end +end diff --git a/apps/explorer/priv/optimism/migrations/20230131115105_add_op_output_roots_table.exs b/apps/explorer/priv/optimism/migrations/20230131115105_add_op_output_roots_table.exs new file mode 100644 index 000000000000..da07e936ee7d --- /dev/null +++ b/apps/explorer/priv/optimism/migrations/20230131115105_add_op_output_roots_table.exs @@ -0,0 +1,16 @@ +defmodule Explorer.Repo.Migrations.AddOpOutputRootsTable do + use Ecto.Migration + + def change do + create table(:op_output_roots, primary_key: false) do + add(:l2_output_index, :bigint, null: false, primary_key: true) + add(:l2_block_number, :bigint, null: false) + add(:l1_tx_hash, :bytea, null: false) + add(:l1_timestamp, :"timestamp without time zone", null: false) + add(:l1_block_number, :bigint, null: false) + add(:output_root, :bytea, null: false) + + timestamps(null: false, type: :utc_datetime_usec) + end + end +end diff --git a/apps/explorer/priv/optimism/migrations/20230206123308_add_op_withdrawals_table.exs b/apps/explorer/priv/optimism/migrations/20230206123308_add_op_withdrawals_table.exs new file mode 100644 index 000000000000..cee87054f02b --- /dev/null +++ b/apps/explorer/priv/optimism/migrations/20230206123308_add_op_withdrawals_table.exs @@ -0,0 +1,14 @@ +defmodule Explorer.Repo.Migrations.AddOpWithdrawalsTable do + use Ecto.Migration + + def change do + create table(:op_withdrawals, primary_key: false) do + add(:msg_nonce, :numeric, precision: 100, null: false, primary_key: true) + add(:withdrawal_hash, :bytea, null: false) + add(:l2_tx_hash, :bytea, null: false) + add(:l2_block_number, :bigint, null: false) + + timestamps(null: false, type: :utc_datetime_usec) + end + end +end diff --git a/apps/explorer/priv/optimism/migrations/20230212162845_add_op_withdrawal_events_table.exs b/apps/explorer/priv/optimism/migrations/20230212162845_add_op_withdrawal_events_table.exs new file mode 100644 index 000000000000..12f5d3160614 --- /dev/null +++ b/apps/explorer/priv/optimism/migrations/20230212162845_add_op_withdrawal_events_table.exs @@ -0,0 +1,22 @@ +defmodule Explorer.Repo.Migrations.AddOpWithdrawalEventsTable do + use Ecto.Migration + + def change do + execute( + "CREATE TYPE withdrawal_event_type AS ENUM ('WithdrawalProven', 'WithdrawalFinalized')", + "DROP TYPE withdrawal_event_type" + ) + + create table(:op_withdrawal_events, primary_key: false) do + add(:withdrawal_hash, :bytea, null: false, primary_key: true) + add(:l1_event_type, :withdrawal_event_type, null: false, primary_key: true) + add(:l1_timestamp, :"timestamp without time zone", null: false) + add(:l1_tx_hash, :bytea, null: false) + add(:l1_block_number, :bigint, null: false) + + timestamps(null: false, type: :utc_datetime_usec) + end + + create(index(:op_withdrawal_events, :l1_timestamp)) + end +end diff --git a/apps/explorer/priv/optimism/migrations/20230216135703_add_op_transaction_batches_table.exs b/apps/explorer/priv/optimism/migrations/20230216135703_add_op_transaction_batches_table.exs new file mode 100644 index 000000000000..59fbc9822675 --- /dev/null +++ b/apps/explorer/priv/optimism/migrations/20230216135703_add_op_transaction_batches_table.exs @@ -0,0 +1,14 @@ +defmodule Explorer.Repo.Migrations.AddOpTransactionBatchesTable do + use Ecto.Migration + + def change do + create table(:op_transaction_batches, primary_key: false) do + add(:l2_block_number, :bigint, null: false, primary_key: true) + add(:epoch_number, :bigint, null: false) + add(:l1_tx_hashes, {:array, :bytea}, null: false) + add(:l1_tx_timestamp, :"timestamp without time zone", null: false) + + timestamps(null: false, type: :utc_datetime_usec) + end + end +end diff --git a/apps/explorer/priv/optimism/migrations/20230220202107_create_op_deposits.exs b/apps/explorer/priv/optimism/migrations/20230220202107_create_op_deposits.exs new file mode 100644 index 000000000000..3f313995528c --- /dev/null +++ b/apps/explorer/priv/optimism/migrations/20230220202107_create_op_deposits.exs @@ -0,0 +1,17 @@ +defmodule Explorer.Repo.Migrations.CreateOpDeposits do + use Ecto.Migration + + def change do + create table(:op_deposits, primary_key: false) do + add(:l1_block_number, :bigint, null: false) + add(:l1_block_timestamp, :"timestamp without time zone", null: true) + add(:l1_transaction_hash, :bytea, null: false) + add(:l1_transaction_origin, :bytea, null: false) + add(:l2_transaction_hash, :bytea, null: false, primary_key: true) + + timestamps(null: false, type: :utc_datetime_usec) + end + + create(index(:op_deposits, [:l1_block_number])) + end +end diff --git a/apps/explorer/priv/optimism/migrations/20230301105051_rename_fields.exs b/apps/explorer/priv/optimism/migrations/20230301105051_rename_fields.exs new file mode 100644 index 000000000000..d7041587a38f --- /dev/null +++ b/apps/explorer/priv/optimism/migrations/20230301105051_rename_fields.exs @@ -0,0 +1,12 @@ +defmodule Explorer.Repo.Migrations.RenameFields do + use Ecto.Migration + + def change do + rename(table(:op_transaction_batches), :l1_tx_hashes, to: :l1_transaction_hashes) + rename(table(:op_transaction_batches), :l1_tx_timestamp, to: :l1_timestamp) + rename(table(:op_output_roots), :l1_tx_hash, to: :l1_transaction_hash) + rename(table(:op_withdrawals), :l2_tx_hash, to: :l2_transaction_hash) + rename(table(:op_withdrawals), :withdrawal_hash, to: :hash) + rename(table(:op_withdrawal_events), :l1_tx_hash, to: :l1_transaction_hash) + end +end diff --git a/apps/explorer/priv/optimism/migrations/20230303125841_add_op_indexes.exs b/apps/explorer/priv/optimism/migrations/20230303125841_add_op_indexes.exs new file mode 100644 index 000000000000..e9db8591c361 --- /dev/null +++ b/apps/explorer/priv/optimism/migrations/20230303125841_add_op_indexes.exs @@ -0,0 +1,8 @@ +defmodule Explorer.Repo.Migrations.AddOpIndexes do + use Ecto.Migration + + def change do + create(index(:op_output_roots, [:l1_block_number])) + create(index(:op_withdrawal_events, [:l1_block_number])) + end +end diff --git a/apps/explorer/priv/optimism/migrations/20230307090655_add_op_frame_sequences_table.exs b/apps/explorer/priv/optimism/migrations/20230307090655_add_op_frame_sequences_table.exs new file mode 100644 index 000000000000..ea7f192af638 --- /dev/null +++ b/apps/explorer/priv/optimism/migrations/20230307090655_add_op_frame_sequences_table.exs @@ -0,0 +1,24 @@ +defmodule Explorer.Repo.Migrations.AddOpFrameSequencesTable do + use Ecto.Migration + + def change do + create table(:op_frame_sequences, primary_key: true) do + add(:l1_transaction_hashes, {:array, :bytea}, null: false) + add(:l1_timestamp, :"timestamp without time zone", null: false) + + timestamps(null: false, type: :utc_datetime_usec) + end + + alter table(:op_transaction_batches) do + remove(:l1_transaction_hashes) + remove(:l1_timestamp) + + add( + :frame_sequence_id, + references(:op_frame_sequences, on_delete: :restrict, on_update: :update_all, type: :bigint), + null: false, + after: :epoch_number + ) + end + end +end diff --git a/apps/explorer/priv/optimism/migrations/20230731130103_modify_collated_gas_price_constraint.exs b/apps/explorer/priv/optimism/migrations/20230731130103_modify_collated_gas_price_constraint.exs new file mode 100644 index 000000000000..59a07d849153 --- /dev/null +++ b/apps/explorer/priv/optimism/migrations/20230731130103_modify_collated_gas_price_constraint.exs @@ -0,0 +1,15 @@ +defmodule Explorer.Repo.Optimism.Migrations.ModifyCollatedGasPriceConstraint do + use Ecto.Migration + + def change do + execute("ALTER TABLE transactions DROP CONSTRAINT collated_gas_price") + + create( + constraint( + :transactions, + :collated_gas_price, + check: "block_hash IS NULL OR gas_price IS NOT NULL OR max_fee_per_gas IS NOT NULL" + ) + ) + end +end diff --git a/apps/explorer/priv/optimism/migrations/20231025102325_add_op_withdrawal_index.exs b/apps/explorer/priv/optimism/migrations/20231025102325_add_op_withdrawal_index.exs new file mode 100644 index 000000000000..3fda19158b86 --- /dev/null +++ b/apps/explorer/priv/optimism/migrations/20231025102325_add_op_withdrawal_index.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Migrations.AddOpWithdrawalIndex do + use Ecto.Migration + + def change do + create(index(:op_withdrawals, :l2_transaction_hash)) + end +end diff --git a/apps/explorer/priv/optimism/migrations/20240124124644_remove_op_epoch_number_field.exs b/apps/explorer/priv/optimism/migrations/20240124124644_remove_op_epoch_number_field.exs new file mode 100644 index 000000000000..652f9551896c --- /dev/null +++ b/apps/explorer/priv/optimism/migrations/20240124124644_remove_op_epoch_number_field.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.RemoveOpEpochNumberField do + use Ecto.Migration + + def change do + alter table(:op_transaction_batches) do + remove(:epoch_number) + end + end +end diff --git a/apps/explorer/test/explorer/chain/transaction_test.exs b/apps/explorer/test/explorer/chain/transaction_test.exs index 7a5ce5bbe51a..4fb41c3b2407 100644 --- a/apps/explorer/test/explorer/chain/transaction_test.exs +++ b/apps/explorer/test/explorer/chain/transaction_test.exs @@ -840,4 +840,50 @@ defmodule Explorer.Chain.TransactionTest do {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) end + + describe "fee/2" do + test "is_nil(gas_price), is_nil(gas_used)" do + assert {:maximum, nil} == Transaction.fee(%Transaction{gas: 100_500, gas_price: nil, gas_used: nil}, :wei) + end + + test "not is_nil(gas_price), is_nil(gas_used)" do + assert {:maximum, Decimal.new("20100000")} == + Transaction.fee( + %Transaction{gas: 100_500, gas_price: %Explorer.Chain.Wei{value: 200}, gas_used: nil}, + :wei + ) + end + + test "is_nil(gas_price), not is_nil(gas_used)" do + transaction = %Transaction{ + gas_price: nil, + max_priority_fee_per_gas: %Explorer.Chain.Wei{value: 10_000_000_000}, + max_fee_per_gas: %Explorer.Chain.Wei{value: 63_000_000_000}, + gas_used: Decimal.new(100), + block: %{base_fee_per_gas: %Explorer.Chain.Wei{value: 42_000_000_000}} + } + + if Application.get_env(:explorer, :chain_type) == "optimism" do + {:actual, nil} == + Transaction.fee( + transaction, + :wei + ) + else + assert {:actual, Decimal.new("5200000000000")} == + Transaction.fee( + transaction, + :wei + ) + end + end + + test "not is_nil(gas_price), not is_nil(gas_used)" do + assert {:actual, Decimal.new("6")} == + Transaction.fee( + %Transaction{gas_price: %Explorer.Chain.Wei{value: 2}, gas_used: Decimal.new(3)}, + :wei + ) + end + end end diff --git a/apps/indexer/README.md b/apps/indexer/README.md index 60d286cdf533..5a22018299a6 100644 --- a/apps/indexer/README.md +++ b/apps/indexer/README.md @@ -24,6 +24,7 @@ Some data has to be extracted from already fetched data, and there're several tr - `transaction_actions`: parses logs to extract transaction actions - `address_token_balances`: creates token balance entities for further fetching, based on detected token transfers - `blocks`: extracts block signer hash from additional data for Clique chains +- `optimism_withdrawals`: parses logs to extract L2 withdrawal messages ### Root fetchers @@ -31,6 +32,11 @@ Some data has to be extracted from already fetched data, and there're several tr - `block/realtime`: listens for new blocks from websocket and polls node for new blocks, imports new ones one by one - `block/catchup`: gets unfetched ranges of blocks, imports them in batches - `transaction_action`: optionally fetches/rewrites transaction actions for old blocks (in a given range of blocks for given protocols) +- `optimism/txn_batch`: fetches transaction batches of Optimism chain +- `optimism/output_root`: fetches output roots of Optimism chain +- `optimism/deposit`: fetches deposits to Optimism chain +- `optimism/withdrawal`: fetches withdrawals from Optimism chain +- `optimism/withdrawal_event`: fetches withdrawal events on L1 chain - `withdrawals`: optionally fetches withdrawals for old blocks (in the given from boundary of block numbers) Both block fetchers retrieve/extract the blocks themselves and the following additional data: diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 39b984d9573e..e17c48c3e597 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -44,6 +44,8 @@ defmodule Indexer.Block.Fetcher do TransactionActions } + alias Indexer.Transform.Optimism.Withdrawals, as: OptimismWithdrawals + alias Indexer.Transform.PolygonEdge.{DepositExecutes, Withdrawals} alias Indexer.Transform.Shibarium.Bridge, as: ShibariumBridge @@ -149,6 +151,8 @@ defmodule Indexer.Block.Fetcher do %{token_transfers: token_transfers, tokens: tokens} = TokenTransfers.parse(logs), %{transaction_actions: transaction_actions} = TransactionActions.parse(logs), %{mint_transfers: mint_transfers} = MintTransfers.parse(logs), + optimism_withdrawals = + if(callback_module == Indexer.Block.Realtime.Fetcher, do: OptimismWithdrawals.parse(logs), else: []), polygon_edge_withdrawals = if(callback_module == Indexer.Block.Realtime.Fetcher, do: Withdrawals.parse(logs), else: []), polygon_edge_deposit_executes = @@ -215,6 +219,7 @@ defmodule Indexer.Block.Fetcher do }, chain_type_import_options = %{ transactions_with_receipts: transactions_with_receipts, + optimism_withdrawals: optimism_withdrawals, polygon_edge_withdrawals: polygon_edge_withdrawals, polygon_edge_deposit_executes: polygon_edge_deposit_executes, polygon_zkevm_bridge_operations: polygon_zkevm_bridge_operations, @@ -248,6 +253,7 @@ defmodule Indexer.Block.Fetcher do defp import_options(basic_import_options, %{ transactions_with_receipts: transactions_with_receipts, + optimism_withdrawals: optimism_withdrawals, polygon_edge_withdrawals: polygon_edge_withdrawals, polygon_edge_deposit_executes: polygon_edge_deposit_executes, polygon_zkevm_bridge_operations: polygon_zkevm_bridge_operations, @@ -260,6 +266,10 @@ defmodule Indexer.Block.Fetcher do params: transactions_with_receipts |> Enum.filter(&Map.has_key?(&1, :max_fee_per_blob_gas)) }) + "optimism" -> + basic_import_options + |> Map.put_new(:optimism_withdrawals, %{params: optimism_withdrawals}) + "polygon_edge" -> basic_import_options |> Map.put_new(:polygon_edge_withdrawals, %{params: polygon_edge_withdrawals}) diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index 31e3a83ea77c..61e86cf1220a 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -35,6 +35,8 @@ defmodule Indexer.Block.Realtime.Fetcher do alias Explorer.Utility.MissingRangesManipulator alias Indexer.{Block, Tracer} alias Indexer.Block.Realtime.TaskSupervisor + alias Indexer.Fetcher.Optimism.TxnBatch, as: OptimismTxnBatch + alias Indexer.Fetcher.Optimism.Withdrawal, as: OptimismWithdrawal alias Indexer.Fetcher.PolygonEdge.{DepositExecute, Withdrawal} alias Indexer.Fetcher.PolygonZkevm.BridgeL2, as: PolygonZkevmBridgeL2 alias Indexer.Fetcher.Shibarium.L2, as: ShibariumBridgeL2 @@ -264,6 +266,9 @@ defmodule Indexer.Block.Realtime.Fetcher do Indexer.Logger.metadata( fn -> if reorg? do + # we need to remove all rows from `op_transaction_batches` and `op_withdrawals` tables previously written starting from reorg block number + remove_optimism_assets_by_number(block_number_to_fetch) + # we need to remove all rows from `polygon_edge_withdrawals` and `polygon_edge_deposit_executes` tables previously written starting from reorg block number remove_polygon_edge_assets_by_number(block_number_to_fetch) @@ -285,6 +290,13 @@ defmodule Indexer.Block.Realtime.Fetcher do ) end + defp remove_optimism_assets_by_number(block_number_to_fetch) do + if Application.get_env(:explorer, :chain_type) == "optimism" do + OptimismTxnBatch.handle_l2_reorg(block_number_to_fetch) + OptimismWithdrawal.remove(block_number_to_fetch) + end + end + defp remove_polygon_edge_assets_by_number(block_number_to_fetch) do if Application.get_env(:explorer, :chain_type) == "polygon_edge" do Withdrawal.remove(block_number_to_fetch) diff --git a/apps/indexer/lib/indexer/fetcher/coin_balance/realtime.ex b/apps/indexer/lib/indexer/fetcher/coin_balance/realtime.ex index 72b216c92a30..ba93a5c790fb 100644 --- a/apps/indexer/lib/indexer/fetcher/coin_balance/realtime.ex +++ b/apps/indexer/lib/indexer/fetcher/coin_balance/realtime.ex @@ -9,7 +9,6 @@ defmodule Indexer.Fetcher.CoinBalance.Realtime do alias Explorer.Chain.{Block, Hash} alias Indexer.{BufferedTask, Tracer} alias Indexer.Fetcher.CoinBalance.Helper - alias Indexer.Fetcher.CoinBalance.Realtime.Supervisor, as: CoinBalanceSupervisor @behaviour BufferedTask diff --git a/apps/indexer/lib/indexer/fetcher/optimism.ex b/apps/indexer/lib/indexer/fetcher/optimism.ex new file mode 100644 index 000000000000..62d5fe1cd0be --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/optimism.ex @@ -0,0 +1,406 @@ +defmodule Indexer.Fetcher.Optimism do + @moduledoc """ + Contains common functions for Optimism* fetchers. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + import EthereumJSONRPC, + only: [ + fetch_block_number_by_tag_op_version: 2, + json_rpc: 2, + integer_to_quantity: 1, + quantity_to_integer: 1, + request: 1 + ] + + import Explorer.Helper, only: [parse_integer: 1] + + alias EthereumJSONRPC.Block.ByNumber + alias Explorer.Chain.Events.{Publisher, Subscriber} + alias Indexer.{BoundQueue, Helper} + + @fetcher_name :optimism + @block_check_interval_range_size 100 + @eth_get_logs_range_size 1000 + @finite_retries_number 3 + + def child_spec(start_link_arguments) do + spec = %{ + id: __MODULE__, + start: {__MODULE__, :start_link, start_link_arguments}, + restart: :transient, + type: :worker + } + + Supervisor.child_spec(spec, []) + end + + def start_link(args, gen_server_options \\ []) do + GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__)) + end + + @impl GenServer + def init(_args) do + Logger.metadata(fetcher: @fetcher_name) + + modules_using_reorg_monitor = [ + Indexer.Fetcher.Optimism.TxnBatch, + Indexer.Fetcher.Optimism.OutputRoot, + Indexer.Fetcher.Optimism.WithdrawalEvent + ] + + reorg_monitor_not_needed = + modules_using_reorg_monitor + |> Enum.all?(fn module -> + is_nil(Application.get_all_env(:indexer)[module][:start_block_l1]) + end) + + if reorg_monitor_not_needed do + :ignore + else + optimism_l1_rpc = Application.get_all_env(:indexer)[Indexer.Fetcher.Optimism][:optimism_l1_rpc] + + json_rpc_named_arguments = json_rpc_named_arguments(optimism_l1_rpc) + + {:ok, %{}, {:continue, json_rpc_named_arguments}} + end + end + + @impl GenServer + def handle_continue(json_rpc_named_arguments, _state) do + {:ok, block_check_interval, _} = get_block_check_interval(json_rpc_named_arguments) + Process.send(self(), :reorg_monitor, []) + + {:noreply, + %{block_check_interval: block_check_interval, json_rpc_named_arguments: json_rpc_named_arguments, prev_latest: 0}} + end + + @impl GenServer + def handle_info( + :reorg_monitor, + %{ + block_check_interval: block_check_interval, + json_rpc_named_arguments: json_rpc_named_arguments, + prev_latest: prev_latest + } = state + ) do + {:ok, latest} = get_block_number_by_tag("latest", json_rpc_named_arguments, Helper.infinite_retries_number()) + + if latest < prev_latest do + Logger.warning("Reorg detected: previous latest block ##{prev_latest}, current latest block ##{latest}.") + + Publisher.broadcast([{:optimism_reorg_block, latest}], :realtime) + end + + Process.send_after(self(), :reorg_monitor, block_check_interval) + + {:noreply, %{state | prev_latest: latest}} + end + + @doc """ + Calculates average block time in milliseconds (based on the latest 100 blocks) divided by 2. + Sends corresponding requests to the RPC node. + Returns a tuple {:ok, block_check_interval, last_safe_block} + where `last_safe_block` is the number of the recent `safe` or `latest` block (depending on which one is available). + Returns {:error, description} in case of error. + """ + @spec get_block_check_interval(list()) :: {:ok, non_neg_integer(), non_neg_integer()} | {:error, any()} + def get_block_check_interval(json_rpc_named_arguments) do + {last_safe_block, _} = get_safe_block(json_rpc_named_arguments) + + first_block = max(last_safe_block - @block_check_interval_range_size, 1) + + with {:ok, first_block_timestamp} <- + get_block_timestamp_by_number(first_block, json_rpc_named_arguments, Helper.infinite_retries_number()), + {:ok, last_safe_block_timestamp} <- + get_block_timestamp_by_number(last_safe_block, json_rpc_named_arguments, Helper.infinite_retries_number()) do + block_check_interval = + ceil((last_safe_block_timestamp - first_block_timestamp) / (last_safe_block - first_block) * 1000 / 2) + + Logger.info("Block check interval is calculated as #{block_check_interval} ms.") + {:ok, block_check_interval, last_safe_block} + else + {:error, error} -> + {:error, "Failed to calculate block check interval due to #{inspect(error)}"} + end + end + + @doc """ + Fetches block number by its tag (e.g. `latest` or `safe`) using RPC request. + Performs a specified number of retries (up to) if the first attempt returns error. + """ + @spec get_block_number_by_tag(binary(), list(), non_neg_integer()) :: {:ok, non_neg_integer()} | {:error, atom()} + def get_block_number_by_tag(tag, json_rpc_named_arguments, retries \\ @finite_retries_number) do + error_message = &"Cannot fetch #{tag} block number. Error: #{inspect(&1)}" + + Helper.repeated_call( + &fetch_block_number_by_tag_op_version/2, + [tag, json_rpc_named_arguments], + error_message, + retries + ) + end + + @doc """ + Tries to get `safe` block number from the RPC node. + If it's not available, gets the `latest` one. + Returns a tuple of `{block_number, is_latest}` + where `is_latest` is true if the `safe` is not available. + """ + @spec get_safe_block(list()) :: {non_neg_integer(), boolean()} + def get_safe_block(json_rpc_named_arguments) do + case get_block_number_by_tag("safe", json_rpc_named_arguments) do + {:ok, safe_block} -> + {safe_block, false} + + {:error, :not_found} -> + {:ok, latest_block} = + get_block_number_by_tag("latest", json_rpc_named_arguments, Helper.infinite_retries_number()) + + {latest_block, true} + end + end + + defp get_block_timestamp_by_number_inner(number, json_rpc_named_arguments) do + result = + %{id: 0, number: number} + |> ByNumber.request(false) + |> json_rpc(json_rpc_named_arguments) + + with {:ok, block} <- result, + false <- is_nil(block), + timestamp <- Map.get(block, "timestamp"), + false <- is_nil(timestamp) do + {:ok, quantity_to_integer(timestamp)} + else + {:error, message} -> + {:error, message} + + true -> + {:error, "RPC returned nil."} + end + end + + @doc """ + Fetches block timestamp by its number using RPC request. + Performs a specified number of retries (up to) if the first attempt returns error. + """ + @spec get_block_timestamp_by_number(non_neg_integer(), list(), non_neg_integer()) :: + {:ok, non_neg_integer()} | {:error, any()} + def get_block_timestamp_by_number(number, json_rpc_named_arguments, retries \\ @finite_retries_number) do + func = &get_block_timestamp_by_number_inner/2 + args = [number, json_rpc_named_arguments] + error_message = &"Cannot fetch block ##{number} or its timestamp. Error: #{inspect(&1)}" + Helper.repeated_call(func, args, error_message, retries) + end + + @doc """ + Fetches logs emitted by the specified contract (address) + within the specified block range and the first topic from the RPC node. + Performs a specified number of retries (up to) if the first attempt returns error. + """ + @spec get_logs( + non_neg_integer() | binary(), + non_neg_integer() | binary(), + binary(), + binary() | list(), + list(), + non_neg_integer() + ) :: {:ok, list()} | {:error, term()} + def get_logs(from_block, to_block, address, topic0, json_rpc_named_arguments, retries) do + processed_from_block = if is_integer(from_block), do: integer_to_quantity(from_block), else: from_block + processed_to_block = if is_integer(to_block), do: integer_to_quantity(to_block), else: to_block + + req = + request(%{ + id: 0, + method: "eth_getLogs", + params: [ + %{ + :fromBlock => processed_from_block, + :toBlock => processed_to_block, + :address => address, + :topics => [topic0] + } + ] + }) + + error_message = &"Cannot fetch logs for the block range #{from_block}..#{to_block}. Error: #{inspect(&1)}" + + Helper.repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) + end + + @doc """ + Fetches transaction data by its hash using RPC request. + Performs a specified number of retries (up to) if the first attempt returns error. + """ + @spec get_transaction_by_hash(binary() | nil, list(), non_neg_integer()) :: {:ok, any()} | {:error, any()} + def get_transaction_by_hash(hash, json_rpc_named_arguments, retries_left \\ @finite_retries_number) + + def get_transaction_by_hash(hash, _json_rpc_named_arguments, _retries_left) when is_nil(hash), do: {:ok, nil} + + def get_transaction_by_hash(hash, json_rpc_named_arguments, retries) do + req = + request(%{ + id: 0, + method: "eth_getTransactionByHash", + params: [hash] + }) + + error_message = &"eth_getTransactionByHash failed. Error: #{inspect(&1)}" + + Helper.repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) + end + + def get_logs_range_size do + @eth_get_logs_range_size + end + + @doc """ + Forms JSON RPC named arguments for the given RPC URL. + """ + @spec json_rpc_named_arguments(binary()) :: list() + def json_rpc_named_arguments(optimism_l1_rpc) do + [ + transport: EthereumJSONRPC.HTTP, + transport_options: [ + http: EthereumJSONRPC.HTTP.HTTPoison, + url: optimism_l1_rpc, + http_options: [ + recv_timeout: :timer.minutes(10), + timeout: :timer.minutes(10), + hackney: [pool: :ethereum_jsonrpc] + ] + ] + ] + end + + def init_continue(env, contract_address, caller) + when caller in [Indexer.Fetcher.Optimism.WithdrawalEvent, Indexer.Fetcher.Optimism.OutputRoot] do + {contract_name, table_name, start_block_note} = + if caller == Indexer.Fetcher.Optimism.WithdrawalEvent do + {"Optimism Portal", "op_withdrawal_events", "Withdrawals L1"} + else + {"Output Oracle", "op_output_roots", "Output Roots"} + end + + with {:start_block_l1_undefined, false} <- {:start_block_l1_undefined, is_nil(env[:start_block_l1])}, + {:reorg_monitor_started, true} <- {:reorg_monitor_started, !is_nil(Process.whereis(Indexer.Fetcher.Optimism))}, + optimism_l1_rpc = Application.get_all_env(:indexer)[Indexer.Fetcher.Optimism][:optimism_l1_rpc], + {:rpc_l1_undefined, false} <- {:rpc_l1_undefined, is_nil(optimism_l1_rpc)}, + {:contract_is_valid, true} <- {:contract_is_valid, Helper.address_correct?(contract_address)}, + start_block_l1 = parse_integer(env[:start_block_l1]), + false <- is_nil(start_block_l1), + true <- start_block_l1 > 0, + {last_l1_block_number, last_l1_transaction_hash} <- caller.get_last_l1_item(), + {:start_block_l1_valid, true} <- + {:start_block_l1_valid, start_block_l1 <= last_l1_block_number || last_l1_block_number == 0}, + json_rpc_named_arguments = json_rpc_named_arguments(optimism_l1_rpc), + {:ok, last_l1_tx} <- get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), + {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)}, + {:ok, block_check_interval, last_safe_block} <- get_block_check_interval(json_rpc_named_arguments) do + start_block = max(start_block_l1, last_l1_block_number) + + Subscriber.to(:optimism_reorg_block, :realtime) + + Process.send(self(), :continue, []) + + {:noreply, + %{ + contract_address: contract_address, + block_check_interval: block_check_interval, + start_block: start_block, + end_block: last_safe_block, + json_rpc_named_arguments: json_rpc_named_arguments + }} + else + {:start_block_l1_undefined, true} -> + # the process shouldn't start if the start block is not defined + {:stop, :normal, %{}} + + {:reorg_monitor_started, false} -> + Logger.error("Cannot start this process as reorg monitor in Indexer.Fetcher.Optimism is not started.") + {:stop, :normal, %{}} + + {:rpc_l1_undefined, true} -> + Logger.error("L1 RPC URL is not defined.") + {:stop, :normal, %{}} + + {:contract_is_valid, false} -> + Logger.error("#{contract_name} contract address is invalid or not defined.") + {:stop, :normal, %{}} + + {:start_block_l1_valid, false} -> + Logger.error("Invalid L1 Start Block value. Please, check the value and #{table_name} table.") + {:stop, :normal, %{}} + + {:error, error_data} -> + Logger.error( + "Cannot get last L1 transaction from RPC by its hash, last safe/latest block, or block timestamp by its number due to RPC error: #{inspect(error_data)}" + ) + + {:stop, :normal, %{}} + + {:l1_tx_not_found, true} -> + Logger.error( + "Cannot find last L1 transaction from RPC by its hash. Probably, there was a reorg on L1 chain. Please, check #{table_name} table." + ) + + {:stop, :normal, %{}} + + _ -> + Logger.error("#{start_block_note} Start Block is invalid or zero.") + {:stop, :normal, %{}} + end + end + + def repeated_request(req, error_message, json_rpc_named_arguments, retries) do + Helper.repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) + end + + def reorg_block_pop(fetcher_name) do + table_name = reorg_table_name(fetcher_name) + + case BoundQueue.pop_front(reorg_queue_get(table_name)) do + {:ok, {block_number, updated_queue}} -> + :ets.insert(table_name, {:queue, updated_queue}) + block_number + + {:error, :empty} -> + nil + end + end + + def reorg_block_push(fetcher_name, block_number) do + table_name = reorg_table_name(fetcher_name) + {:ok, updated_queue} = BoundQueue.push_back(reorg_queue_get(table_name), block_number) + :ets.insert(table_name, {:queue, updated_queue}) + end + + defp reorg_queue_get(table_name) do + if :ets.whereis(table_name) == :undefined do + :ets.new(table_name, [ + :set, + :named_table, + :public, + read_concurrency: true, + write_concurrency: true + ]) + end + + with info when info != :undefined <- :ets.info(table_name), + [{_, value}] <- :ets.lookup(table_name, :queue) do + value + else + _ -> %BoundQueue{} + end + end + + defp reorg_table_name(fetcher_name) do + :"#{fetcher_name}#{:_reorgs}" + end +end diff --git a/apps/indexer/lib/indexer/fetcher/optimism/deposit.ex b/apps/indexer/lib/indexer/fetcher/optimism/deposit.ex new file mode 100644 index 000000000000..c3a436358757 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/optimism/deposit.ex @@ -0,0 +1,565 @@ +defmodule Indexer.Fetcher.Optimism.Deposit do + @moduledoc """ + Fills op_deposits DB table. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + import Ecto.Query + + import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1, request: 1] + import Explorer.Helper, only: [decode_data: 2, parse_integer: 1] + + alias EthereumJSONRPC.Block.ByNumber + alias EthereumJSONRPC.Blocks + alias Explorer.{Chain, Repo} + alias Explorer.Chain.Events.Publisher + alias Explorer.Chain.Optimism.Deposit + alias Indexer.Fetcher.Optimism + alias Indexer.Helper + + defstruct [ + :batch_size, + :start_block, + :from_block, + :safe_block, + :optimism_portal, + :json_rpc_named_arguments, + mode: :catch_up, + filter_id: nil, + check_interval: nil + ] + + # 32-byte signature of the event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData) + @transaction_deposited_event "0xb3813568d9991fc951961fcb4c784893574240a28925604d09fc577c55bb7c32" + @retry_interval_minutes 3 + @retry_interval :timer.minutes(@retry_interval_minutes) + @address_prefix "0x000000000000000000000000" + @batch_size 500 + @fetcher_name :optimism_deposits + + def child_spec(start_link_arguments) do + spec = %{ + id: __MODULE__, + start: {__MODULE__, :start_link, start_link_arguments}, + restart: :transient, + type: :worker + } + + Supervisor.child_spec(spec, []) + end + + def start_link(args, gen_server_options \\ []) do + GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__)) + end + + @impl GenServer + def init(_args) do + {:ok, %{}, {:continue, :ok}} + end + + @impl GenServer + def handle_continue(:ok, state) do + Logger.metadata(fetcher: @fetcher_name) + + env = Application.get_all_env(:indexer)[__MODULE__] + optimism_env = Application.get_all_env(:indexer)[Indexer.Fetcher.Optimism] + optimism_portal = optimism_env[:optimism_l1_portal] + optimism_l1_rpc = optimism_env[:optimism_l1_rpc] + + with {:start_block_l1_undefined, false} <- {:start_block_l1_undefined, is_nil(env[:start_block_l1])}, + {:optimism_portal_valid, true} <- {:optimism_portal_valid, Helper.address_correct?(optimism_portal)}, + {:rpc_l1_undefined, false} <- {:rpc_l1_undefined, is_nil(optimism_l1_rpc)}, + start_block_l1 <- parse_integer(env[:start_block_l1]), + false <- is_nil(start_block_l1), + true <- start_block_l1 > 0, + {last_l1_block_number, last_l1_tx_hash} <- get_last_l1_item(), + json_rpc_named_arguments = Optimism.json_rpc_named_arguments(optimism_l1_rpc), + {:ok, last_l1_tx} <- Optimism.get_transaction_by_hash(last_l1_tx_hash, json_rpc_named_arguments), + {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_tx_hash) && is_nil(last_l1_tx)}, + {safe_block, _} = Optimism.get_safe_block(json_rpc_named_arguments), + {:start_block_l1_valid, true} <- + {:start_block_l1_valid, + (start_block_l1 <= last_l1_block_number || last_l1_block_number == 0) && start_block_l1 <= safe_block} do + start_block = max(start_block_l1, last_l1_block_number) + + if start_block > safe_block do + Process.send(self(), :switch_to_realtime, []) + else + Process.send(self(), :fetch, []) + end + + {:noreply, + %__MODULE__{ + start_block: start_block, + from_block: start_block, + safe_block: safe_block, + optimism_portal: optimism_portal, + json_rpc_named_arguments: json_rpc_named_arguments, + batch_size: parse_integer(env[:batch_size]) || @batch_size + }} + else + {:start_block_l1_undefined, true} -> + # the process shouldn't start if the start block is not defined + {:stop, :normal, state} + + {:start_block_l1_valid, false} -> + Logger.error("Invalid L1 Start Block value. Please, check the value and op_deposits table.") + {:stop, :normal, state} + + {:rpc_l1_undefined, true} -> + Logger.error("L1 RPC URL is not defined.") + {:stop, :normal, state} + + {:optimism_portal_valid, false} -> + Logger.error("OptimismPortal contract address is invalid or undefined.") + {:stop, :normal, state} + + {:error, error_data} -> + Logger.error("Cannot get last L1 transaction from RPC by its hash due to the RPC error: #{inspect(error_data)}") + + {:stop, :normal, state} + + {:l1_tx_not_found, true} -> + Logger.error( + "Cannot find last L1 transaction from RPC by its hash. Probably, there was a reorg on L1 chain. Please, check op_deposits table." + ) + + {:stop, :normal, state} + + _ -> + Logger.error("Optimism deposits L1 Start Block is invalid or zero.") + {:stop, :normal, state} + end + end + + @impl GenServer + def handle_info( + :fetch, + %__MODULE__{ + start_block: start_block, + from_block: from_block, + safe_block: safe_block, + optimism_portal: optimism_portal, + json_rpc_named_arguments: json_rpc_named_arguments, + mode: :catch_up, + batch_size: batch_size + } = state + ) do + to_block = min(from_block + batch_size, safe_block) + + with {:logs, {:ok, logs}} <- + {:logs, + Optimism.get_logs( + from_block, + to_block, + optimism_portal, + @transaction_deposited_event, + json_rpc_named_arguments, + 3 + )}, + _ = Helper.log_blocks_chunk_handling(from_block, to_block, start_block, safe_block, nil, :L1), + deposits = events_to_deposits(logs, json_rpc_named_arguments), + {:import, {:ok, _imported}} <- + {:import, Chain.import(%{optimism_deposits: %{params: deposits}, timeout: :infinity})} do + Publisher.broadcast(%{optimism_deposits: deposits}, :realtime) + + Helper.log_blocks_chunk_handling( + from_block, + to_block, + start_block, + safe_block, + "#{Enum.count(deposits)} TransactionDeposited event(s)", + :L1 + ) + + if to_block == safe_block do + Logger.info("Fetched all L1 blocks (#{start_block}..#{safe_block}), switching to realtime mode.") + Process.send(self(), :switch_to_realtime, []) + {:noreply, state} + else + Process.send(self(), :fetch, []) + {:noreply, %{state | from_block: to_block + 1}} + end + else + {:logs, {:error, _error}} -> + Logger.error("Cannot fetch logs. Retrying in #{@retry_interval_minutes} minutes...") + Process.send_after(self(), :fetch, @retry_interval) + {:noreply, state} + + {:import, {:error, error}} -> + Logger.error("Cannot import logs due to #{inspect(error)}. Retrying in #{@retry_interval_minutes} minutes...") + Process.send_after(self(), :fetch, @retry_interval) + {:noreply, state} + + {:import, {:error, step, failed_value, _changes_so_far}} -> + Logger.error( + "Failed to import #{inspect(failed_value)} during #{step}. Retrying in #{@retry_interval_minutes} minutes..." + ) + + Process.send_after(self(), :fetch, @retry_interval) + {:noreply, state} + end + end + + @impl GenServer + def handle_info( + :switch_to_realtime, + %__MODULE__{ + from_block: from_block, + safe_block: safe_block, + optimism_portal: optimism_portal, + json_rpc_named_arguments: json_rpc_named_arguments, + batch_size: batch_size, + mode: :catch_up + } = state + ) do + with {:check_interval, {:ok, check_interval, new_safe}} <- + {:check_interval, Optimism.get_block_check_interval(json_rpc_named_arguments)}, + {:catch_up, _, false} <- {:catch_up, new_safe, new_safe - safe_block + 1 > batch_size}, + {:logs, {:ok, logs}} <- + {:logs, + Optimism.get_logs( + max(safe_block, from_block), + "latest", + optimism_portal, + @transaction_deposited_event, + json_rpc_named_arguments, + 3 + )}, + {:ok, filter_id} <- + get_new_filter( + max(safe_block, from_block), + "latest", + optimism_portal, + @transaction_deposited_event, + json_rpc_named_arguments + ) do + handle_new_logs(logs, json_rpc_named_arguments) + Process.send(self(), :fetch, []) + {:noreply, %{state | mode: :realtime, filter_id: filter_id, check_interval: check_interval}} + else + {:catch_up, new_safe, true} -> + Process.send(self(), :fetch, []) + {:noreply, %{state | safe_block: new_safe}} + + {:logs, {:error, error}} -> + Logger.error("Failed to get logs while switching to realtime mode, reason: #{inspect(error)}") + Process.send_after(self(), :switch_to_realtime, @retry_interval) + {:noreply, state} + + {:error, _error} -> + Logger.error("Failed to set logs filter. Retrying in #{@retry_interval_minutes} minutes...") + Process.send_after(self(), :switch_to_realtime, @retry_interval) + {:noreply, state} + + {:check_interval, {:error, _error}} -> + Logger.error("Failed to calculate check_interval. Retrying in #{@retry_interval_minutes} minutes...") + Process.send_after(self(), :switch_to_realtime, @retry_interval) + {:noreply, state} + end + end + + @impl GenServer + def handle_info( + :fetch, + %__MODULE__{ + json_rpc_named_arguments: json_rpc_named_arguments, + mode: :realtime, + filter_id: filter_id, + check_interval: check_interval + } = state + ) do + case get_filter_changes(filter_id, json_rpc_named_arguments) do + {:ok, logs} -> + handle_new_logs(logs, json_rpc_named_arguments) + Process.send_after(self(), :fetch, check_interval) + {:noreply, state} + + {:error, :filter_not_found} -> + Logger.error("The old filter not found on the node. Creating new filter...") + Process.send(self(), :update_filter, []) + {:noreply, state} + + {:error, _error} -> + Logger.error("Failed to set logs filter. Retrying in #{@retry_interval_minutes} minutes...") + Process.send_after(self(), :fetch, @retry_interval) + {:noreply, state} + end + end + + @impl GenServer + def handle_info( + :update_filter, + %__MODULE__{ + optimism_portal: optimism_portal, + json_rpc_named_arguments: json_rpc_named_arguments, + mode: :realtime + } = state + ) do + {last_l1_block_number, _} = get_last_l1_item() + + case get_new_filter( + last_l1_block_number + 1, + "latest", + optimism_portal, + @transaction_deposited_event, + json_rpc_named_arguments + ) do + {:ok, filter_id} -> + Process.send(self(), :fetch, []) + {:noreply, %{state | filter_id: filter_id}} + + {:error, _error} -> + Logger.error("Failed to set logs filter. Retrying in #{@retry_interval_minutes} minutes...") + Process.send_after(self(), :update_filter, @retry_interval) + {:noreply, state} + end + end + + @impl GenServer + def handle_info({ref, _result}, state) do + Process.demonitor(ref, [:flush]) + {:noreply, state} + end + + @impl GenServer + def terminate( + _reason, + %__MODULE__{ + json_rpc_named_arguments: json_rpc_named_arguments + } = state + ) do + if state.filter_id do + Logger.info("Optimism deposits fetcher is terminating, uninstalling filter") + uninstall_filter(state.filter_id, json_rpc_named_arguments) + end + end + + @impl GenServer + def terminate(:normal, _state) do + :ok + end + + defp handle_new_logs(logs, json_rpc_named_arguments) do + {reorgs, logs_to_parse, min_block, max_block, cnt} = + logs + |> Enum.reduce({MapSet.new(), [], nil, 0, 0}, fn + %{"removed" => true, "blockNumber" => block_number}, {reorgs, logs_to_parse, min_block, max_block, cnt} -> + {MapSet.put(reorgs, block_number), logs_to_parse, min_block, max_block, cnt} + + %{"blockNumber" => block_number} = log, {reorgs, logs_to_parse, min_block, max_block, cnt} -> + { + reorgs, + [log | logs_to_parse], + min(min_block, quantity_to_integer(block_number)), + max(max_block, quantity_to_integer(block_number)), + cnt + 1 + } + end) + + handle_reorgs(reorgs) + + unless Enum.empty?(logs_to_parse) do + deposits = events_to_deposits(logs_to_parse, json_rpc_named_arguments) + {:ok, _imported} = Chain.import(%{optimism_deposits: %{params: deposits}, timeout: :infinity}) + + Publisher.broadcast(%{optimism_deposits: deposits}, :realtime) + + Helper.log_blocks_chunk_handling( + min_block, + max_block, + min_block, + max_block, + "#{cnt} TransactionDeposited event(s)", + :L1 + ) + end + end + + defp events_to_deposits(logs, json_rpc_named_arguments) do + timestamps = + logs + |> Enum.reduce(MapSet.new(), fn %{"blockNumber" => block_number_quantity}, acc -> + block_number = quantity_to_integer(block_number_quantity) + MapSet.put(acc, block_number) + end) + |> MapSet.to_list() + |> get_block_timestamps_by_numbers(json_rpc_named_arguments) + |> case do + {:ok, timestamps} -> + timestamps + + {:error, error} -> + Logger.error( + "Failed to get L1 block timestamps for deposits due to #{inspect(error)}. Timestamps will be set to null." + ) + + %{} + end + + Enum.map(logs, &event_to_deposit(&1, timestamps)) + end + + defp event_to_deposit( + %{ + "blockHash" => "0x" <> stripped_block_hash, + "blockNumber" => block_number_quantity, + "transactionHash" => transaction_hash, + "logIndex" => "0x" <> stripped_log_index, + "topics" => [_, @address_prefix <> from_stripped, @address_prefix <> to_stripped, _], + "data" => opaque_data + }, + timestamps + ) do + {_, prefixed_block_hash} = (String.pad_leading("", 64, "0") <> stripped_block_hash) |> String.split_at(-64) + {_, prefixed_log_index} = (String.pad_leading("", 64, "0") <> stripped_log_index) |> String.split_at(-64) + + deposit_id_hash = + "#{prefixed_block_hash}#{prefixed_log_index}" + |> Base.decode16!(case: :mixed) + |> ExKeccak.hash_256() + |> Base.encode16(case: :lower) + + source_hash = + "#{String.pad_leading("", 64, "0")}#{deposit_id_hash}" + |> Base.decode16!(case: :mixed) + |> ExKeccak.hash_256() + + [ + << + msg_value::binary-size(32), + value::binary-size(32), + gas_limit::binary-size(8), + is_creation::binary-size(1), + data::binary + >> + ] = decode_data(opaque_data, [:bytes]) + + rlp_encoded = + ExRLP.encode( + [ + source_hash, + from_stripped |> Base.decode16!(case: :mixed), + to_stripped |> Base.decode16!(case: :mixed), + msg_value |> String.replace_leading(<<0>>, <<>>), + value |> String.replace_leading(<<0>>, <<>>), + gas_limit |> String.replace_leading(<<0>>, <<>>), + is_creation |> String.replace_leading(<<0>>, <<>>), + data + ], + encoding: :hex + ) + + l2_tx_hash = + "0x" <> ("7e#{rlp_encoded}" |> Base.decode16!(case: :mixed) |> ExKeccak.hash_256() |> Base.encode16(case: :lower)) + + block_number = quantity_to_integer(block_number_quantity) + + %{ + l1_block_number: block_number, + l1_block_timestamp: Map.get(timestamps, block_number), + l1_transaction_hash: transaction_hash, + l1_transaction_origin: "0x" <> from_stripped, + l2_transaction_hash: l2_tx_hash + } + end + + defp handle_reorgs(reorgs) do + if MapSet.size(reorgs) > 0 do + Logger.warning("L1 reorg detected. The following L1 blocks were removed: #{inspect(MapSet.to_list(reorgs))}") + + {deleted_count, _} = Repo.delete_all(from(d in Deposit, where: d.l1_block_number in ^reorgs)) + + if deleted_count > 0 do + Logger.warning( + "As L1 reorg was detected, all affected rows were removed from the op_deposits table. Number of removed rows: #{deleted_count}." + ) + end + end + end + + defp get_block_timestamps_by_numbers(numbers, json_rpc_named_arguments, retries \\ 3) do + id_to_params = + numbers + |> Stream.map(fn number -> %{number: number} end) + |> Stream.with_index() + |> Enum.into(%{}, fn {params, id} -> {id, params} end) + + request = Blocks.requests(id_to_params, &ByNumber.request(&1, false)) + error_message = &"Cannot fetch timestamps for blocks #{numbers}. Error: #{inspect(&1)}" + + case Optimism.repeated_request(request, error_message, json_rpc_named_arguments, retries) do + {:ok, response} -> + %Blocks{blocks_params: blocks_params} = Blocks.from_responses(response, id_to_params) + + {:ok, + blocks_params + |> Enum.reduce(%{}, fn %{number: number, timestamp: timestamp}, acc -> Map.put_new(acc, number, timestamp) end)} + + err -> + err + end + end + + defp get_new_filter(from_block, to_block, address, topic0, json_rpc_named_arguments, retries \\ 3) do + processed_from_block = if is_integer(from_block), do: integer_to_quantity(from_block), else: from_block + processed_to_block = if is_integer(to_block), do: integer_to_quantity(to_block), else: to_block + + req = + request(%{ + id: 0, + method: "eth_newFilter", + params: [ + %{ + fromBlock: processed_from_block, + toBlock: processed_to_block, + address: address, + topics: [topic0] + } + ] + }) + + error_message = &"Cannot create new log filter. Error: #{inspect(&1)}" + + Optimism.repeated_request(req, error_message, json_rpc_named_arguments, retries) + end + + defp get_filter_changes(filter_id, json_rpc_named_arguments, retries \\ 3) do + req = + request(%{ + id: 0, + method: "eth_getFilterChanges", + params: [filter_id] + }) + + error_message = &"Cannot fetch filter changes. Error: #{inspect(&1)}" + + case Optimism.repeated_request(req, error_message, json_rpc_named_arguments, retries) do + {:error, %{code: _, message: "filter not found"}} -> {:error, :filter_not_found} + response -> response + end + end + + defp uninstall_filter(filter_id, json_rpc_named_arguments, retries \\ 1) do + req = + request(%{ + id: 0, + method: "eth_getFilterChanges", + params: [filter_id] + }) + + error_message = &"Cannot uninstall filter. Error: #{inspect(&1)}" + + Optimism.repeated_request(req, error_message, json_rpc_named_arguments, retries) + end + + defp get_last_l1_item do + Deposit.last_deposit_l1_block_number_query() + |> Repo.one() + |> Kernel.||({0, nil}) + end +end diff --git a/apps/indexer/lib/indexer/fetcher/optimism/output_root.ex b/apps/indexer/lib/indexer/fetcher/optimism/output_root.ex new file mode 100644 index 000000000000..ca35b71a83e0 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/optimism/output_root.ex @@ -0,0 +1,187 @@ +defmodule Indexer.Fetcher.Optimism.OutputRoot do + @moduledoc """ + Fills op_output_roots DB table. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + import Ecto.Query + + import EthereumJSONRPC, only: [quantity_to_integer: 1] + + alias Explorer.{Chain, Helper, Repo} + alias Explorer.Chain.Optimism.OutputRoot + alias Indexer.Fetcher.Optimism + alias Indexer.Helper, as: IndexerHelper + + @fetcher_name :optimism_output_roots + + # 32-byte signature of the event OutputProposed(bytes32 indexed outputRoot, uint256 indexed l2OutputIndex, uint256 indexed l2BlockNumber, uint256 l1Timestamp) + @output_proposed_event "0xa7aaf2512769da4e444e3de247be2564225c2e7a8f74cfe528e46e17d24868e2" + + def child_spec(start_link_arguments) do + spec = %{ + id: __MODULE__, + start: {__MODULE__, :start_link, start_link_arguments}, + restart: :transient, + type: :worker + } + + Supervisor.child_spec(spec, []) + end + + def start_link(args, gen_server_options \\ []) do + GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__)) + end + + @impl GenServer + def init(_args) do + {:ok, %{}, {:continue, :ok}} + end + + @impl GenServer + def handle_continue(:ok, _state) do + Logger.metadata(fetcher: @fetcher_name) + + env = Application.get_all_env(:indexer)[__MODULE__] + + Optimism.init_continue(env, env[:output_oracle], __MODULE__) + end + + @impl GenServer + def handle_info( + :continue, + %{ + contract_address: output_oracle, + block_check_interval: block_check_interval, + start_block: start_block, + end_block: end_block, + json_rpc_named_arguments: json_rpc_named_arguments + } = state + ) do + # credo:disable-for-next-line + time_before = Timex.now() + + chunks_number = ceil((end_block - start_block + 1) / Optimism.get_logs_range_size()) + chunk_range = Range.new(0, max(chunks_number - 1, 0), 1) + + last_written_block = + chunk_range + |> Enum.reduce_while(start_block - 1, fn current_chunk, _ -> + chunk_start = start_block + Optimism.get_logs_range_size() * current_chunk + chunk_end = min(chunk_start + Optimism.get_logs_range_size() - 1, end_block) + + if chunk_end >= chunk_start do + IndexerHelper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, :L1) + + {:ok, result} = + Optimism.get_logs( + chunk_start, + chunk_end, + output_oracle, + @output_proposed_event, + json_rpc_named_arguments, + IndexerHelper.infinite_retries_number() + ) + + output_roots = events_to_output_roots(result) + + {:ok, _} = + Chain.import(%{ + optimism_output_roots: %{params: output_roots}, + timeout: :infinity + }) + + IndexerHelper.log_blocks_chunk_handling( + chunk_start, + chunk_end, + start_block, + end_block, + "#{Enum.count(output_roots)} OutputProposed event(s)", + :L1 + ) + end + + reorg_block = Optimism.reorg_block_pop(@fetcher_name) + + if !is_nil(reorg_block) && reorg_block > 0 do + {deleted_count, _} = Repo.delete_all(from(r in OutputRoot, where: r.l1_block_number >= ^reorg_block)) + + log_deleted_rows_count(reorg_block, deleted_count) + + {:halt, if(reorg_block <= chunk_end, do: reorg_block - 1, else: chunk_end)} + else + {:cont, chunk_end} + end + end) + + new_start_block = last_written_block + 1 + + {:ok, new_end_block} = + Optimism.get_block_number_by_tag("latest", json_rpc_named_arguments, IndexerHelper.infinite_retries_number()) + + delay = + if new_end_block == last_written_block do + # there is no new block, so wait for some time to let the chain issue the new block + max(block_check_interval - Timex.diff(Timex.now(), time_before, :milliseconds), 0) + else + 0 + end + + Process.send_after(self(), :continue, delay) + + {:noreply, %{state | start_block: new_start_block, end_block: new_end_block}} + end + + @impl GenServer + def handle_info({:chain_event, :optimism_reorg_block, :realtime, block_number}, state) do + Optimism.reorg_block_push(@fetcher_name, block_number) + {:noreply, state} + end + + @impl GenServer + def handle_info({ref, _result}, state) do + Process.demonitor(ref, [:flush]) + {:noreply, state} + end + + defp events_to_output_roots(events) do + Enum.map(events, fn event -> + [l1_timestamp] = Helper.decode_data(event["data"], [{:uint, 256}]) + {:ok, l1_timestamp} = DateTime.from_unix(l1_timestamp) + + %{ + l2_output_index: quantity_to_integer(Enum.at(event["topics"], 2)), + l2_block_number: quantity_to_integer(Enum.at(event["topics"], 3)), + l1_transaction_hash: event["transactionHash"], + l1_timestamp: l1_timestamp, + l1_block_number: quantity_to_integer(event["blockNumber"]), + output_root: Enum.at(event["topics"], 1) + } + end) + end + + defp log_deleted_rows_count(reorg_block, count) do + if count > 0 do + Logger.warning( + "As L1 reorg was detected, all rows with l1_block_number >= #{reorg_block} were removed from the op_output_roots table. Number of removed rows: #{count}." + ) + end + end + + def get_last_l1_item do + query = + from(root in OutputRoot, + select: {root.l1_block_number, root.l1_transaction_hash}, + order_by: [desc: root.l2_output_index], + limit: 1 + ) + + query + |> Repo.one() + |> Kernel.||({0, nil}) + end +end diff --git a/apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex b/apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex new file mode 100644 index 000000000000..01bc603caeb4 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex @@ -0,0 +1,850 @@ +defmodule Indexer.Fetcher.Optimism.TxnBatch do + @moduledoc """ + Fills op_transaction_batches DB table. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + import Ecto.Query + + import EthereumJSONRPC, only: [fetch_blocks_by_range: 2, json_rpc: 2, quantity_to_integer: 1] + + import Explorer.Helper, only: [parse_integer: 1] + + alias EthereumJSONRPC.Block.ByHash + alias EthereumJSONRPC.Blocks + alias Explorer.{Chain, Repo} + alias Explorer.Chain.Block + alias Explorer.Chain.Events.Subscriber + alias Explorer.Chain.Optimism.FrameSequence + alias Explorer.Chain.Optimism.TxnBatch, as: OptimismTxnBatch + alias Indexer.Fetcher.Optimism + alias Indexer.Helper + alias Varint.LEB128 + + @fetcher_name :optimism_txn_batches + + # Optimism chain block time is a constant (2 seconds) + @op_chain_block_time 2 + + def child_spec(start_link_arguments) do + spec = %{ + id: __MODULE__, + start: {__MODULE__, :start_link, start_link_arguments}, + restart: :transient, + type: :worker + } + + Supervisor.child_spec(spec, []) + end + + def start_link(args, gen_server_options \\ []) do + GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__)) + end + + @impl GenServer + def init(args) do + {:ok, %{json_rpc_named_arguments_l2: args[:json_rpc_named_arguments]}, {:continue, nil}} + end + + @impl GenServer + def handle_continue(_, state) do + Logger.metadata(fetcher: @fetcher_name) + # two seconds pause needed to avoid exceeding Supervisor restart intensity when DB issues + Process.send_after(self(), :init_with_delay, 2000) + {:noreply, state} + end + + @impl GenServer + def handle_info(:init_with_delay, %{json_rpc_named_arguments_l2: json_rpc_named_arguments_l2} = state) do + env = Application.get_all_env(:indexer)[__MODULE__] + + with {:start_block_l1_undefined, false} <- {:start_block_l1_undefined, is_nil(env[:start_block_l1])}, + {:genesis_block_l2_invalid, false} <- + {:genesis_block_l2_invalid, is_nil(env[:genesis_block_l2]) or env[:genesis_block_l2] < 0}, + {:reorg_monitor_started, true} <- {:reorg_monitor_started, !is_nil(Process.whereis(Indexer.Fetcher.Optimism))}, + optimism_l1_rpc = Application.get_all_env(:indexer)[Indexer.Fetcher.Optimism][:optimism_l1_rpc], + {:rpc_l1_undefined, false} <- {:rpc_l1_undefined, is_nil(optimism_l1_rpc)}, + {:batch_inbox_valid, true} <- {:batch_inbox_valid, Helper.address_correct?(env[:batch_inbox])}, + {:batch_submitter_valid, true} <- {:batch_submitter_valid, Helper.address_correct?(env[:batch_submitter])}, + start_block_l1 = parse_integer(env[:start_block_l1]), + false <- is_nil(start_block_l1), + true <- start_block_l1 > 0, + chunk_size = parse_integer(env[:blocks_chunk_size]), + {:chunk_size_valid, true} <- {:chunk_size_valid, !is_nil(chunk_size) && chunk_size > 0}, + json_rpc_named_arguments = Optimism.json_rpc_named_arguments(optimism_l1_rpc), + {last_l1_block_number, last_l1_transaction_hash, last_l1_tx} = get_last_l1_item(json_rpc_named_arguments), + {:start_block_l1_valid, true} <- + {:start_block_l1_valid, start_block_l1 <= last_l1_block_number || last_l1_block_number == 0}, + {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)}, + {:ok, block_check_interval, last_safe_block} <- Optimism.get_block_check_interval(json_rpc_named_arguments) do + start_block = max(start_block_l1, last_l1_block_number) + + Subscriber.to(:optimism_reorg_block, :realtime) + + Process.send(self(), :continue, []) + + {:noreply, + %{ + batch_inbox: String.downcase(env[:batch_inbox]), + batch_submitter: String.downcase(env[:batch_submitter]), + block_check_interval: block_check_interval, + start_block: start_block, + end_block: last_safe_block, + chunk_size: chunk_size, + incomplete_channels: %{}, + genesis_block_l2: env[:genesis_block_l2], + json_rpc_named_arguments: json_rpc_named_arguments, + json_rpc_named_arguments_l2: json_rpc_named_arguments_l2 + }} + else + {:start_block_l1_undefined, true} -> + # the process shouldn't start if the start block is not defined + {:stop, :normal, state} + + {:genesis_block_l2_invalid, true} -> + Logger.error("L2 genesis block number is undefined or invalid.") + {:stop, :normal, state} + + {:reorg_monitor_started, false} -> + Logger.error("Cannot start this process as reorg monitor in Indexer.Fetcher.Optimism is not started.") + {:stop, :normal, state} + + {:rpc_l1_undefined, true} -> + Logger.error("L1 RPC URL is not defined.") + {:stop, :normal, state} + + {:batch_inbox_valid, false} -> + Logger.error("Batch Inbox address is invalid or not defined.") + {:stop, :normal, state} + + {:batch_submitter_valid, false} -> + Logger.error("Batch Submitter address is invalid or not defined.") + {:stop, :normal, state} + + {:start_block_l1_valid, false} -> + Logger.error("Invalid L1 Start Block value. Please, check the value and op_transaction_batches table.") + {:stop, :normal, state} + + {:chunk_size_valid, false} -> + Logger.error("Invalid blocks chunk size value.") + {:stop, :normal, state} + + {:error, error_data} -> + Logger.error("Cannot get block timestamp by its number due to RPC error: #{inspect(error_data)}") + + {:stop, :normal, state} + + {:l1_tx_not_found, true} -> + Logger.error( + "Cannot find last L1 transaction from RPC by its hash. Probably, there was a reorg on L1 chain. Please, check op_transaction_batches table." + ) + + {:stop, :normal, state} + + _ -> + Logger.error("Batch Start Block is invalid or zero.") + {:stop, :normal, state} + end + end + + @impl GenServer + def handle_info( + :continue, + %{ + batch_inbox: batch_inbox, + batch_submitter: batch_submitter, + block_check_interval: block_check_interval, + start_block: start_block, + end_block: end_block, + chunk_size: chunk_size, + incomplete_channels: incomplete_channels, + genesis_block_l2: genesis_block_l2, + json_rpc_named_arguments: json_rpc_named_arguments, + json_rpc_named_arguments_l2: json_rpc_named_arguments_l2 + } = state + ) do + time_before = Timex.now() + + chunks_number = ceil((end_block - start_block + 1) / chunk_size) + chunk_range = Range.new(0, max(chunks_number - 1, 0), 1) + + {last_written_block, new_incomplete_channels} = + chunk_range + |> Enum.reduce_while({start_block - 1, incomplete_channels}, fn current_chunk, {_, incomplete_channels_acc} -> + chunk_start = start_block + chunk_size * current_chunk + chunk_end = min(chunk_start + chunk_size - 1, end_block) + + new_incomplete_channels = + if chunk_end >= chunk_start do + Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, :L1) + + {:ok, new_incomplete_channels, batches, sequences} = + get_txn_batches( + Range.new(chunk_start, chunk_end), + batch_inbox, + batch_submitter, + genesis_block_l2, + incomplete_channels_acc, + json_rpc_named_arguments, + json_rpc_named_arguments_l2, + Helper.infinite_retries_number() + ) + + {batches, sequences} = remove_duplicates(batches, sequences) + + {:ok, _} = + Chain.import(%{ + optimism_frame_sequences: %{params: sequences}, + optimism_txn_batches: %{params: batches}, + timeout: :infinity + }) + + Helper.log_blocks_chunk_handling( + chunk_start, + chunk_end, + start_block, + end_block, + "#{Enum.count(sequences)} batch(es) containing #{Enum.count(batches)} block(s).", + :L1 + ) + + new_incomplete_channels + else + incomplete_channels_acc + end + + reorg_block = Optimism.reorg_block_pop(@fetcher_name) + + if !is_nil(reorg_block) && reorg_block > 0 do + new_incomplete_channels = handle_l1_reorg(reorg_block, new_incomplete_channels) + {:halt, {if(reorg_block <= chunk_end, do: reorg_block - 1, else: chunk_end), new_incomplete_channels}} + else + {:cont, {chunk_end, new_incomplete_channels}} + end + end) + + new_start_block = last_written_block + 1 + + {:ok, new_end_block} = + Optimism.get_block_number_by_tag("latest", json_rpc_named_arguments, Helper.infinite_retries_number()) + + delay = + if new_end_block == last_written_block do + # there is no new block, so wait for some time to let the chain issue the new block + max(block_check_interval - Timex.diff(Timex.now(), time_before, :milliseconds), 0) + else + 0 + end + + Process.send_after(self(), :continue, delay) + + {:noreply, + %{ + state + | start_block: new_start_block, + end_block: new_end_block, + incomplete_channels: new_incomplete_channels + }} + end + + @impl GenServer + def handle_info({:chain_event, :optimism_reorg_block, :realtime, block_number}, state) do + Optimism.reorg_block_push(@fetcher_name, block_number) + {:noreply, state} + end + + @impl GenServer + def handle_info({ref, _result}, state) do + Process.demonitor(ref, [:flush]) + {:noreply, state} + end + + defp get_block_numbers_by_hashes([], _json_rpc_named_arguments_l2) do + %{} + end + + defp get_block_numbers_by_hashes(hashes, json_rpc_named_arguments_l2) do + query = + from( + b in Block, + select: {b.hash, b.number}, + where: b.hash in ^hashes + ) + + number_by_hash = + query + |> Repo.all(timeout: :infinity) + |> Enum.reduce(%{}, fn {hash, number}, acc -> + Map.put(acc, hash.bytes, number) + end) + + requests = + hashes + |> Enum.filter(fn hash -> is_nil(Map.get(number_by_hash, hash)) end) + |> Enum.with_index() + |> Enum.map(fn {hash, id} -> + ByHash.request(%{hash: "0x" <> Base.encode16(hash, case: :lower), id: id}, false) + end) + + chunk_size = 50 + chunks_number = ceil(Enum.count(requests) / chunk_size) + chunk_range = Range.new(0, chunks_number - 1, 1) + + chunk_range + |> Enum.reduce([], fn current_chunk, acc -> + {:ok, resp} = + requests + |> Enum.slice(chunk_size * current_chunk, chunk_size) + |> json_rpc(json_rpc_named_arguments_l2) + + acc ++ resp + end) + |> Enum.map(fn %{result: result} -> result end) + |> Enum.reduce(number_by_hash, fn block, acc -> + if is_nil(block) do + acc + else + block_number = quantity_to_integer(Map.get(block, "number")) + "0x" <> hash = Map.get(block, "hash") + {:ok, hash} = Base.decode16(hash, case: :lower) + Map.put(acc, hash, block_number) + end + end) + end + + defp get_block_timestamp_by_number(block_number, blocks_params) do + block = Enum.find(blocks_params, %{timestamp: nil}, fn b -> b.number == block_number end) + block.timestamp + end + + defp get_last_l1_item(json_rpc_named_arguments) do + l1_transaction_hashes = + Repo.one( + from( + tb in OptimismTxnBatch, + inner_join: fs in FrameSequence, + on: fs.id == tb.frame_sequence_id, + select: fs.l1_transaction_hashes, + order_by: [desc: tb.l2_block_number], + limit: 1 + ) + ) + + last_l1_transaction_hash = + if is_nil(l1_transaction_hashes) do + nil + else + List.last(l1_transaction_hashes) + end + + if is_nil(last_l1_transaction_hash) do + {0, nil, nil} + else + {:ok, last_l1_tx} = Optimism.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments) + last_l1_block_number = quantity_to_integer(Map.get(last_l1_tx || %{}, "blockNumber", 0)) + {last_l1_block_number, last_l1_transaction_hash, last_l1_tx} + end + end + + defp get_txn_batches( + block_range, + batch_inbox, + batch_submitter, + genesis_block_l2, + incomplete_channels, + json_rpc_named_arguments, + json_rpc_named_arguments_l2, + retries_left + ) do + case fetch_blocks_by_range(block_range, json_rpc_named_arguments) do + {:ok, %Blocks{transactions_params: transactions_params, blocks_params: blocks_params, errors: []}} -> + transactions_params + |> txs_filter(batch_submitter, batch_inbox) + |> get_txn_batches_inner( + blocks_params, + genesis_block_l2, + incomplete_channels, + json_rpc_named_arguments_l2 + ) + + {_, message_or_errors} -> + message = + case message_or_errors do + %Blocks{errors: errors} -> errors + msg -> msg + end + + retries_left = retries_left - 1 + + error_message = "Cannot fetch blocks #{inspect(block_range)}. Error(s): #{inspect(message)}" + + if retries_left <= 0 do + Logger.error(error_message) + {:error, message} + else + Logger.error("#{error_message} Retrying...") + :timer.sleep(3000) + + get_txn_batches( + block_range, + batch_inbox, + batch_submitter, + genesis_block_l2, + incomplete_channels, + json_rpc_named_arguments, + json_rpc_named_arguments_l2, + retries_left + ) + end + end + end + + defp get_txn_batches_inner( + transactions_filtered, + blocks_params, + genesis_block_l2, + incomplete_channels, + json_rpc_named_arguments_l2 + ) do + transactions_filtered + |> Enum.reduce({:ok, incomplete_channels, [], []}, fn t, {_, incomplete_channels_acc, batches_acc, sequences_acc} -> + frame = input_to_frame(t.input) + + channel = Map.get(incomplete_channels_acc, frame.channel_id, %{frames: %{}}) + + channel_frames = + Map.put(channel.frames, frame.number, %{ + data: frame.data, + is_last: frame.is_last, + block_number: t.block_number, + tx_hash: t.hash + }) + + l1_timestamp = + if frame.is_last do + get_block_timestamp_by_number(t.block_number, blocks_params) + else + Map.get(channel, :l1_timestamp) + end + + channel = + channel + |> Map.put_new(:id, frame.channel_id) + |> Map.put(:frames, channel_frames) + |> Map.put(:timestamp, DateTime.utc_now()) + |> Map.put(:l1_timestamp, l1_timestamp) + + if channel_complete?(channel) do + handle_channel( + channel, + incomplete_channels_acc, + batches_acc, + sequences_acc, + genesis_block_l2, + json_rpc_named_arguments_l2 + ) + else + {:ok, Map.put(incomplete_channels_acc, frame.channel_id, channel), batches_acc, sequences_acc} + end + end) + end + + defp handle_channel( + channel, + incomplete_channels_acc, + batches_acc, + sequences_acc, + genesis_block_l2, + json_rpc_named_arguments_l2 + ) do + frame_sequence_last = List.first(sequences_acc) + frame_sequence_id = next_frame_sequence_id(frame_sequence_last) + + {bytes, l1_transaction_hashes} = + 0..(Enum.count(channel.frames) - 1) + |> Enum.reduce({<<>>, []}, fn frame_number, {bytes_acc, tx_hashes_acc} -> + frame = Map.get(channel.frames, frame_number) + {bytes_acc <> frame.data, [frame.tx_hash | tx_hashes_acc]} + end) + + batches_parsed = + parse_frame_sequence( + bytes, + frame_sequence_id, + channel.l1_timestamp, + genesis_block_l2, + json_rpc_named_arguments_l2 + ) + + if batches_parsed == :error do + Logger.error("Cannot parse frame sequence from these L1 transaction(s): #{inspect(l1_transaction_hashes)}") + end + + seq = %{ + id: frame_sequence_id, + l1_transaction_hashes: Enum.reverse(l1_transaction_hashes), + l1_timestamp: channel.l1_timestamp + } + + new_incomplete_channels_acc = + incomplete_channels_acc + |> Map.delete(channel.id) + |> remove_expired_channels() + + if batches_parsed == :error or Enum.empty?(batches_parsed) do + {:ok, new_incomplete_channels_acc, batches_acc, sequences_acc} + else + {:ok, new_incomplete_channels_acc, batches_acc ++ batches_parsed, [seq | sequences_acc]} + end + end + + defp handle_l1_reorg(reorg_block, incomplete_channels) do + incomplete_channels + |> Enum.reduce(incomplete_channels, fn {channel_id, %{frames: frames} = channel}, acc -> + updated_frames = + frames + |> Enum.filter(fn {_frame_number, %{block_number: block_number}} -> + block_number < reorg_block + end) + |> Enum.into(%{}) + + if Enum.empty?(updated_frames) do + Map.delete(acc, channel_id) + else + Map.put(acc, channel_id, Map.put(channel, :frames, updated_frames)) + end + end) + end + + @doc """ + Removes rows from op_transaction_batches and op_frame_sequences tables written beginning from the L2 reorg block. + """ + @spec handle_l2_reorg(non_neg_integer()) :: any() + def handle_l2_reorg(reorg_block) do + frame_sequence_ids = + Repo.all( + from( + tb in OptimismTxnBatch, + select: tb.frame_sequence_id, + where: tb.l2_block_number >= ^reorg_block + ), + timeout: :infinity + ) + + {deleted_count, _} = Repo.delete_all(from(tb in OptimismTxnBatch, where: tb.l2_block_number >= ^reorg_block)) + + Repo.delete_all(from(fs in FrameSequence, where: fs.id in ^frame_sequence_ids)) + + if deleted_count > 0 do + Logger.warning( + "As L2 reorg was detected, all rows with l2_block_number >= #{reorg_block} were removed from the op_transaction_batches table. Number of removed rows: #{deleted_count}." + ) + end + end + + defp channel_complete?(channel) do + last_frame_number = + channel.frames + |> Map.keys() + |> Enum.max() + + Map.get(channel.frames, last_frame_number).is_last and last_frame_number == Enum.count(channel.frames) - 1 + end + + defp remove_expired_channels(channels_map) do + now = DateTime.utc_now() + + Enum.reduce(channels_map, channels_map, fn {channel_id, %{timestamp: timestamp}}, channels_acc -> + if DateTime.diff(now, timestamp) >= 86400 do + Map.delete(channels_acc, channel_id) + else + channels_acc + end + end) + end + + defp input_to_frame("0x" <> input) do + input_binary = Base.decode16!(input, case: :mixed) + + # the structure of the input is as follows: + # + # input = derivation_version ++ channel_id ++ frame_number ++ frame_data_length ++ frame_data ++ is_last + # + # derivation_version = uint8 + # channel_id = bytes16 + # frame_number = uint16 + # frame_data_length = uint32 + # frame_data = bytes + # is_last = bool (uint8) + + derivation_version_length = 1 + channel_id_length = 16 + frame_number_size = 2 + frame_data_length_size = 4 + is_last_size = 1 + + # the first byte must be zero (so called Derivation Version) + [0] = :binary.bin_to_list(binary_part(input_binary, 0, derivation_version_length)) + + # channel id has 16 bytes + channel_id = binary_part(input_binary, derivation_version_length, channel_id_length) + + # frame number consists of 2 bytes + frame_number_offset = derivation_version_length + channel_id_length + frame_number = :binary.decode_unsigned(binary_part(input_binary, frame_number_offset, frame_number_size)) + + # frame data length consists of 4 bytes + frame_data_length_offset = frame_number_offset + frame_number_size + + frame_data_length = + :binary.decode_unsigned(binary_part(input_binary, frame_data_length_offset, frame_data_length_size)) + + input_length_must_be = + derivation_version_length + channel_id_length + frame_number_size + frame_data_length_size + frame_data_length + + is_last_size + + input_length_current = byte_size(input_binary) + + if input_length_current == input_length_must_be do + # frame data is a byte array of frame_data_length size + frame_data_offset = frame_data_length_offset + frame_data_length_size + frame_data = binary_part(input_binary, frame_data_offset, frame_data_length) + + # is_last is 1-byte item + is_last_offset = frame_data_offset + frame_data_length + is_last = :binary.decode_unsigned(binary_part(input_binary, is_last_offset, is_last_size)) > 0 + + %{number: frame_number, data: frame_data, is_last: is_last, channel_id: channel_id} + else + # workaround to remove a leading extra byte + # for example, the case for Base Goerli batch L1 transaction: https://goerli.etherscan.io/tx/0xa43fa9da683a6157a114e3175a625b5aed85d8c573aae226768c58a924a17be0 + input_to_frame("0x" <> Base.encode16(binary_part(input_binary, 1, input_length_current - 1))) + end + end + + defp next_frame_sequence_id(last_known_sequence) when is_nil(last_known_sequence) do + last_known_id = + Repo.one( + from( + fs in FrameSequence, + select: fs.id, + order_by: [desc: fs.id], + limit: 1 + ) + ) + + if is_nil(last_known_id) do + 1 + else + last_known_id + 1 + end + end + + defp next_frame_sequence_id(last_known_sequence) do + last_known_sequence.id + 1 + end + + defp parse_frame_sequence( + bytes, + id, + l1_timestamp, + genesis_block_l2, + json_rpc_named_arguments_l2 + ) do + uncompressed_bytes = zlib_decompress(bytes) + + batches = + Enum.reduce_while(Stream.iterate(0, &(&1 + 1)), {uncompressed_bytes, []}, fn _i, {remainder, batch_acc} -> + try do + {decoded, new_remainder} = ExRLP.decode(remainder, stream: true) + + <> = binary_part(decoded, 0, 1) + content = binary_part(decoded, 1, byte_size(decoded) - 1) + + new_batch_acc = + cond do + version == 0 -> + handle_v0_batch(content, id, l1_timestamp, batch_acc) + + version <= 2 -> + # parsing the span batch + handle_v1_batch(content, id, l1_timestamp, genesis_block_l2, batch_acc) + + true -> + Logger.error("Unsupported batch version ##{version}") + :error + end + + if byte_size(new_remainder) > 0 and new_batch_acc != :error do + {:cont, {new_remainder, new_batch_acc}} + else + {:halt, new_batch_acc} + end + rescue + _ -> {:halt, :error} + end + end) + + if batches == :error do + :error + else + batches = Enum.reverse(batches) + + numbers_by_hashes = + batches + |> Stream.filter(&Map.has_key?(&1, :parent_hash)) + |> Enum.map(fn batch -> batch.parent_hash end) + |> get_block_numbers_by_hashes(json_rpc_named_arguments_l2) + + Enum.map(batches, &parent_hash_to_l2_block_number(&1, numbers_by_hashes)) + end + end + + defp handle_v0_batch(content, frame_sequence_id, l1_timestamp, batch_acc) do + content_decoded = ExRLP.decode(content) + + batch = %{ + parent_hash: Enum.at(content_decoded, 0), + frame_sequence_id: frame_sequence_id, + l1_timestamp: l1_timestamp + } + + [batch | batch_acc] + end + + defp handle_v1_batch(content, frame_sequence_id, l1_timestamp, genesis_block_l2, batch_acc) do + {rel_timestamp, content_remainder} = LEB128.decode(content) + + # skip l1_origin_num + {_l1_origin_num, checks_and_payload} = LEB128.decode(content_remainder) + + # skip `parent_check` and `l1_origin_check` fields (20 bytes each) + # and read the block count + {block_count, _} = + checks_and_payload + |> binary_part(40, byte_size(checks_and_payload) - 40) + |> LEB128.decode() + + # the first and last L2 blocks in the span + span_start = div(rel_timestamp, @op_chain_block_time) + genesis_block_l2 + span_end = span_start + block_count - 1 + + cond do + rem(rel_timestamp, @op_chain_block_time) != 0 -> + Logger.error("rel_timestamp is not divisible by #{@op_chain_block_time}. We ignore the span batch.") + batch_acc + + block_count <= 0 -> + Logger.error("Empty span batch found. We ignore it.") + batch_acc + + true -> + span_start..span_end + |> Enum.reduce(batch_acc, fn l2_block_number, batch_acc -> + [ + %{ + l2_block_number: l2_block_number, + frame_sequence_id: frame_sequence_id, + l1_timestamp: l1_timestamp + } + | batch_acc + ] + end) + end + end + + defp parent_hash_to_l2_block_number(batch, numbers_by_hashes) do + if Map.has_key?(batch, :parent_hash) do + number = Map.get(numbers_by_hashes, batch.parent_hash) + + batch + |> Map.put(:l2_block_number, number + 1) + |> Map.delete(:parent_hash) + else + batch + end + end + + defp remove_duplicates(batches, sequences) do + unique_batches = + batches + |> Enum.sort(fn b1, b2 -> + b1.l2_block_number < b2.l2_block_number or + (b1.l2_block_number == b2.l2_block_number and b1.l1_timestamp < b2.l1_timestamp) + end) + |> Enum.reduce(%{}, fn b, acc -> + Map.put(acc, b.l2_block_number, Map.delete(b, :l1_timestamp)) + end) + |> Map.values() + + unique_sequences = + if Enum.empty?(sequences) do + [] + else + sequences + |> Enum.reverse() + |> Enum.filter(fn seq -> + Enum.any?(unique_batches, fn batch -> batch.frame_sequence_id == seq.id end) + end) + end + + {unique_batches, unique_sequences} + end + + defp txs_filter(transactions_params, batch_submitter, batch_inbox) do + transactions_params + |> Enum.filter(fn t -> + from_address_hash = Map.get(t, :from_address_hash) + to_address_hash = Map.get(t, :to_address_hash) + + if is_nil(from_address_hash) or is_nil(to_address_hash) do + false + else + String.downcase(from_address_hash) == batch_submitter and String.downcase(to_address_hash) == batch_inbox + end + end) + end + + defp zlib_decompress(bytes) do + z = :zlib.open() + :zlib.inflateInit(z) + + uncompressed_bytes = + try do + zlib_inflate(z, bytes) + rescue + _ -> <<>> + end + + try do + :zlib.inflateEnd(z) + rescue + _ -> nil + end + + :zlib.close(z) + + uncompressed_bytes + end + + defp zlib_inflate_handler(z, {:continue, [uncompressed_bytes]}, acc) do + zlib_inflate(z, [], acc <> uncompressed_bytes) + end + + defp zlib_inflate_handler(_z, {:finished, [uncompressed_bytes]}, acc) do + acc <> uncompressed_bytes + end + + defp zlib_inflate_handler(_z, {:finished, []}, acc) do + acc + end + + defp zlib_inflate(z, compressed_bytes, acc \\ <<>>) do + result = :zlib.safeInflate(z, compressed_bytes) + zlib_inflate_handler(z, result, acc) + end +end diff --git a/apps/indexer/lib/indexer/fetcher/optimism/withdrawal.ex b/apps/indexer/lib/indexer/fetcher/optimism/withdrawal.ex new file mode 100644 index 000000000000..aac9684bce25 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/optimism/withdrawal.ex @@ -0,0 +1,361 @@ +defmodule Indexer.Fetcher.Optimism.Withdrawal do + @moduledoc """ + Fills op_withdrawals DB table. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + import Ecto.Query + + import EthereumJSONRPC, only: [quantity_to_integer: 1] + import Explorer.Helper, only: [decode_data: 2, parse_integer: 1] + + alias Explorer.{Chain, Repo} + alias Explorer.Chain.Log + alias Explorer.Chain.Optimism.Withdrawal, as: OptimismWithdrawal + alias Indexer.Fetcher.Optimism + alias Indexer.Helper + + @fetcher_name :optimism_withdrawals + + # 32-byte signature of the event MessagePassed(uint256 indexed nonce, address indexed sender, address indexed target, uint256 value, uint256 gasLimit, bytes data, bytes32 withdrawalHash) + @message_passed_event "0x02a52367d10742d8032712c1bb8e0144ff1ec5ffda1ed7d70bb05a2744955054" + + def child_spec(start_link_arguments) do + spec = %{ + id: __MODULE__, + start: {__MODULE__, :start_link, start_link_arguments}, + restart: :transient, + type: :worker + } + + Supervisor.child_spec(spec, []) + end + + def start_link(args, gen_server_options \\ []) do + GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__)) + end + + @impl GenServer + def init(args) do + json_rpc_named_arguments = args[:json_rpc_named_arguments] + {:ok, %{}, {:continue, json_rpc_named_arguments}} + end + + @impl GenServer + def handle_continue(json_rpc_named_arguments, state) do + Logger.metadata(fetcher: @fetcher_name) + + env = Application.get_all_env(:indexer)[__MODULE__] + + with {:start_block_l2_undefined, false} <- {:start_block_l2_undefined, is_nil(env[:start_block_l2])}, + {:message_passer_valid, true} <- {:message_passer_valid, Helper.address_correct?(env[:message_passer])}, + start_block_l2 = parse_integer(env[:start_block_l2]), + false <- is_nil(start_block_l2), + true <- start_block_l2 > 0, + {last_l2_block_number, last_l2_transaction_hash} <- get_last_l2_item(), + {safe_block, safe_block_is_latest} = Optimism.get_safe_block(json_rpc_named_arguments), + {:start_block_l2_valid, true} <- + {:start_block_l2_valid, + (start_block_l2 <= last_l2_block_number || last_l2_block_number == 0) && start_block_l2 <= safe_block}, + {:ok, last_l2_tx} <- Optimism.get_transaction_by_hash(last_l2_transaction_hash, json_rpc_named_arguments), + {:l2_tx_not_found, false} <- {:l2_tx_not_found, !is_nil(last_l2_transaction_hash) && is_nil(last_l2_tx)} do + Process.send(self(), :continue, []) + + {:noreply, + %{ + start_block: max(start_block_l2, last_l2_block_number), + start_block_l2: start_block_l2, + safe_block: safe_block, + safe_block_is_latest: safe_block_is_latest, + message_passer: env[:message_passer], + json_rpc_named_arguments: json_rpc_named_arguments + }} + else + {:start_block_l2_undefined, true} -> + # the process shouldn't start if the start block is not defined + {:stop, :normal, state} + + {:message_passer_valid, false} -> + Logger.error("L2ToL1MessagePasser contract address is invalid or not defined.") + {:stop, :normal, state} + + {:start_block_l2_valid, false} -> + Logger.error("Invalid L2 Start Block value. Please, check the value and op_withdrawals table.") + {:stop, :normal, state} + + {:error, error_data} -> + Logger.error("Cannot get last L2 transaction from RPC by its hash due to RPC error: #{inspect(error_data)}") + + {:stop, :normal, state} + + {:l2_tx_not_found, true} -> + Logger.error( + "Cannot find last L2 transaction from RPC by its hash. Probably, there was a reorg on L2 chain. Please, check op_withdrawals table." + ) + + {:stop, :normal, state} + + _ -> + Logger.error("Withdrawals L2 Start Block is invalid or zero.") + {:stop, :normal, state} + end + end + + @impl GenServer + def handle_info( + :continue, + %{ + start_block_l2: start_block_l2, + message_passer: message_passer, + json_rpc_named_arguments: json_rpc_named_arguments + } = state + ) do + fill_msg_nonce_gaps(start_block_l2, message_passer, json_rpc_named_arguments) + Process.send(self(), :find_new_events, []) + {:noreply, state} + end + + @impl GenServer + def handle_info( + :find_new_events, + %{ + start_block: start_block, + safe_block: safe_block, + safe_block_is_latest: safe_block_is_latest, + message_passer: message_passer, + json_rpc_named_arguments: json_rpc_named_arguments + } = state + ) do + # find and fill all events between start_block and "safe" block + # the "safe" block can be "latest" (when safe_block_is_latest == true) + fill_block_range(start_block, safe_block, message_passer, json_rpc_named_arguments) + + if not safe_block_is_latest do + # find and fill all events between "safe" and "latest" block (excluding "safe") + {:ok, latest_block} = Optimism.get_block_number_by_tag("latest", json_rpc_named_arguments) + fill_block_range(safe_block + 1, latest_block, message_passer, json_rpc_named_arguments) + end + + {:stop, :normal, state} + end + + @impl GenServer + def handle_info({ref, _result}, state) do + Process.demonitor(ref, [:flush]) + {:noreply, state} + end + + def remove(starting_block) do + Repo.delete_all(from(w in OptimismWithdrawal, where: w.l2_block_number >= ^starting_block)) + end + + def event_to_withdrawal(second_topic, data, l2_transaction_hash, l2_block_number) do + [_value, _gas_limit, _data, hash] = decode_data(data, [{:uint, 256}, {:uint, 256}, :bytes, {:bytes, 32}]) + + msg_nonce = + second_topic + |> Helper.log_topic_to_string() + |> quantity_to_integer() + |> Decimal.new() + + %{ + msg_nonce: msg_nonce, + hash: hash, + l2_transaction_hash: l2_transaction_hash, + l2_block_number: quantity_to_integer(l2_block_number) + } + end + + defp msg_nonce_gap_starts(nonce_max) do + Repo.all( + from(w in OptimismWithdrawal, + select: w.l2_block_number, + order_by: w.msg_nonce, + where: + fragment( + "NOT EXISTS (SELECT msg_nonce FROM op_withdrawals WHERE msg_nonce = (? + 1)) AND msg_nonce != ?", + w.msg_nonce, + ^nonce_max + ) + ) + ) + end + + defp msg_nonce_gap_ends(nonce_min) do + Repo.all( + from(w in OptimismWithdrawal, + select: w.l2_block_number, + order_by: w.msg_nonce, + where: + fragment( + "NOT EXISTS (SELECT msg_nonce FROM op_withdrawals WHERE msg_nonce = (? - 1)) AND msg_nonce != ?", + w.msg_nonce, + ^nonce_min + ) + ) + ) + end + + defp find_and_save_withdrawals( + scan_db, + message_passer, + block_start, + block_end, + json_rpc_named_arguments + ) do + withdrawals = + if scan_db do + query = + from(log in Log, + select: {log.second_topic, log.data, log.transaction_hash, log.block_number}, + where: + log.first_topic == ^@message_passed_event and log.address_hash == ^message_passer and + log.block_number >= ^block_start and log.block_number <= ^block_end + ) + + query + |> Repo.all(timeout: :infinity) + |> Enum.map(fn {second_topic, data, l2_transaction_hash, l2_block_number} -> + event_to_withdrawal(second_topic, data, l2_transaction_hash, l2_block_number) + end) + else + {:ok, result} = + Optimism.get_logs( + block_start, + block_end, + message_passer, + @message_passed_event, + json_rpc_named_arguments, + 3 + ) + + Enum.map(result, fn event -> + event_to_withdrawal( + Enum.at(event["topics"], 1), + event["data"], + event["transactionHash"], + event["blockNumber"] + ) + end) + end + + {:ok, _} = + Chain.import(%{ + optimism_withdrawals: %{params: withdrawals}, + timeout: :infinity + }) + + Enum.count(withdrawals) + end + + defp fill_block_range(l2_block_start, l2_block_end, message_passer, json_rpc_named_arguments, scan_db) do + chunks_number = + if scan_db do + 1 + else + ceil((l2_block_end - l2_block_start + 1) / Optimism.get_logs_range_size()) + end + + chunk_range = Range.new(0, max(chunks_number - 1, 0), 1) + + Enum.reduce(chunk_range, 0, fn current_chunk, withdrawals_count_acc -> + chunk_start = l2_block_start + Optimism.get_logs_range_size() * current_chunk + + chunk_end = + if scan_db do + l2_block_end + else + min(chunk_start + Optimism.get_logs_range_size() - 1, l2_block_end) + end + + Helper.log_blocks_chunk_handling(chunk_start, chunk_end, l2_block_start, l2_block_end, nil, :L2) + + withdrawals_count = + find_and_save_withdrawals( + scan_db, + message_passer, + chunk_start, + chunk_end, + json_rpc_named_arguments + ) + + Helper.log_blocks_chunk_handling( + chunk_start, + chunk_end, + l2_block_start, + l2_block_end, + "#{withdrawals_count} MessagePassed event(s)", + :L2 + ) + + withdrawals_count_acc + withdrawals_count + end) + end + + defp fill_block_range(start_block, end_block, message_passer, json_rpc_named_arguments) do + fill_block_range(start_block, end_block, message_passer, json_rpc_named_arguments, true) + fill_msg_nonce_gaps(start_block, message_passer, json_rpc_named_arguments, false) + {last_l2_block_number, _} = get_last_l2_item() + fill_block_range(max(start_block, last_l2_block_number), end_block, message_passer, json_rpc_named_arguments, false) + end + + defp fill_msg_nonce_gaps(start_block_l2, message_passer, json_rpc_named_arguments, scan_db \\ true) do + nonce_min = Repo.aggregate(OptimismWithdrawal, :min, :msg_nonce) + nonce_max = Repo.aggregate(OptimismWithdrawal, :max, :msg_nonce) + + with true <- !is_nil(nonce_min) and !is_nil(nonce_max), + starts = msg_nonce_gap_starts(nonce_max), + ends = msg_nonce_gap_ends(nonce_min), + min_block_l2 = l2_block_number_by_msg_nonce(nonce_min), + {new_starts, new_ends} = + if(start_block_l2 < min_block_l2, + do: {[start_block_l2 | starts], [min_block_l2 | ends]}, + else: {starts, ends} + ), + true <- Enum.count(new_starts) == Enum.count(new_ends) do + new_starts + |> Enum.zip(new_ends) + |> Enum.each(fn {l2_block_start, l2_block_end} -> + withdrawals_count = + fill_block_range(l2_block_start, l2_block_end, message_passer, json_rpc_named_arguments, scan_db) + + if withdrawals_count > 0 do + log_fill_msg_nonce_gaps(scan_db, l2_block_start, l2_block_end, withdrawals_count) + end + end) + + if scan_db do + fill_msg_nonce_gaps(start_block_l2, message_passer, json_rpc_named_arguments, false) + end + end + end + + defp get_last_l2_item do + query = + from(w in OptimismWithdrawal, + select: {w.l2_block_number, w.l2_transaction_hash}, + order_by: [desc: w.msg_nonce], + limit: 1 + ) + + query + |> Repo.one() + |> Kernel.||({0, nil}) + end + + defp log_fill_msg_nonce_gaps(scan_db, l2_block_start, l2_block_end, withdrawals_count) do + find_place = if scan_db, do: "in DB", else: "through RPC" + + Logger.info( + "Filled gaps between L2 blocks #{l2_block_start} and #{l2_block_end}. #{withdrawals_count} event(s) were found #{find_place} and written to op_withdrawals table." + ) + end + + defp l2_block_number_by_msg_nonce(nonce) do + Repo.one(from(w in OptimismWithdrawal, select: w.l2_block_number, where: w.msg_nonce == ^nonce)) + end +end diff --git a/apps/indexer/lib/indexer/fetcher/optimism/withdrawal_event.ex b/apps/indexer/lib/indexer/fetcher/optimism/withdrawal_event.ex new file mode 100644 index 000000000000..8854f868ba23 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/optimism/withdrawal_event.ex @@ -0,0 +1,226 @@ +defmodule Indexer.Fetcher.Optimism.WithdrawalEvent do + @moduledoc """ + Fills op_withdrawal_events DB table. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + import Ecto.Query + + import EthereumJSONRPC, only: [quantity_to_integer: 1] + + alias EthereumJSONRPC.Block.ByNumber + alias EthereumJSONRPC.Blocks + alias Explorer.{Chain, Repo} + alias Explorer.Chain.Optimism.WithdrawalEvent + alias Indexer.Fetcher.Optimism + alias Indexer.Helper + + @fetcher_name :optimism_withdrawal_events + + # 32-byte signature of the event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to) + @withdrawal_proven_event "0x67a6208cfcc0801d50f6cbe764733f4fddf66ac0b04442061a8a8c0cb6b63f62" + + # 32-byte signature of the event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success) + @withdrawal_finalized_event "0xdb5c7652857aa163daadd670e116628fb42e869d8ac4251ef8971d9e5727df1b" + + def child_spec(start_link_arguments) do + spec = %{ + id: __MODULE__, + start: {__MODULE__, :start_link, start_link_arguments}, + restart: :transient, + type: :worker + } + + Supervisor.child_spec(spec, []) + end + + def start_link(args, gen_server_options \\ []) do + GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__)) + end + + @impl GenServer + def init(_args) do + {:ok, %{}, {:continue, :ok}} + end + + @impl GenServer + def handle_continue(:ok, _state) do + Logger.metadata(fetcher: @fetcher_name) + + env = Application.get_all_env(:indexer)[__MODULE__] + optimism_l1_portal = Application.get_all_env(:indexer)[Indexer.Fetcher.Optimism][:optimism_l1_portal] + + Optimism.init_continue(env, optimism_l1_portal, __MODULE__) + end + + @impl GenServer + def handle_info( + :continue, + %{ + contract_address: optimism_portal, + block_check_interval: block_check_interval, + start_block: start_block, + end_block: end_block, + json_rpc_named_arguments: json_rpc_named_arguments + } = state + ) do + # credo:disable-for-next-line + time_before = Timex.now() + + chunks_number = ceil((end_block - start_block + 1) / Optimism.get_logs_range_size()) + chunk_range = Range.new(0, max(chunks_number - 1, 0), 1) + + last_written_block = + chunk_range + |> Enum.reduce_while(start_block - 1, fn current_chunk, _ -> + chunk_start = start_block + Optimism.get_logs_range_size() * current_chunk + chunk_end = min(chunk_start + Optimism.get_logs_range_size() - 1, end_block) + + if chunk_end >= chunk_start do + Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, :L1) + + {:ok, result} = + Optimism.get_logs( + chunk_start, + chunk_end, + optimism_portal, + [@withdrawal_proven_event, @withdrawal_finalized_event], + json_rpc_named_arguments, + Helper.infinite_retries_number() + ) + + withdrawal_events = prepare_events(result, json_rpc_named_arguments) + + {:ok, _} = + Chain.import(%{ + optimism_withdrawal_events: %{params: withdrawal_events}, + timeout: :infinity + }) + + Helper.log_blocks_chunk_handling( + chunk_start, + chunk_end, + start_block, + end_block, + "#{Enum.count(withdrawal_events)} WithdrawalProven/WithdrawalFinalized event(s)", + :L1 + ) + end + + reorg_block = Optimism.reorg_block_pop(@fetcher_name) + + if !is_nil(reorg_block) && reorg_block > 0 do + {deleted_count, _} = Repo.delete_all(from(we in WithdrawalEvent, where: we.l1_block_number >= ^reorg_block)) + + log_deleted_rows_count(reorg_block, deleted_count) + + {:halt, if(reorg_block <= chunk_end, do: reorg_block - 1, else: chunk_end)} + else + {:cont, chunk_end} + end + end) + + new_start_block = last_written_block + 1 + + {:ok, new_end_block} = + Optimism.get_block_number_by_tag("latest", json_rpc_named_arguments, Helper.infinite_retries_number()) + + delay = + if new_end_block == last_written_block do + # there is no new block, so wait for some time to let the chain issue the new block + max(block_check_interval - Timex.diff(Timex.now(), time_before, :milliseconds), 0) + else + 0 + end + + Process.send_after(self(), :continue, delay) + + {:noreply, %{state | start_block: new_start_block, end_block: new_end_block}} + end + + @impl GenServer + def handle_info({:chain_event, :optimism_reorg_block, :realtime, block_number}, state) do + Optimism.reorg_block_push(@fetcher_name, block_number) + {:noreply, state} + end + + @impl GenServer + def handle_info({ref, _result}, state) do + Process.demonitor(ref, [:flush]) + {:noreply, state} + end + + defp log_deleted_rows_count(reorg_block, count) do + if count > 0 do + Logger.warning( + "As L1 reorg was detected, all rows with l1_block_number >= #{reorg_block} were removed from the op_withdrawal_events table. Number of removed rows: #{count}." + ) + end + end + + defp prepare_events(events, json_rpc_named_arguments) do + timestamps = + events + |> get_blocks_by_events(json_rpc_named_arguments, Helper.infinite_retries_number()) + |> Enum.reduce(%{}, fn block, acc -> + block_number = quantity_to_integer(Map.get(block, "number")) + {:ok, timestamp} = DateTime.from_unix(quantity_to_integer(Map.get(block, "timestamp"))) + Map.put(acc, block_number, timestamp) + end) + + Enum.map(events, fn event -> + l1_event_type = + if Enum.at(event["topics"], 0) == @withdrawal_proven_event do + "WithdrawalProven" + else + "WithdrawalFinalized" + end + + l1_block_number = quantity_to_integer(event["blockNumber"]) + + %{ + withdrawal_hash: Enum.at(event["topics"], 1), + l1_event_type: l1_event_type, + l1_timestamp: Map.get(timestamps, l1_block_number), + l1_transaction_hash: event["transactionHash"], + l1_block_number: l1_block_number + } + end) + end + + def get_last_l1_item do + query = + from(we in WithdrawalEvent, + select: {we.l1_block_number, we.l1_transaction_hash}, + order_by: [desc: we.l1_timestamp], + limit: 1 + ) + + query + |> Repo.one() + |> Kernel.||({0, nil}) + end + + defp get_blocks_by_events(events, json_rpc_named_arguments, retries) do + request = + events + |> Enum.reduce(%{}, fn event, acc -> + Map.put(acc, event["blockNumber"], 0) + end) + |> Stream.map(fn {block_number, _} -> %{number: block_number} end) + |> Stream.with_index() + |> Enum.into(%{}, fn {params, id} -> {id, params} end) + |> Blocks.requests(&ByNumber.request(&1, false, false)) + + error_message = &"Cannot fetch blocks with batch request. Error: #{inspect(&1)}. Request: #{inspect(request)}" + + case Optimism.repeated_request(request, error_message, json_rpc_named_arguments, retries) do + {:ok, results} -> Enum.map(results, fn %{result: result} -> result end) + {:error, _} -> [] + end + end +end diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge.ex index ee1fe71ddb62..9332e5b8f941 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge.ex @@ -67,7 +67,11 @@ defmodule Indexer.Fetcher.PolygonEdge do {:start_block_l1_valid, start_block_l1 <= last_l1_block_number || last_l1_block_number == 0}, json_rpc_named_arguments = json_rpc_named_arguments(polygon_edge_l1_rpc), {:ok, last_l1_tx} <- - Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments, 100_000_000), + Helper.get_transaction_by_hash( + last_l1_transaction_hash, + json_rpc_named_arguments, + Helper.infinite_retries_number() + ), {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)}, {:ok, block_check_interval, last_safe_block} <- Helper.get_block_check_interval(json_rpc_named_arguments) do @@ -144,7 +148,11 @@ defmodule Indexer.Fetcher.PolygonEdge do {:start_block_l2_valid, (start_block_l2 <= last_l2_block_number || last_l2_block_number == 0) && start_block_l2 <= safe_block}, {:ok, last_l2_tx} <- - Helper.get_transaction_by_hash(last_l2_transaction_hash, json_rpc_named_arguments, 100_000_000), + Helper.get_transaction_by_hash( + last_l2_transaction_hash, + json_rpc_named_arguments, + Helper.infinite_retries_number() + ), {:l2_tx_not_found, false} <- {:l2_tx_not_found, !is_nil(last_l2_transaction_hash) && is_nil(last_l2_tx)} do Process.send(pid, :continue, []) @@ -217,7 +225,7 @@ defmodule Indexer.Fetcher.PolygonEdge do chunk_end = min(chunk_start + eth_get_logs_range_size - 1, end_block) if chunk_end >= chunk_start do - Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, "L1") + Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, :L1) {:ok, result} = get_logs( @@ -226,7 +234,7 @@ defmodule Indexer.Fetcher.PolygonEdge do contract_address, event_signature, json_rpc_named_arguments, - 100_000_000 + Helper.infinite_retries_number() ) {events, event_name} = @@ -240,7 +248,7 @@ defmodule Indexer.Fetcher.PolygonEdge do start_block, end_block, "#{Enum.count(events)} #{event_name} event(s)", - "L1" + :L1 ) end @@ -248,7 +256,9 @@ defmodule Indexer.Fetcher.PolygonEdge do end) new_start_block = last_written_block + 1 - {:ok, new_end_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + + {:ok, new_end_block} = + Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, Helper.infinite_retries_number()) delay = if new_end_block == last_written_block do @@ -298,7 +308,7 @@ defmodule Indexer.Fetcher.PolygonEdge do min(chunk_start + eth_get_logs_range_size - 1, l2_block_end) end - Helper.log_blocks_chunk_handling(chunk_start, chunk_end, l2_block_start, l2_block_end, nil, "L2") + Helper.log_blocks_chunk_handling(chunk_start, chunk_end, l2_block_start, l2_block_end, nil, :L2) count = calling_module.find_and_save_entities( @@ -322,7 +332,7 @@ defmodule Indexer.Fetcher.PolygonEdge do l2_block_start, l2_block_end, "#{count} #{event_name} event(s)", - "L2" + :L2 ) count_acc + count @@ -488,7 +498,9 @@ defmodule Indexer.Fetcher.PolygonEdge do {safe_block, false} {:error, :not_found} -> - {:ok, latest_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + {:ok, latest_block} = + Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, Helper.infinite_retries_number()) + {latest_block, true} end end @@ -573,7 +585,7 @@ defmodule Indexer.Fetcher.PolygonEdge do defp import_events(events, calling_module) do # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise {import_data, event_name} = - case System.get_env("CHAIN_TYPE") == "polygon_edge" && calling_module do + case Application.get_env(:explorer, :chain_type) == "polygon_edge" && calling_module do Deposit -> {%{polygon_edge_deposits: %{params: events}, timeout: :infinity}, "StateSynced"} diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit.ex index 642e1951cacc..556acfd892a6 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit.ex @@ -18,6 +18,7 @@ defmodule Indexer.Fetcher.PolygonEdge.Deposit do alias EthereumJSONRPC.Blocks alias Explorer.Chain.PolygonEdge.Deposit alias Indexer.Fetcher.PolygonEdge + alias Indexer.Helper @fetcher_name :polygon_edge_deposit @@ -123,7 +124,7 @@ defmodule Indexer.Fetcher.PolygonEdge.Deposit do defp get_timestamps_by_events(events, json_rpc_named_arguments) do events - |> get_blocks_by_events(json_rpc_named_arguments, 100_000_000) + |> get_blocks_by_events(json_rpc_named_arguments, Helper.infinite_retries_number()) |> Enum.reduce(%{}, fn block, acc -> block_number = quantity_to_integer(Map.get(block, "number")) {:ok, timestamp} = DateTime.from_unix(quantity_to_integer(Map.get(block, "timestamp"))) diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex index 82a8a2d7ed2f..8c1c85c220d1 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex @@ -105,7 +105,8 @@ defmodule Indexer.Fetcher.PolygonEdge.DepositExecute do if not safe_block_is_latest do # find and fill all events between "safe" and "latest" block (excluding "safe") - {:ok, latest_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + {:ok, latest_block} = + Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, Helper.infinite_retries_number()) fill_block_range( safe_block + 1, @@ -181,7 +182,7 @@ defmodule Indexer.Fetcher.PolygonEdge.DepositExecute do state_receiver, @state_sync_result_event, json_rpc_named_arguments, - 100_000_000 + Helper.infinite_retries_number() ) Enum.map(result, fn event -> @@ -196,7 +197,7 @@ defmodule Indexer.Fetcher.PolygonEdge.DepositExecute do # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise import_options = - if System.get_env("CHAIN_TYPE") == "polygon_edge" do + if Application.get_env(:explorer, :chain_type) == "polygon_edge" do %{ polygon_edge_deposit_executes: %{params: executes}, timeout: :infinity diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex index c952d51618f2..4ec0f7e0776c 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex @@ -110,7 +110,8 @@ defmodule Indexer.Fetcher.PolygonEdge.Withdrawal do if not safe_block_is_latest do # find and fill all events between "safe" and "latest" block (excluding "safe") - {:ok, latest_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + {:ok, latest_block} = + Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, Helper.infinite_retries_number()) fill_block_range( safe_block + 1, @@ -196,7 +197,7 @@ defmodule Indexer.Fetcher.PolygonEdge.Withdrawal do state_sender, @l2_state_synced_event, json_rpc_named_arguments, - 100_000_000 + Helper.infinite_retries_number() ) Enum.map(result, fn event -> @@ -211,7 +212,7 @@ defmodule Indexer.Fetcher.PolygonEdge.Withdrawal do # here we explicitly check CHAIN_TYPE as Dialyzer throws an error otherwise import_options = - if System.get_env("CHAIN_TYPE") == "polygon_edge" do + if Application.get_env(:explorer, :chain_type) == "polygon_edge" do %{ polygon_edge_withdrawals: %{params: withdrawals}, timeout: :infinity diff --git a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1.ex index cd7fe24344f8..9899c709910a 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1.ex @@ -146,7 +146,7 @@ defmodule Indexer.Fetcher.PolygonZkevm.BridgeL1 do chunk_end = List.last(current_chunk) if chunk_start <= chunk_end do - Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, "L1") + Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, :L1) operations = {chunk_start, chunk_end} @@ -161,7 +161,7 @@ defmodule Indexer.Fetcher.PolygonZkevm.BridgeL1 do start_block, end_block, "#{Enum.count(operations)} L1 operation(s)", - "L1" + :L1 ) end diff --git a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l2.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l2.ex index f8d7695cd91a..2cd121881b2e 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l2.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l2.ex @@ -134,7 +134,7 @@ defmodule Indexer.Fetcher.PolygonZkevm.BridgeL2 do chunk_end = List.last(current_chunk) if chunk_start <= chunk_end do - Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, "L2") + Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, :L2) operations = {chunk_start, chunk_end} @@ -149,7 +149,7 @@ defmodule Indexer.Fetcher.PolygonZkevm.BridgeL2 do start_block, end_block, "#{Enum.count(operations)} L2 operation(s)", - "L2" + :L2 ) end end) diff --git a/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex b/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex index 76ed302099de..4a3889dd2a4d 100644 --- a/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex +++ b/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex @@ -243,7 +243,7 @@ defmodule Indexer.Fetcher.Shibarium.L1 do chunk_end = List.last(current_chunk) if chunk_start <= chunk_end do - Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, "L1") + Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, :L1) operations = {chunk_start, chunk_end} @@ -278,7 +278,7 @@ defmodule Indexer.Fetcher.Shibarium.L1 do start_block, end_block, "#{Enum.count(operations)} L1 operation(s)", - "L1" + :L1 ) end @@ -293,7 +293,9 @@ defmodule Indexer.Fetcher.Shibarium.L1 do end) new_start_block = last_written_block + 1 - {:ok, new_end_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + + {:ok, new_end_block} = + Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, Helper.infinite_retries_number()) delay = if new_end_block == last_written_block do @@ -352,7 +354,7 @@ defmodule Indexer.Fetcher.Shibarium.L1 do |> Kernel.||({0, nil}) end - defp get_logs(from_block, to_block, address, topics, json_rpc_named_arguments, retries \\ 100_000_000) do + defp get_logs(from_block, to_block, address, topics, json_rpc_named_arguments, retries) do processed_from_block = integer_to_quantity(from_block) processed_to_block = integer_to_quantity(to_block) @@ -402,7 +404,8 @@ defmodule Indexer.Fetcher.Shibarium.L1 do @exited_ether_event ] ], - json_rpc_named_arguments + json_rpc_named_arguments, + Helper.infinite_retries_number() ) contract_addresses = @@ -421,7 +424,8 @@ defmodule Indexer.Fetcher.Shibarium.L1 do @transfer_event, contract_addresses ], - json_rpc_named_arguments + json_rpc_named_arguments, + Helper.infinite_retries_number() ) {:ok, unknown_erc1155_tokens_result} = @@ -437,7 +441,8 @@ defmodule Indexer.Fetcher.Shibarium.L1 do nil, pad_address_hash(erc1155_predicate_proxy) ], - json_rpc_named_arguments + json_rpc_named_arguments, + Helper.infinite_retries_number() ) end @@ -552,7 +557,7 @@ defmodule Indexer.Fetcher.Shibarium.L1 do timestamps = events |> filter_deposit_events() - |> Helper.get_blocks_by_events(json_rpc_named_arguments, 100_000_000) + |> Helper.get_blocks_by_events(json_rpc_named_arguments, Helper.infinite_retries_number()) |> Enum.reduce(%{}, fn block, acc -> block_number = quantity_to_integer(Map.get(block, "number")) {:ok, timestamp} = DateTime.from_unix(quantity_to_integer(Map.get(block, "timestamp"))) diff --git a/apps/indexer/lib/indexer/fetcher/shibarium/l2.ex b/apps/indexer/lib/indexer/fetcher/shibarium/l2.ex index 41512c464944..b85e4b558694 100644 --- a/apps/indexer/lib/indexer/fetcher/shibarium/l2.ex +++ b/apps/indexer/lib/indexer/fetcher/shibarium/l2.ex @@ -174,7 +174,7 @@ defmodule Indexer.Fetcher.Shibarium.L2 do chunk_start = List.first(current_chunk) chunk_end = List.last(current_chunk) - Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, "L2") + Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, :L2) operations = chunk_start..chunk_end @@ -201,7 +201,7 @@ defmodule Indexer.Fetcher.Shibarium.L2 do start_block, end_block, "#{Enum.count(operations)} L2 operation(s)", - "L2" + :L2 ) end) @@ -316,7 +316,7 @@ defmodule Indexer.Fetcher.Shibarium.L2 do end defp get_logs_all(block_range, child_chain, bone_withdraw, json_rpc_named_arguments) do - blocks = get_blocks_by_range(block_range, json_rpc_named_arguments, 100_000_000) + blocks = get_blocks_by_range(block_range, json_rpc_named_arguments, Helper.infinite_retries_number()) deposit_logs = get_deposit_logs_from_receipts(blocks, child_chain, json_rpc_named_arguments) @@ -355,7 +355,7 @@ defmodule Indexer.Fetcher.Shibarium.L2 do end) |> Enum.chunk_every(@eth_get_logs_range_size) |> Enum.reduce([], fn hashes, acc -> - acc ++ get_receipt_logs(hashes, json_rpc_named_arguments, 100_000_000) + acc ++ get_receipt_logs(hashes, json_rpc_named_arguments, Helper.infinite_retries_number()) end) |> filter_deposit_events(child_chain) end @@ -376,7 +376,7 @@ defmodule Indexer.Fetcher.Shibarium.L2 do end) |> Enum.chunk_every(@eth_get_logs_range_size) |> Enum.reduce([], fn hashes, acc -> - acc ++ get_receipt_logs(hashes, json_rpc_named_arguments, 100_000_000) + acc ++ get_receipt_logs(hashes, json_rpc_named_arguments, Helper.infinite_retries_number()) end) |> filter_withdrawal_events(bone_withdraw) end diff --git a/apps/indexer/lib/indexer/fetcher/transaction_action.ex b/apps/indexer/lib/indexer/fetcher/transaction_action.ex index 1d142aad502c..ac851b39992e 100644 --- a/apps/indexer/lib/indexer/fetcher/transaction_action.ex +++ b/apps/indexer/lib/indexer/fetcher/transaction_action.ex @@ -13,8 +13,9 @@ defmodule Indexer.Fetcher.TransactionAction do from: 2 ] + import Explorer.Helper, only: [parse_integer: 1] + alias Explorer.{Chain, Repo} - alias Explorer.Helper, as: ExplorerHelper alias Explorer.Chain.{Block, BlockNumberHelper, Log, TransactionAction} alias Indexer.Transform.{Addresses, TransactionActions} @@ -197,8 +198,8 @@ defmodule Indexer.Fetcher.TransactionAction do logger_metadata = Logger.metadata() Logger.metadata(fetcher: :transaction_action) - first_block = ExplorerHelper.parse_integer(first_block) - last_block = ExplorerHelper.parse_integer(last_block) + first_block = parse_integer(first_block) + last_block = parse_integer(last_block) return = if is_nil(first_block) or is_nil(last_block) or first_block <= 0 or last_block <= 0 or first_block > last_block do diff --git a/apps/indexer/lib/indexer/helper.ex b/apps/indexer/lib/indexer/helper.ex index d8d3a650cb23..9f4983799e47 100644 --- a/apps/indexer/lib/indexer/helper.ex +++ b/apps/indexer/lib/indexer/helper.ex @@ -17,6 +17,8 @@ defmodule Indexer.Helper do alias EthereumJSONRPC.Blocks alias Explorer.Chain.Hash + @finite_retries_number 3 + @infinite_retries_number 100_000_000 @block_check_interval_range_size 100 @block_by_number_chunk_size 50 @@ -102,7 +104,7 @@ defmodule Indexer.Helper do Performs a specified number of retries (up to) if the first attempt returns error. """ @spec get_block_number_by_tag(binary(), list(), non_neg_integer()) :: {:ok, non_neg_integer()} | {:error, atom()} - def get_block_number_by_tag(tag, json_rpc_named_arguments, retries \\ 3) do + def get_block_number_by_tag(tag, json_rpc_named_arguments, retries \\ @finite_retries_number) do error_message = &"Cannot fetch #{tag} block number. Error: #{inspect(&1)}" repeated_call(&fetch_block_number_by_tag/2, [tag, json_rpc_named_arguments], error_message, retries) end @@ -112,7 +114,7 @@ defmodule Indexer.Helper do Performs a specified number of retries (up to) if the first attempt returns error. """ @spec get_transaction_by_hash(binary() | nil, list(), non_neg_integer()) :: {:ok, any()} | {:error, any()} - def get_transaction_by_hash(hash, json_rpc_named_arguments, retries_left \\ 3) + def get_transaction_by_hash(hash, json_rpc_named_arguments, retries_left \\ @finite_retries_number) def get_transaction_by_hash(hash, _json_rpc_named_arguments, _retries_left) when is_nil(hash), do: {:ok, nil} @@ -129,6 +131,10 @@ defmodule Indexer.Helper do repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) end + def infinite_retries_number do + @infinite_retries_number + end + @doc """ Forms JSON RPC named arguments for the given RPC URL. """ @@ -157,7 +163,7 @@ defmodule Indexer.Helper do non_neg_integer(), non_neg_integer(), binary() | nil, - binary() + :L1 | :L2 ) :: :ok def log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, items_count, layer) do is_start = is_nil(items_count) @@ -200,11 +206,11 @@ defmodule Indexer.Helper do @doc """ Calls the given function with the given arguments until it returns {:ok, any()} or the given attempts number is reached. - Pauses execution between invokes for 3 seconds. + Pauses execution between invokes for 3..1200 seconds (depending on the number of retries). """ @spec repeated_call((... -> any()), list(), (... -> any()), non_neg_integer()) :: - {:ok, any()} | {:error, binary() | atom()} - def repeated_call(func, args, error_message, retries_left) do + {:ok, any()} | {:error, binary() | atom() | map()} + def repeated_call(func, args, error_message, retries_left, retries_done \\ 0) do case apply(func, args) do {:ok, _} = res -> res @@ -217,8 +223,11 @@ defmodule Indexer.Helper do err else Logger.error("#{error_message.(message)} Retrying...") - :timer.sleep(3000) - repeated_call(func, args, error_message, retries_left) + + # wait up to 20 minutes + :timer.sleep(min(3000 * Integer.pow(2, retries_done), 1_200_000)) + + repeated_call(func, args, error_message, retries_left, retries_done + 1) end end end @@ -266,7 +275,7 @@ defmodule Indexer.Helper do """ @spec get_block_timestamp_by_number(non_neg_integer(), list(), non_neg_integer()) :: {:ok, non_neg_integer()} | {:error, any()} - def get_block_timestamp_by_number(number, json_rpc_named_arguments, retries \\ 3) do + def get_block_timestamp_by_number(number, json_rpc_named_arguments, retries \\ @finite_retries_number) do func = &get_block_timestamp_by_number_inner/2 args = [number, json_rpc_named_arguments] error_message = &"Cannot fetch block ##{number} or its timestamp. Error: #{inspect(&1)}" diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index defc0e3bae22..b1018baa39a4 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -137,6 +137,18 @@ defmodule Indexer.Supervisor do {TokenUpdater.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, {ReplacedTransaction.Supervisor, [[memory_monitor: memory_monitor]]}, + configure(Indexer.Fetcher.Optimism.Supervisor, [[memory_monitor: memory_monitor]]), + configure( + Indexer.Fetcher.Optimism.TxnBatch.Supervisor, + [[memory_monitor: memory_monitor, json_rpc_named_arguments: json_rpc_named_arguments]] + ), + configure(Indexer.Fetcher.Optimism.OutputRoot.Supervisor, [[memory_monitor: memory_monitor]]), + configure(Indexer.Fetcher.Optimism.Deposit.Supervisor, [[memory_monitor: memory_monitor]]), + configure( + Indexer.Fetcher.Optimism.Withdrawal.Supervisor, + [[memory_monitor: memory_monitor, json_rpc_named_arguments: json_rpc_named_arguments]] + ), + configure(Indexer.Fetcher.Optimism.WithdrawalEvent.Supervisor, [[memory_monitor: memory_monitor]]), {Indexer.Fetcher.RollupL1ReorgMonitor.Supervisor, [[memory_monitor: memory_monitor]]}, configure(Indexer.Fetcher.PolygonEdge.Deposit.Supervisor, [[memory_monitor: memory_monitor]]), configure(Indexer.Fetcher.PolygonEdge.DepositExecute.Supervisor, [ diff --git a/apps/indexer/lib/indexer/transform/addresses.ex b/apps/indexer/lib/indexer/transform/addresses.ex index 543352d123e9..af645ba34457 100644 --- a/apps/indexer/lib/indexer/transform/addresses.ex +++ b/apps/indexer/lib/indexer/transform/addresses.ex @@ -48,6 +48,8 @@ defmodule Indexer.Transform.Addresses do } """ + alias Indexer.Helper + @entity_to_address_map %{ address_coin_balances: [ [ @@ -506,7 +508,7 @@ defmodule Indexer.Transform.Addresses do end defp find_tx_action_addresses(block_number, value, accumulator) when is_binary(value) do - if is_address?(value) do + if Helper.address_correct?(value) do [%{:fetched_coin_balance_block_number => block_number, :hash => value} | accumulator] else accumulator @@ -599,8 +601,4 @@ defmodule Indexer.Transform.Addresses do defp max_nil_last(first_integer, second_integer) when is_integer(first_integer) and is_integer(second_integer), do: max(first_integer, second_integer) - - defp is_address?(value) when is_binary(value) do - String.match?(value, ~r/^0x[[:xdigit:]]{40}$/i) - end end diff --git a/apps/indexer/lib/indexer/transform/optimism/withdrawals.ex b/apps/indexer/lib/indexer/transform/optimism/withdrawals.ex new file mode 100644 index 000000000000..f82dd4fcdd84 --- /dev/null +++ b/apps/indexer/lib/indexer/transform/optimism/withdrawals.ex @@ -0,0 +1,49 @@ +defmodule Indexer.Transform.Optimism.Withdrawals do + @moduledoc """ + Helper functions for transforming data for Optimism withdrawals. + """ + + require Logger + + alias Indexer.Fetcher.Optimism.Withdrawal, as: OptimismWithdrawal + alias Indexer.Helper + + # 32-byte signature of the event MessagePassed(uint256 indexed nonce, address indexed sender, address indexed target, uint256 value, uint256 gasLimit, bytes data, bytes32 withdrawalHash) + @message_passed_event "0x02a52367d10742d8032712c1bb8e0144ff1ec5ffda1ed7d70bb05a2744955054" + + @doc """ + Returns a list of withdrawals given a list of logs. + """ + def parse(logs) do + prev_metadata = Logger.metadata() + Logger.metadata(fetcher: :optimism_withdrawals_realtime) + + items = + with false <- is_nil(Application.get_env(:indexer, Indexer.Fetcher.OptimismWithdrawal)[:start_block_l2]), + message_passer = Application.get_env(:indexer, Indexer.Fetcher.OptimismWithdrawal)[:message_passer], + true <- Helper.address_correct?(message_passer) do + message_passer = String.downcase(message_passer) + + logs + |> Enum.filter(fn log -> + !is_nil(log.first_topic) && String.downcase(log.first_topic) == @message_passed_event && + String.downcase(Helper.address_hash_to_string(log.address_hash)) == message_passer + end) + |> Enum.map(fn log -> + Logger.info("Withdrawal message found, nonce: #{log.second_topic}.") + OptimismWithdrawal.event_to_withdrawal(log.second_topic, log.data, log.transaction_hash, log.block_number) + end) + else + true -> + [] + + false -> + Logger.error("L2ToL1MessagePasser contract address is incorrect. Cannot use #{__MODULE__} for parsing logs.") + [] + end + + Logger.reset_metadata(prev_metadata) + + items + end +end diff --git a/apps/indexer/lib/indexer/transform/polygon_zkevm/bridge.ex b/apps/indexer/lib/indexer/transform/polygon_zkevm/bridge.ex index 441e6950d132..4ee7fa4126ef 100644 --- a/apps/indexer/lib/indexer/transform/polygon_zkevm/bridge.ex +++ b/apps/indexer/lib/indexer/transform/polygon_zkevm/bridge.ex @@ -21,7 +21,7 @@ defmodule Indexer.Transform.PolygonZkevm.Bridge do items = with false <- is_nil(Application.get_env(:indexer, BridgeL2)[:start_block]), - false <- System.get_env("CHAIN_TYPE") != "polygon_zkevm", + false <- Application.get_env(:explorer, :chain_type) != "polygon_zkevm", rpc_l1 = Application.get_all_env(:indexer)[BridgeL1][:rpc], {:rpc_l1_undefined, false} <- {:rpc_l1_undefined, is_nil(rpc_l1)}, bridge_contract = Application.get_env(:indexer, BridgeL2)[:bridge_contract], @@ -33,7 +33,7 @@ defmodule Indexer.Transform.PolygonZkevm.Bridge do start_block = Enum.min(block_numbers) end_block = Enum.max(block_numbers) - Helper.log_blocks_chunk_handling(start_block, end_block, start_block, end_block, nil, "L2") + Helper.log_blocks_chunk_handling(start_block, end_block, start_block, end_block, nil, :L2) json_rpc_named_arguments_l1 = Helper.json_rpc_named_arguments(rpc_l1) @@ -50,7 +50,7 @@ defmodule Indexer.Transform.PolygonZkevm.Bridge do start_block, end_block, "#{Enum.count(items)} L2 operation(s)", - "L2" + :L2 ) items diff --git a/apps/indexer/lib/indexer/transform/shibarium/bridge.ex b/apps/indexer/lib/indexer/transform/shibarium/bridge.ex index 950d187d8bff..ddf734507ecf 100644 --- a/apps/indexer/lib/indexer/transform/shibarium/bridge.ex +++ b/apps/indexer/lib/indexer/transform/shibarium/bridge.ex @@ -24,7 +24,7 @@ defmodule Indexer.Transform.Shibarium.Bridge do items = with false <- is_nil(Application.get_env(:indexer, Indexer.Fetcher.Shibarium.L2)[:start_block]), - false <- System.get_env("CHAIN_TYPE") != "shibarium", + false <- Application.get_env(:explorer, :chain_type) != "shibarium", child_chain = Application.get_env(:indexer, Indexer.Fetcher.Shibarium.L2)[:child_chain], weth = Application.get_env(:indexer, Indexer.Fetcher.Shibarium.L2)[:weth], bone_withdraw = Application.get_env(:indexer, Indexer.Fetcher.Shibarium.L2)[:bone_withdraw], @@ -39,7 +39,7 @@ defmodule Indexer.Transform.Shibarium.Bridge do start_block = Enum.min(block_numbers) end_block = Enum.max(block_numbers) - Helper.log_blocks_chunk_handling(start_block, end_block, start_block, end_block, nil, "L2") + Helper.log_blocks_chunk_handling(start_block, end_block, start_block, end_block, nil, :L2) deposit_transaction_hashes = transactions_with_receipts @@ -76,7 +76,7 @@ defmodule Indexer.Transform.Shibarium.Bridge do start_block, end_block, "#{Enum.count(operations)} L2 operation(s)", - "L2" + :L2 ) items diff --git a/apps/indexer/lib/indexer/transform/token_transfers.ex b/apps/indexer/lib/indexer/transform/token_transfers.ex index 5163c5f7f458..5acc4ce0fd7a 100644 --- a/apps/indexer/lib/indexer/transform/token_transfers.ex +++ b/apps/indexer/lib/indexer/transform/token_transfers.ex @@ -6,9 +6,8 @@ defmodule Indexer.Transform.TokenTransfers do require Logger import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] - import Explorer.Helper, only: [decode_data: 2] - alias Explorer.Repo + alias Explorer.{Helper, Repo} alias Explorer.Chain.{Token, TokenTransfer} alias Indexer.Fetcher.TokenTotalSupplyUpdater @@ -177,7 +176,7 @@ defmodule Indexer.Transform.TokenTransfers do # ERC-20 token transfer defp parse_params(%{second_topic: second_topic, third_topic: third_topic, fourth_topic: nil} = log) when not is_nil(second_topic) and not is_nil(third_topic) do - [amount] = decode_data(log.data, [{:uint, 256}]) + [amount] = Helper.decode_data(log.data, [{:uint, 256}]) token_transfer = %{ amount: Decimal.new(amount || 0), @@ -203,7 +202,7 @@ defmodule Indexer.Transform.TokenTransfers do # ERC-20 token transfer for WETH defp parse_params(%{second_topic: second_topic, third_topic: nil, fourth_topic: nil} = log) when not is_nil(second_topic) do - [amount] = decode_data(log.data, [{:uint, 256}]) + [amount] = Helper.decode_data(log.data, [{:uint, 256}]) {from_address_hash, to_address_hash} = if log.first_topic == TokenTransfer.weth_deposit_signature() do @@ -236,7 +235,7 @@ defmodule Indexer.Transform.TokenTransfers do # ERC-721 token transfer with topics as addresses defp parse_params(%{second_topic: second_topic, third_topic: third_topic, fourth_topic: fourth_topic} = log) when not is_nil(second_topic) and not is_nil(third_topic) and not is_nil(fourth_topic) do - [token_id] = decode_data(fourth_topic, [{:uint, 256}]) + [token_id] = Helper.decode_data(fourth_topic, [{:uint, 256}]) token_transfer = %{ block_number: log.block_number, @@ -268,7 +267,7 @@ defmodule Indexer.Transform.TokenTransfers do } = log ) when not is_nil(data) do - [from_address_hash, to_address_hash, token_id] = decode_data(data, [:address, :address, {:uint, 256}]) + [from_address_hash, to_address_hash, token_id] = Helper.decode_data(data, [:address, :address, {:uint, 256}]) token_transfer = %{ block_number: log.block_number, @@ -298,7 +297,7 @@ defmodule Indexer.Transform.TokenTransfers do data: data } = log ) do - [token_ids, values] = decode_data(data, [{:array, {:uint, 256}}, {:array, {:uint, 256}}]) + [token_ids, values] = Helper.decode_data(data, [{:array, {:uint, 256}}, {:array, {:uint, 256}}]) if is_nil(token_ids) or token_ids == [] or is_nil(values) or values == [] do nil @@ -326,7 +325,7 @@ defmodule Indexer.Transform.TokenTransfers do end def parse_erc1155_params(%{third_topic: third_topic, fourth_topic: fourth_topic, data: data} = log) do - [token_id, value] = decode_data(data, [{:uint, 256}, {:uint, 256}]) + [token_id, value] = Helper.decode_data(data, [{:uint, 256}, {:uint, 256}]) token_transfer = %{ amount: value, diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs index ae4e6323837e..58bdf7c312cf 100644 --- a/apps/indexer/mix.exs +++ b/apps/indexer/mix.exs @@ -14,7 +14,17 @@ defmodule Indexer.MixProject do elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", start_permanent: Mix.env() == :prod, - version: "6.2.2" + version: "6.2.2", + xref: [ + exclude: [ + Explorer.Chain.Optimism.Deposit, + Explorer.Chain.Optimism.FrameSequence, + Explorer.Chain.Optimism.OutputRoot, + Explorer.Chain.Optimism.TxnBatch, + Explorer.Chain.Optimism.Withdrawal, + Explorer.Chain.Optimism.WithdrawalEvent + ] + ] ] end @@ -56,7 +66,8 @@ defmodule Indexer.MixProject do {:spandex, "~> 3.0"}, # `:spandex` integration with Datadog {:spandex_datadog, "~> 1.0"}, - {:logger_json, "~> 5.1"} + {:logger_json, "~> 5.1"}, + {:varint, "~> 1.4"} ] end diff --git a/config/config_helper.exs b/config/config_helper.exs index 45e34c40cd7f..a5c0a2e67fbf 100644 --- a/config/config_helper.exs +++ b/config/config_helper.exs @@ -10,6 +10,7 @@ defmodule ConfigHelper do repos = case System.get_env("CHAIN_TYPE") do "ethereum" -> base_repos ++ [Explorer.Repo.Beacon] + "optimism" -> base_repos ++ [Explorer.Repo.Optimism] "polygon_edge" -> base_repos ++ [Explorer.Repo.PolygonEdge] "polygon_zkevm" -> base_repos ++ [Explorer.Repo.PolygonZkevm] "rsk" -> base_repos ++ [Explorer.Repo.RSK] diff --git a/config/runtime.exs b/config/runtime.exs index 3b7ec8e420a6..7f17424b8b38 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -259,6 +259,8 @@ config :explorer, Explorer.Chain.Cache.RootstockLockedBTC, global_ttl: ConfigHelper.parse_time_env_var("ROOTSTOCK_LOCKED_BTC_CACHE_PERIOD", "10m"), locking_cap: ConfigHelper.parse_integer_env_var("ROOTSTOCK_LOCKING_CAP", 21_000_000) +config :explorer, Explorer.Chain.Cache.OptimismFinalizationPeriod, enabled: ConfigHelper.chain_type() == "optimism" + config :explorer, Explorer.Counters.AddressTransactionsGasUsageCounter, cache_period: ConfigHelper.parse_time_env_var("CACHE_ADDRESS_TRANSACTIONS_GAS_USAGE_COUNTER_PERIOD", "30m") @@ -681,6 +683,39 @@ config :indexer, Indexer.Fetcher.CoinBalance.Realtime, batch_size: coin_balances_batch_size, concurrency: coin_balances_concurrency +config :indexer, Indexer.Fetcher.Optimism.Supervisor, enabled: ConfigHelper.chain_type() == "optimism" +config :indexer, Indexer.Fetcher.Optimism.TxnBatch.Supervisor, enabled: ConfigHelper.chain_type() == "optimism" +config :indexer, Indexer.Fetcher.Optimism.OutputRoot.Supervisor, enabled: ConfigHelper.chain_type() == "optimism" +config :indexer, Indexer.Fetcher.Optimism.Deposit.Supervisor, enabled: ConfigHelper.chain_type() == "optimism" +config :indexer, Indexer.Fetcher.Optimism.Withdrawal.Supervisor, enabled: ConfigHelper.chain_type() == "optimism" +config :indexer, Indexer.Fetcher.Optimism.WithdrawalEvent.Supervisor, enabled: ConfigHelper.chain_type() == "optimism" + +config :indexer, Indexer.Fetcher.Optimism, + optimism_l1_rpc: System.get_env("INDEXER_OPTIMISM_L1_RPC"), + optimism_l1_portal: System.get_env("INDEXER_OPTIMISM_L1_PORTAL_CONTRACT") + +config :indexer, Indexer.Fetcher.Optimism.Deposit, + start_block_l1: System.get_env("INDEXER_OPTIMISM_L1_DEPOSITS_START_BLOCK"), + batch_size: System.get_env("INDEXER_OPTIMISM_L1_DEPOSITS_BATCH_SIZE") + +config :indexer, Indexer.Fetcher.Optimism.OutputRoot, + start_block_l1: System.get_env("INDEXER_OPTIMISM_L1_OUTPUT_ROOTS_START_BLOCK"), + output_oracle: System.get_env("INDEXER_OPTIMISM_L1_OUTPUT_ORACLE_CONTRACT") + +config :indexer, Indexer.Fetcher.Optimism.Withdrawal, + start_block_l2: System.get_env("INDEXER_OPTIMISM_L2_WITHDRAWALS_START_BLOCK"), + message_passer: System.get_env("INDEXER_OPTIMISM_L2_MESSAGE_PASSER_CONTRACT") + +config :indexer, Indexer.Fetcher.Optimism.WithdrawalEvent, + start_block_l1: System.get_env("INDEXER_OPTIMISM_L1_WITHDRAWALS_START_BLOCK") + +config :indexer, Indexer.Fetcher.Optimism.TxnBatch, + start_block_l1: System.get_env("INDEXER_OPTIMISM_L1_BATCH_START_BLOCK"), + batch_inbox: System.get_env("INDEXER_OPTIMISM_L1_BATCH_INBOX"), + batch_submitter: System.get_env("INDEXER_OPTIMISM_L1_BATCH_SUBMITTER"), + blocks_chunk_size: System.get_env("INDEXER_OPTIMISM_L1_BATCH_BLOCKS_CHUNK_SIZE", "4"), + genesis_block_l2: ConfigHelper.parse_integer_or_nil_env_var("INDEXER_OPTIMISM_L2_BATCH_GENESIS_BLOCK_NUMBER") + config :indexer, Indexer.Fetcher.Withdrawal.Supervisor, disabled?: System.get_env("INDEXER_DISABLE_WITHDRAWALS_FETCHER", "true") == "true" diff --git a/config/runtime/dev.exs b/config/runtime/dev.exs index 12809ebda8b1..12b0d3bde838 100644 --- a/config/runtime/dev.exs +++ b/config/runtime/dev.exs @@ -92,6 +92,13 @@ config :explorer, Explorer.Repo.BridgedTokens, # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type pool_size: 1 +# Configure Optimism database +config :explorer, Explorer.Repo.Optimism, + database: database, + hostname: hostname, + url: System.get_env("DATABASE_URL"), + pool_size: 1 + # Configure PolygonEdge database config :explorer, Explorer.Repo.PolygonEdge, database: database, diff --git a/config/runtime/prod.exs b/config/runtime/prod.exs index 7474cde488cf..abf8c2137dc4 100644 --- a/config/runtime/prod.exs +++ b/config/runtime/prod.exs @@ -67,6 +67,12 @@ config :explorer, Explorer.Repo.BridgedTokens, pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() +# Configures Optimism database +config :explorer, Explorer.Repo.Optimism, + url: System.get_env("DATABASE_URL"), + pool_size: 1, + ssl: ExplorerConfigHelper.ssl_enabled?() + # Configures PolygonEdge database config :explorer, Explorer.Repo.PolygonEdge, url: System.get_env("DATABASE_URL"), diff --git a/cspell.json b/cspell.json index 691c40233b58..e3c50bb6d418 100644 --- a/cspell.json +++ b/cspell.json @@ -203,6 +203,7 @@ "graphiql", "grecaptcha", "greymatter", + "gtag", "happygokitty", "haspopup", "Hazkne", @@ -387,8 +388,10 @@ "recollated", "redix", "refetched", + "regclass", "REINDEX", "relname", + "relpages", "reltuples", "remasc", "removedfile", @@ -412,6 +415,7 @@ "selfdestruct", "selfdestructed", "SENDGRID", + "Sepolia", "Sérgio", "sharelock", "sharelocks", @@ -519,6 +523,7 @@ "valuemax", "valuemin", "valuenow", + "varint", "verifysourcecode", "viewerjs", "volumefrom", diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index dff3a43c5091..5738154d2bda 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -193,6 +193,20 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false # INDEXER_TOKEN_BALANCES_FETCHER_INIT_QUERY_LIMIT= # INDEXER_COIN_BALANCES_FETCHER_INIT_QUERY_LIMIT= # WITHDRAWALS_FIRST_BLOCK= +# INDEXER_OPTIMISM_L1_RPC= +# INDEXER_OPTIMISM_L1_BATCH_START_BLOCK= +# INDEXER_OPTIMISM_L1_BATCH_INBOX= +# INDEXER_OPTIMISM_L1_BATCH_SUBMITTER= +# INDEXER_OPTIMISM_L1_BATCH_BLOCKS_CHUNK_SIZE= +# INDEXER_OPTIMISM_L2_BATCH_GENESIS_BLOCK_NUMBER= +# INDEXER_OPTIMISM_L1_PORTAL_CONTRACT= +# INDEXER_OPTIMISM_L1_OUTPUT_ROOTS_START_BLOCK= +# INDEXER_OPTIMISM_L1_OUTPUT_ORACLE_CONTRACT= +# INDEXER_OPTIMISM_L1_WITHDRAWALS_START_BLOCK= +# INDEXER_OPTIMISM_L2_WITHDRAWALS_START_BLOCK= +# INDEXER_OPTIMISM_L2_MESSAGE_PASSER_CONTRACT= +# INDEXER_OPTIMISM_L1_DEPOSITS_START_BLOCK= +# INDEXER_OPTIMISM_L1_DEPOSITS_BATCH_SIZE= # ROOTSTOCK_REMASC_ADDRESS= # ROOTSTOCK_BRIDGE_ADDRESS= # ROOTSTOCK_LOCKED_BTC_CACHE_PERIOD= diff --git a/mix.lock b/mix.lock index f2dfe2a39a1e..8908d5bde698 100644 --- a/mix.lock +++ b/mix.lock @@ -140,6 +140,7 @@ "ueberauth": {:hex, :ueberauth, "0.10.7", "5a31cbe11e7ce5c7484d745dc9e1f11948e89662f8510d03c616de03df581ebd", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "0bccf73e2ffd6337971340832947ba232877aa8122dba4c95be9f729c8987377"}, "ueberauth_auth0": {:hex, :ueberauth_auth0, "2.1.0", "0632d5844049fa2f26823f15e1120aa32f27df6f27ce515a4b04641736594bf4", [:mix], [{:oauth2, "~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "8d3b30fa27c95c9e82c30c4afb016251405706d2e9627e603c3c9787fd1314fc"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, + "varint": {:hex, :varint, "1.4.0", "b7405c8a99db7b95d4341fa9cb15e7c3af6c8dda43e21bbe1c4a9cdff50b6502", [:mix], [], "hexpm", "0fd461901b7120c03467530dff3c58fa3475328fd75ba72c7d3cbf13bce6b0d2"}, "wallaby": {:hex, :wallaby, "0.30.6", "7dc4c1213f3b52c4152581d126632bc7e06892336d3a0f582853efeeabd45a71", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "50950c1d968549b54c20e16175c68c7fc0824138e2bb93feb11ef6add8eb23d4"}, "web_driver_client": {:hex, :web_driver_client, "0.2.0", "63b76cd9eb3b0716ec5467a0f8bead73d3d9612e63f7560d21357f03ad86e31a", [:mix], [{:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.3", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "83cc6092bc3e74926d1c8455f0ce927d5d1d36707b74d9a65e38c084aab0350f"}, "websocket_client": {:git, "https://github.com/blockscout/websocket_client.git", "0b4ecc5b1fb8a0bd1c8352728da787c20add53aa", [branch: "master"]}, From 66b7badf37ddb0bbec74d32edf28670fca7415b7 Mon Sep 17 00:00:00 2001 From: Fedor Ivanov Date: Mon, 4 Mar 2024 18:58:28 +0300 Subject: [PATCH 528/607] Fix missing `0x` prefix in `eth_getLogs` response (#9514) * Remove `transactionLogIndex` from `eth_getLogs` response * Fix missing `0x` prefix for `blockNumber`, `logIndex`, `transactionIndex` in `eth_getLogs` response * Fix logs pagination test * Fix docs for `index` field in chain log * Update smart contract ABI wiki link * Add regression test for missing `0x` in `eth_getLogs` --- CHANGELOG.md | 1 + .../api/rpc/eth_controller_test.exs | 49 ++++++++++++++++++- apps/explorer/lib/explorer/chain/log.ex | 2 +- apps/explorer/lib/explorer/eth_rpc.ex | 13 +++-- .../lib/explorer/smart_contract/reader.ex | 2 +- 5 files changed, 59 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c4440da5a7f..3bc36e357e75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Fixes +- [#9514](https://github.com/blockscout/blockscout/pull/9514) - Fix missing `0x` prefix for `blockNumber`, `logIndex`, `transactionIndex` and remove `transactionLogIndex` in `eth_getLogs` response. - [#9512](https://github.com/blockscout/blockscout/pull/9512) - Docker-compose 2.24.6 compatibility - [#9262](https://github.com/blockscout/blockscout/pull/9262) - Fix withdrawal status - [#9123](https://github.com/blockscout/blockscout/pull/9123) - Fixes in Optimism due to changed log topics type diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs index b77a23a39a30..5b4bb4f6fc79 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs @@ -198,7 +198,8 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do assert Enum.count(response["result"]) == 1000 - {last_log_index, ""} = Integer.parse(List.last(response["result"])["logIndex"], 16) + "0x" <> hexadecimal_digits = List.last(response["result"])["logIndex"] + {last_log_index, ""} = Integer.parse(hexadecimal_digits, 16) next_page_params = %{ "blockNumber" => Integer.to_string(transaction.block_number, 16), @@ -225,7 +226,8 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do assert Enum.all?(inserted_records, fn record -> Enum.any?(all_found_logs, fn found_log -> - {index, ""} = Integer.parse(found_log["logIndex"], 16) + "0x" <> hexadecimal_digits = found_log["logIndex"] + {index, ""} = Integer.parse(hexadecimal_digits, 16) record.index == index end) @@ -456,6 +458,49 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do assert [%{"data" => "0x030303"}] = response["result"] end + + test "numerical fields are hexadecimals with 0x prefix", + %{conn: conn, api_params: api_params} do + address = insert(:address) + block = insert(:block, number: 0) + transaction = insert(:transaction, from_address: address) |> with_block(block) + + 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] + } + ]) + + response = + conn + |> post("/api/eth-rpc", params) + |> json_response(200) + + [result] = response["result"] + + assert result + |> Map.take([ + "address", + "blockHash", + "blockNumber", + "data", + "transactionIndex", + "logIndex", + "transactionHash" + ]) + |> Enum.all?(fn {_, v} -> String.starts_with?(v, "0x") end) + end end describe "eth_get_balance" do diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 0ea5b9313ffd..444daa88c6c7 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -26,7 +26,7 @@ defmodule Explorer.Chain.Log do * `fourth_topic` - `topics[3]` * `transaction` - transaction for which `log` is * `transaction_hash` - foreign key for `transaction`. - * `index` - index of the log entry in all logs for the `transaction` + * `index` - index of the log entry within the block """ @primary_key false typed_schema "logs" do diff --git a/apps/explorer/lib/explorer/eth_rpc.ex b/apps/explorer/lib/explorer/eth_rpc.ex index d7260cf3987c..c3b8a80af44a 100644 --- a/apps/explorer/lib/explorer/eth_rpc.ex +++ b/apps/explorer/lib/explorer/eth_rpc.ex @@ -173,14 +173,19 @@ defmodule Explorer.EthRPC do %{ "address" => to_string(log.address_hash), "blockHash" => to_string(log.block_hash), - "blockNumber" => Integer.to_string(log.block_number, 16), + "blockNumber" => + log.block_number + |> encode_quantity(), "data" => to_string(log.data), - "logIndex" => Integer.to_string(log.index, 16), + "logIndex" => + log.index + |> encode_quantity(), "removed" => log.block_consensus == false, "topics" => topics, "transactionHash" => to_string(log.transaction_hash), - "transactionIndex" => log.transaction_index, - "transactionLogIndex" => log.index + "transactionIndex" => + log.transaction_index + |> encode_quantity() } end diff --git a/apps/explorer/lib/explorer/smart_contract/reader.ex b/apps/explorer/lib/explorer/smart_contract/reader.ex index cdfd2ca51c53..791782874ed4 100644 --- a/apps/explorer/lib/explorer/smart_contract/reader.ex +++ b/apps/explorer/lib/explorer/smart_contract/reader.ex @@ -3,7 +3,7 @@ defmodule Explorer.SmartContract.Reader do Reads Smart Contract functions from the blockchain. For information on smart contract's Application Binary Interface (ABI), visit the - [wiki](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI). + [wiki](https://docs.soliditylang.org/en/develop/abi-spec.html). """ alias EthereumJSONRPC.{Contract, Encoder} From fdbb881a18a4b52538d2a9ffb9277a68e56a5e9d Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Mon, 4 Mar 2024 09:43:25 -0700 Subject: [PATCH 529/607] Add blob transaction count and transaction type filter to block view (#9490) * feat: add blob tx count and filter to block view * chore: changelog --- CHANGELOG.md | 1 + .../controllers/api/v2/block_controller.ex | 5 ++++- .../lib/block_scout_web/views/api/v2/block_view.ex | 8 ++++++++ apps/explorer/lib/explorer/chain.ex | 2 ++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bc36e357e75..3d783d81e421 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#9490](https://github.com/blockscout/blockscout/pull/9490) - Add blob transaction counter and filter in block view - [#9461](https://github.com/blockscout/blockscout/pull/9461) - Fetch blocks without internal transactions backwards - [#9460](https://github.com/blockscout/blockscout/pull/9460) - Optimism chain type - [#8702](https://github.com/blockscout/blockscout/pull/8702) - Add OP withdrawal status to transaction page in API diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex index ddb798f43209..a7dbae108e5a 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex @@ -10,7 +10,9 @@ defmodule BlockScoutWeb.API.V2.BlockController do parse_block_hash_or_number_param: 1 ] - import BlockScoutWeb.PagingHelper, only: [delete_parameters_from_next_page_params: 1, select_block_type: 1] + import BlockScoutWeb.PagingHelper, + only: [delete_parameters_from_next_page_params: 1, select_block_type: 1, type_filter_options: 1] + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] alias BlockScoutWeb.API.V2.{TransactionView, WithdrawalView} @@ -109,6 +111,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do full_options = @transaction_necessity_by_association |> Keyword.merge(put_key_value_to_paging_options(paging_options(params), :is_index_in_asc_order, true)) + |> Keyword.merge(type_filter_options(params)) |> Keyword.merge(@api_true) transactions_plus_one = Chain.block_to_transactions(block.hash, full_options, false) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex index 68ad9aa45329..5a661af3f709 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex @@ -96,6 +96,12 @@ defmodule BlockScoutWeb.API.V2.BlockView do def count_transactions(%Block{transactions: txs}) when is_list(txs), do: Enum.count(txs) def count_transactions(_), do: nil + def count_blob_transactions(%Block{transactions: txs}) when is_list(txs), + # EIP-2718 blob transaction type + do: Enum.count(txs, &(&1.type == 3)) + + def count_blob_transactions(_), do: nil + def count_withdrawals(%Block{withdrawals: withdrawals}) when is_list(withdrawals), do: Enum.count(withdrawals) def count_withdrawals(_), do: nil @@ -121,12 +127,14 @@ defmodule BlockScoutWeb.API.V2.BlockView do burnt_blob_transaction_fees = Decimal.mult(block.blob_gas_used || 0, blob_gas_price || 0) result + |> Map.put("blob_tx_count", count_blob_transactions(block)) |> Map.put("blob_gas_used", block.blob_gas_used) |> Map.put("excess_blob_gas", block.excess_blob_gas) |> Map.put("blob_gas_price", blob_gas_price) |> Map.put("burnt_blob_fees", burnt_blob_transaction_fees) else result + |> Map.put("blob_tx_count", count_blob_transactions(block)) |> Map.put("blob_gas_used", block.blob_gas_used) |> Map.put("excess_blob_gas", block.excess_blob_gas) end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 3c08bb6a36f8..9fcd39d96f08 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -545,12 +545,14 @@ defmodule Explorer.Chain do ] def block_to_transactions(block_hash, options \\ [], old_ui? \\ true) when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + type_filter = Keyword.get(options, :type) options |> Keyword.get(:paging_options, @default_paging_options) |> fetch_transactions_in_ascending_order_by_index() |> join(:inner, [transaction], block in assoc(transaction, :block)) |> where([_, block], block.hash == ^block_hash) + |> apply_filter_by_tx_type_to_transactions(type_filter) |> join_associations(necessity_by_association) |> Transaction.put_has_token_transfers_to_tx(old_ui?) |> (&if(old_ui?, do: preload(&1, [{:token_transfers, [:token, :from_address, :to_address]}]), else: &1)).() From 546b732ac13630e5e224af44d3e41e6e3a0c1293 Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Mon, 4 Mar 2024 20:17:26 +0300 Subject: [PATCH 530/607] Add stability validators (#9390) * Add stability validators * Process review comments * Fix tests --- CHANGELOG.md | 1 + .../lib/block_scout_web/api_router.ex | 9 + .../lib/block_scout_web/chain.ex | 28 ++ .../api/v2/validator_controller.ex | 83 +++++ .../lib/block_scout_web/paging_helper.ex | 40 ++- .../views/api/v2/validator_view.ex | 17 ++ .../api/v2/validator_controller_test.exs | 205 +++++++++++++ apps/block_scout_web/test/test_helper.exs | 1 + apps/explorer/config/config.exs | 5 + apps/explorer/config/dev.exs | 2 + apps/explorer/config/prod.exs | 4 + apps/explorer/config/test.exs | 4 +- apps/explorer/lib/explorer/application.ex | 14 +- .../cache/stability_validators_counters.ex | 105 +++++++ .../lib/explorer/chain/stability/validator.ex | 288 ++++++++++++++++++ apps/explorer/lib/explorer/repo.ex | 10 + ...0240203091010_add_stability_validators.exs | 14 + apps/explorer/test/support/factory.ex | 10 + apps/explorer/test/test_helper.exs | 1 + .../lib/indexer/block/realtime/fetcher.ex | 30 +- .../indexer/fetcher/stability/validator.ex | 70 +++++ apps/indexer/lib/indexer/supervisor.ex | 16 +- .../fetcher/stability/validator_test.exs | 270 ++++++++++++++++ config/config_helper.exs | 1 + config/runtime/dev.exs | 9 + config/runtime/prod.exs | 8 + 26 files changed, 1231 insertions(+), 14 deletions(-) create mode 100644 apps/block_scout_web/lib/block_scout_web/controllers/api/v2/validator_controller.ex create mode 100644 apps/block_scout_web/lib/block_scout_web/views/api/v2/validator_view.ex create mode 100644 apps/block_scout_web/test/block_scout_web/controllers/api/v2/validator_controller_test.exs create mode 100644 apps/explorer/lib/explorer/chain/cache/stability_validators_counters.ex create mode 100644 apps/explorer/lib/explorer/chain/stability/validator.ex create mode 100644 apps/explorer/priv/stability/migrations/20240203091010_add_stability_validators.exs create mode 100644 apps/indexer/lib/indexer/fetcher/stability/validator.ex create mode 100644 apps/indexer/test/indexer/fetcher/stability/validator_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d783d81e421..dfeaa7ecacb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [#9490](https://github.com/blockscout/blockscout/pull/9490) - Add blob transaction counter and filter in block view - [#9461](https://github.com/blockscout/blockscout/pull/9461) - Fetch blocks without internal transactions backwards - [#9460](https://github.com/blockscout/blockscout/pull/9460) - Optimism chain type +- [#9390](https://github.com/blockscout/blockscout/pull/9390) - Add stability validators - [#8702](https://github.com/blockscout/blockscout/pull/8702) - Add OP withdrawal status to transaction page in API - [#7200](https://github.com/blockscout/blockscout/pull/7200) - Add Optimism BedRock Deposits to the main page in API - [#6980](https://github.com/blockscout/blockscout/pull/6980) - Add Optimism BedRock support (Txn Batches, Output Roots, Deposits, Withdrawals) diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 403d78d3a48b..39a9475bba19 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -369,6 +369,15 @@ defmodule BlockScoutWeb.ApiRouter do get("/:blob_hash_param", V2.BlobController, :blob) end end + + scope "/validators" do + if Application.compile_env(:explorer, :chain_type) == "stability" do + scope "/stability" do + get("/", V2.ValidatorController, :stability_validators_list) + get("/counters", V2.ValidatorController, :stability_validators_counters) + end + end + end end scope "/v1", as: :api_v1 do diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index 8db363d1352c..d0e110218e15 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -17,7 +17,9 @@ defmodule BlockScoutWeb.Chain do import Explorer.Helper, only: [parse_integer: 1] + alias BlockScoutWeb.PagingHelper alias Ecto.Association.NotLoaded + alias Explorer.Chain.UserOperation alias Explorer.Account.{TagAddress, TagTransaction, WatchlistAddress} alias Explorer.Chain.Beacon.Reader, as: BeaconReader alias Explorer.Chain.Block.Reward @@ -459,6 +461,25 @@ defmodule BlockScoutWeb.Chain do [paging_options: %{@default_paging_options | key: {token_contract_address_hash, token_type}}] end + # Clause for `Explorer.Chain.Stability.Validator`, + # returned by `BlockScoutWeb.API.V2.ValidatorController.stability_validators_list/2` (`/api/v2/validators/stability`) + def paging_options(%{ + "state" => state, + "address_hash" => address_hash_string, + "blocks_validated" => blocks_validated_string + }) do + [ + paging_options: %{ + @default_paging_options + | key: %{ + address_hash: parse_address_hash(address_hash_string), + blocks_validated: parse_integer(blocks_validated_string), + state: if(state in PagingHelper.allowed_stability_validators_states(), do: state) + } + } + ] + end + def paging_options(_params), do: [paging_options: @default_paging_options] def put_key_value_to_paging_options([paging_options: paging_options], key, value) do @@ -541,6 +562,13 @@ defmodule BlockScoutWeb.Chain do end end + defp parse_address_hash(address_hash_string) do + case Hash.Address.cast(address_hash_string) do + {:ok, address_hash} -> address_hash + _ -> nil + end + end + defp paging_params({%Address{hash: hash, fetched_coin_balance: fetched_coin_balance}, _}) do %{"hash" => hash, "fetched_coin_balance" => Decimal.to_string(fetched_coin_balance.value)} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/validator_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/validator_controller.ex new file mode 100644 index 000000000000..4c7883502814 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/validator_controller.ex @@ -0,0 +1,83 @@ +defmodule BlockScoutWeb.API.V2.ValidatorController do + use BlockScoutWeb, :controller + + alias Explorer.Chain.Cache.StabilityValidatorsCounters + alias Explorer.Chain.Stability.Validator, as: ValidatorStability + + import BlockScoutWeb.PagingHelper, + only: [ + delete_parameters_from_next_page_params: 1, + stability_validators_state_options: 1, + validators_stability_sorting: 1 + ] + + import BlockScoutWeb.Chain, + only: [ + split_list_by_page: 1, + paging_options: 1, + next_page_params: 4 + ] + + @api_true api?: true + + @doc """ + Function to handle GET requests to `/api/v2/validators/stability` endpoint. + """ + @spec stability_validators_list(Plug.Conn.t(), map()) :: Plug.Conn.t() + def stability_validators_list(conn, params) do + options = + [ + necessity_by_association: %{ + :address => :optional + } + ] + |> Keyword.merge(@api_true) + |> Keyword.merge(paging_options(params)) + |> Keyword.merge(validators_stability_sorting(params)) + |> Keyword.merge(stability_validators_state_options(params)) + + {validators, next_page} = options |> ValidatorStability.get_paginated_validators() |> split_list_by_page() + + next_page_params = + next_page + |> next_page_params( + validators, + delete_parameters_from_next_page_params(params), + &ValidatorStability.next_page_params/1 + ) + + conn + |> render(:stability_validators, %{validators: validators, next_page_params: next_page_params}) + end + + @doc """ + Function to handle GET requests to `/api/v2/validators/stability/counters` endpoint. + """ + @spec stability_validators_counters(Plug.Conn.t(), map()) :: Plug.Conn.t() + def stability_validators_counters(conn, _params) do + %{ + validators_counter: validators_counter, + new_validators_counter: new_validators_counter, + active_validators_counter: active_validators_counter + } = StabilityValidatorsCounters.get_counters(@api_true) + + conn + |> json(%{ + validators_counter: validators_counter, + new_validators_counter_24h: new_validators_counter, + active_validators_counter: active_validators_counter, + active_validators_percentage: + calculate_active_validators_percentage(active_validators_counter, validators_counter) + }) + end + + defp calculate_active_validators_percentage(active_validators_counter, validators_counter) do + if Decimal.compare(validators_counter, Decimal.new(0)) == :gt do + active_validators_counter + |> Decimal.div(validators_counter) + |> Decimal.mult(100) + |> Decimal.to_float() + |> Float.floor(2) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex index 1a054b091e72..72914f1f5d34 100644 --- a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex @@ -3,6 +3,7 @@ defmodule BlockScoutWeb.PagingHelper do Helper for fetching filters and other url query parameters """ import Explorer.Chain, only: [string_to_transaction_hash: 1] + alias Explorer.Chain.Stability.Validator, as: ValidatorStability alias Explorer.Chain.Transaction alias Explorer.{Helper, PagingOptions, SortingHelper} @@ -34,6 +35,9 @@ defmodule BlockScoutWeb.PagingHelper do @allowed_token_transfer_type_labels ["ERC-20", "ERC-721", "ERC-1155"] @allowed_nft_token_type_labels ["ERC-721", "ERC-1155"] @allowed_chain_id [1, 56, 99] + @allowed_stability_validators_states ["active", "probation", "inactive"] + + def allowed_stability_validators_states, do: @allowed_stability_validators_states def paging_options(%{"block_number" => block_number_string, "index" => index_string}, [:validated | _]) do with {block_number, ""} <- Integer.parse(block_number_string), @@ -57,6 +61,13 @@ defmodule BlockScoutWeb.PagingHelper do def paging_options(_params, _filter), do: [paging_options: @default_paging_options] + @spec stability_validators_state_options(map()) :: [{:state, list()}, ...] + def stability_validators_state_options(%{"state_filter" => state}) do + [state: filters_to_list(state, @allowed_stability_validators_states, :downcase)] + end + + def stability_validators_state_options(_), do: [state: []] + @spec token_transfers_types_options(map()) :: [{:token_type, list}] def token_transfers_types_options(%{"type" => filters}) do [ @@ -78,7 +89,9 @@ defmodule BlockScoutWeb.PagingHelper do def nft_token_types_options(_), do: [token_type: []] - defp filters_to_list(filters, allowed), do: filters |> String.upcase() |> parse_filter(allowed) + defp filters_to_list(filters, allowed, variant \\ :upcase) + defp filters_to_list(filters, allowed, :downcase), do: filters |> String.downcase() |> parse_filter(allowed) + defp filters_to_list(filters, allowed, :upcase), do: filters |> String.upcase() |> parse_filter(allowed) # sobelow_skip ["DOS.StringToAtom"] def filter_options(%{"filter" => filter}, fallback) do @@ -188,7 +201,8 @@ defmodule BlockScoutWeb.PagingHelper do "filter", "q", "sort", - "order" + "order", + "state_filter" ]) end @@ -267,4 +281,26 @@ defmodule BlockScoutWeb.PagingHelper do do: [{:dynamic, :fee, :desc_nulls_last, Transaction.dynamic_fee()}] defp do_address_transaction_sorting(_, _), do: [] + + @spec validators_stability_sorting(%{required(String.t()) => String.t()}) :: [ + {:sorting, SortingHelper.sorting_params()} + ] + def validators_stability_sorting(%{"sort" => sort_field, "order" => order}) do + [sorting: do_validators_stability_sorting(sort_field, order)] + end + + def validators_stability_sorting(_), do: [] + + defp do_validators_stability_sorting("state", "asc"), do: [asc_nulls_first: :state] + defp do_validators_stability_sorting("state", "desc"), do: [desc_nulls_last: :state] + defp do_validators_stability_sorting("address_hash", "asc"), do: [asc_nulls_first: :address_hash] + defp do_validators_stability_sorting("address_hash", "desc"), do: [desc_nulls_last: :address_hash] + + defp do_validators_stability_sorting("blocks_validated", "asc"), + do: [{:dynamic, :blocks_validated, :asc_nulls_first, ValidatorStability.dynamic_validated_blocks()}] + + defp do_validators_stability_sorting("blocks_validated", "desc"), + do: [{:dynamic, :blocks_validated, :desc_nulls_last, ValidatorStability.dynamic_validated_blocks()}] + + defp do_validators_stability_sorting(_, _), do: [] end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/validator_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/validator_view.ex new file mode 100644 index 000000000000..e5b719557b95 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/validator_view.ex @@ -0,0 +1,17 @@ +defmodule BlockScoutWeb.API.V2.ValidatorView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.API.V2.Helper + + def render("stability_validators.json", %{validators: validators, next_page_params: next_page_params}) do + %{"items" => Enum.map(validators, &prepare_validator(&1)), "next_page_params" => next_page_params} + end + + defp prepare_validator(validator) do + %{ + "address" => Helper.address_with_info(nil, validator.address, validator.address_hash, true), + "state" => validator.state, + "blocks_validated_count" => validator.blocks_validated + } + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/validator_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/validator_controller_test.exs new file mode 100644 index 000000000000..9981c830ca2f --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/validator_controller_test.exs @@ -0,0 +1,205 @@ +defmodule BlockScoutWeb.API.V2.ValidatorControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.Chain.Address + alias Explorer.Chain.Stability.Validator, as: ValidatorStability + alias Explorer.Chain.Cache.StabilityValidatorsCounters + + if Application.compile_env(:explorer, :chain_type) == "stability" do + describe "/validators/stability" do + test "get paginated list of the validators", %{conn: conn} do + validators = + insert_list(51, :validator_stability) + |> Enum.sort_by( + fn validator -> + {Keyword.fetch!(ValidatorStability.state_enum(), validator.state), validator.address_hash.bytes} + end, + :desc + ) + + request = get(conn, "/api/v2/validators/stability") + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/validators/stability", response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, validators) + end + + test "sort by blocks_validated asc", %{conn: conn} do + validators = + for _ <- 0..50 do + validator = insert(:validator_stability) + blocks_count = Enum.random(0..50) + + _ = + for _ <- 0..blocks_count do + insert(:block, miner_hash: validator.address_hash, miner: nil) + end + + {validator, blocks_count} + end + |> Enum.sort(&compare_default_sorting_for_asc/2) + + init_params = %{"sort" => "blocks_validated", "order" => "asc"} + request = get(conn, "/api/v2/validators/stability", init_params) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/validators/stability", Map.merge(init_params, response["next_page_params"])) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, validators) + end + + test "sort by blocks_validated desc", %{conn: conn} do + validators = + for _ <- 0..50 do + validator = insert(:validator_stability) + blocks_count = Enum.random(0..50) + + _ = + for _ <- 0..blocks_count do + insert(:block, miner_hash: validator.address_hash, miner: nil) + end + + {validator, blocks_count} + end + |> Enum.sort(&compare_default_sorting_for_desc/2) + + init_params = %{"sort" => "blocks_validated", "order" => "desc"} + request = get(conn, "/api/v2/validators/stability", init_params) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/validators/stability", Map.merge(init_params, response["next_page_params"])) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, validators) + end + + test "state_filter=probation", %{conn: conn} do + insert_list(51, :validator_stability, state: Enum.random([:active, :inactive])) + + validators = + insert_list(51, :validator_stability, state: :probation) + |> Enum.sort_by( + fn validator -> + {Keyword.fetch!(ValidatorStability.state_enum(), validator.state), validator.address_hash.bytes} + end, + :desc + ) + + init_params = %{"state_filter" => "probation"} + + request = get(conn, "/api/v2/validators/stability", init_params) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/validators/stability", Map.merge(init_params, response["next_page_params"])) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, validators) + end + end + + describe "/validators/stability/counters" do + test "get counters", %{conn: conn} do + _validator_active1 = + insert(:validator_stability, state: :active, inserted_at: DateTime.add(DateTime.utc_now(), -2, :day)) + + _validator_active2 = insert(:validator_stability, state: :active) + _validator_active3 = insert(:validator_stability, state: :active) + + _validator_inactive1 = + insert(:validator_stability, state: :inactive, inserted_at: DateTime.add(DateTime.utc_now(), -2, :day)) + + _validator_inactive2 = insert(:validator_stability, state: :inactive) + _validator_inactive3 = insert(:validator_stability, state: :inactive) + + _validator_probation1 = + insert(:validator_stability, state: :probation, inserted_at: DateTime.add(DateTime.utc_now(), -2, :day)) + + _validator_probation2 = insert(:validator_stability, state: :probation) + _validator_probation3 = insert(:validator_stability, state: :probation) + + StabilityValidatorsCounters.consolidate() + :timer.sleep(500) + + percentage = (3 / 9 * 100) |> Float.floor(2) + request = get(conn, "/api/v2/validators/stability/counters") + + assert %{ + "active_validators_counter" => "3", + "active_validators_percentage" => ^percentage, + "new_validators_counter_24h" => "6", + "validators_counter" => "9" + } = json_response(request, 200) + end + end + end + + defp compare_item(%ValidatorStability{} = validator, json) do + assert Address.checksum(validator.address_hash) == json["address"]["hash"] + assert to_string(validator.state) == json["state"] + end + + defp compare_item({%ValidatorStability{} = validator, count}, json) do + assert json["blocks_validated_count"] == count + 1 + assert compare_item(validator, json) + end + + defp check_paginated_response(first_page_resp, second_page_resp, list) do + assert Enum.count(first_page_resp["items"]) == 50 + assert first_page_resp["next_page_params"] != nil + compare_item(Enum.at(list, 50), Enum.at(first_page_resp["items"], 0)) + compare_item(Enum.at(list, 1), Enum.at(first_page_resp["items"], 49)) + + assert Enum.count(second_page_resp["items"]) == 1 + assert second_page_resp["next_page_params"] == nil + compare_item(Enum.at(list, 0), Enum.at(second_page_resp["items"], 0)) + end + + defp compare_default_sorting_for_asc({validator_1, blocks_count_1}, {validator_2, blocks_count_2}) do + case { + compare(blocks_count_1, blocks_count_2), + compare( + Keyword.fetch!(ValidatorStability.state_enum(), validator_1.state), + Keyword.fetch!(ValidatorStability.state_enum(), validator_2.state) + ), + compare(validator_1.address_hash.bytes, validator_2.address_hash.bytes) + } do + {:lt, _, _} -> false + {:eq, :lt, _} -> false + {:eq, :eq, :lt} -> false + _ -> true + end + end + + defp compare_default_sorting_for_desc({validator_1, blocks_count_1}, {validator_2, blocks_count_2}) do + case { + compare(blocks_count_1, blocks_count_2), + compare( + Keyword.fetch!(ValidatorStability.state_enum(), validator_1.state), + Keyword.fetch!(ValidatorStability.state_enum(), validator_2.state) + ), + compare(validator_1.address_hash.bytes, validator_2.address_hash.bytes) + } do + {:gt, _, _} -> false + {:eq, :lt, _} -> false + {:eq, :eq, :lt} -> false + _ -> true + end + end + + defp compare(a, b) do + cond do + a < b -> :lt + a > b -> :gt + true -> :eq + end + end +end diff --git a/apps/block_scout_web/test/test_helper.exs b/apps/block_scout_web/test/test_helper.exs index c14efe9d6378..0c0ba46a2fed 100644 --- a/apps/block_scout_web/test/test_helper.exs +++ b/apps/block_scout_web/test/test_helper.exs @@ -32,6 +32,7 @@ Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.RSK, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Shibarium, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Beacon, :manual) +Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Stability, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.BridgedTokens, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Filecoin, :manual) diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index dcc12a69bde8..2c1e69d061b6 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -76,6 +76,11 @@ config :explorer, Explorer.Chain.Cache.WithdrawalsSum, enable_consolidation: true, update_interval_in_milliseconds: update_interval_in_milliseconds_default +config :explorer, Explorer.Chain.Cache.StabilityValidatorsCounters, + enabled: true, + enable_consolidation: true, + update_interval_in_milliseconds: update_interval_in_milliseconds_default + config :explorer, Explorer.Chain.Cache.TransactionActionTokensData, enabled: true config :explorer, Explorer.Chain.Cache.TransactionActionUniswapPools, enabled: true diff --git a/apps/explorer/config/dev.exs b/apps/explorer/config/dev.exs index 85e792bd277d..993294035684 100644 --- a/apps/explorer/config/dev.exs +++ b/apps/explorer/config/dev.exs @@ -32,6 +32,8 @@ config :explorer, Explorer.Repo.BridgedTokens, timeout: :timer.seconds(80) config :explorer, Explorer.Repo.Filecoin, timeout: :timer.seconds(80) +config :explorer, Explorer.Repo.Stability, timeout: :timer.seconds(80) + config :explorer, Explorer.Tracer, env: "dev", disabled?: true config :logger, :explorer, diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs index 2b74469e83e1..4e4519adf99d 100644 --- a/apps/explorer/config/prod.exs +++ b/apps/explorer/config/prod.exs @@ -52,6 +52,10 @@ config :explorer, Explorer.Repo.Filecoin, prepare: :unnamed, timeout: :timer.seconds(60) +config :explorer, Explorer.Repo.Stability, + prepare: :unnamed, + timeout: :timer.seconds(60) + config :explorer, Explorer.Tracer, env: "production", disabled?: true config :logger, :explorer, diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index e815f2082917..d4130b4272e2 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -52,7 +52,8 @@ for repo <- [ Explorer.Repo.Shibarium, Explorer.Repo.Suave, Explorer.Repo.BridgedTokens, - Explorer.Repo.Filecoin + Explorer.Repo.Filecoin, + Explorer.Repo.Stability ] do config :explorer, repo, database: "explorer_test", @@ -84,3 +85,4 @@ config :explorer, Explorer.ExchangeRates.Source.TransactionAndLog, config :explorer, Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand, enabled: false config :explorer, Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand, enabled: false +config :explorer, Explorer.Tags.AddressTag.Cataloger, enabled: false diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index aac514bed79c..35e749c247a5 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -132,7 +132,8 @@ defmodule Explorer.Application do configure(Explorer.Migrator.AddressTokenBalanceTokenType), configure(Explorer.Migrator.SanitizeMissingBlockRanges), configure(Explorer.Migrator.SanitizeIncorrectNFTTokenTransfers), - configure(Explorer.Migrator.TokenTransferTokenType) + configure(Explorer.Migrator.TokenTransferTokenType), + configure_chain_type_dependent_process(Explorer.Chain.Cache.StabilityValidatorsCounters, "stability") ] |> List.flatten() @@ -150,7 +151,8 @@ defmodule Explorer.Application do Explorer.Repo.Shibarium, Explorer.Repo.Suave, Explorer.Repo.BridgedTokens, - Explorer.Repo.Filecoin + Explorer.Repo.Filecoin, + Explorer.Repo.Stability ] else [] @@ -177,6 +179,14 @@ defmodule Explorer.Application do end end + defp configure_chain_type_dependent_process(process, chain_type) do + if Application.get_env(:explorer, :chain_type) == chain_type do + process + else + [] + end + end + defp sc_microservice_configure(process) do if Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)[:eth_bytecode_db?] do process diff --git a/apps/explorer/lib/explorer/chain/cache/stability_validators_counters.ex b/apps/explorer/lib/explorer/chain/cache/stability_validators_counters.ex new file mode 100644 index 000000000000..1034465848c6 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/cache/stability_validators_counters.ex @@ -0,0 +1,105 @@ +defmodule Explorer.Chain.Cache.StabilityValidatorsCounters do + @moduledoc """ + Counts and store counters of validators stability. + + It loads the count asynchronously and in a time interval of 30 minutes. + """ + + use GenServer + + alias Explorer.Chain + alias Explorer.Chain.Stability.Validator, as: ValidatorStability + + @validators_counter_key "stability_validators_counter" + @new_validators_counter_key "new_stability_validators_counter" + @active_validators_counter_key "active_stability_validators_counter" + + # It is undesirable to automatically start the consolidation in all environments. + # Consider the test environment: if the consolidation initiates but does not + # finish before a test ends, that test will fail. This way, hundreds of + # tests were failing before disabling the consolidation and the scheduler in + # the test env. + config = Application.compile_env(:explorer, __MODULE__) + @enable_consolidation Keyword.get(config, :enable_consolidation) + + @update_interval_in_milliseconds Keyword.get(config, :update_interval_in_milliseconds) + + @doc """ + Starts a process to periodically update validators stability counters + """ + @spec start_link(term()) :: GenServer.on_start() + def start_link(_) do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + @impl true + def init(_args) do + {:ok, %{consolidate?: @enable_consolidation}, {:continue, :ok}} + end + + defp schedule_next_consolidation do + Process.send_after(self(), :consolidate, @update_interval_in_milliseconds) + end + + @impl true + def handle_continue(:ok, %{consolidate?: true} = state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @impl true + def handle_continue(:ok, state) do + {:noreply, state} + end + + @impl true + def handle_info(:consolidate, state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @doc """ + Fetches values for a stability validators counters from the `last_fetched_counters` table. + """ + @spec get_counters(Keyword.t()) :: map() + def get_counters(options) do + %{ + validators_counter: Chain.get_last_fetched_counter(@validators_counter_key, options), + new_validators_counter: Chain.get_last_fetched_counter(@new_validators_counter_key, options), + active_validators_counter: Chain.get_last_fetched_counter(@active_validators_counter_key, options) + } + end + + @doc """ + Consolidates the info by populating the `last_fetched_counters` table with the current database information. + """ + @spec consolidate() :: any() + def consolidate do + tasks = [ + Task.async(fn -> ValidatorStability.count_validators() end), + Task.async(fn -> ValidatorStability.count_new_validators() end), + Task.async(fn -> ValidatorStability.count_active_validators() end) + ] + + [validators_counter, new_validators_counter, active_validators_counter] = Task.await_many(tasks, :infinity) + + Chain.upsert_last_fetched_counter(%{ + counter_type: @validators_counter_key, + value: validators_counter + }) + + Chain.upsert_last_fetched_counter(%{ + counter_type: @new_validators_counter_key, + value: new_validators_counter + }) + + Chain.upsert_last_fetched_counter(%{ + counter_type: @active_validators_counter_key, + value: active_validators_counter + }) + end +end diff --git a/apps/explorer/lib/explorer/chain/stability/validator.ex b/apps/explorer/lib/explorer/chain/stability/validator.ex new file mode 100644 index 000000000000..309d95814bd8 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/stability/validator.ex @@ -0,0 +1,288 @@ +defmodule Explorer.Chain.Stability.Validator do + @moduledoc """ + Stability validators + """ + + use Explorer.Schema + + alias Explorer.Chain.{Address, Import} + alias Explorer.Chain.Hash.Address, as: HashAddress + alias Explorer.{Chain, Repo, SortingHelper} + alias Explorer.SmartContract.Reader + + require Logger + + @default_sorting [ + asc: :state, + asc: :address_hash + ] + + @state_enum [active: 0, probation: 1, inactive: 2] + + @primary_key false + typed_schema "validators_stability" do + field(:address_hash, HashAddress, primary_key: true) + field(:state, Ecto.Enum, values: @state_enum) + field(:blocks_validated, :integer, virtual: true) + + has_one(:address, Address, foreign_key: :hash, references: :address_hash) + timestamps() + end + + @required_attrs ~w(address_hash)a + @optional_attrs ~w(state)a + def changeset(%__MODULE__{} = validator, attrs) do + validator + |> cast(attrs, @required_attrs ++ @optional_attrs) + |> validate_required(@required_attrs) + |> unique_constraint(:address_hash) + end + + @doc """ + Get validators list. + Keyword could contain: + - paging_options + - necessity_by_association + - sorting (supported by `Explorer.SortingHelper` module) + - state (one of `@state_enum`) + """ + @spec get_paginated_validators(keyword()) :: [t()] + def get_paginated_validators(options \\ []) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + sorting = Keyword.get(options, :sorting, []) + states = Keyword.get(options, :state, []) + + __MODULE__ + |> apply_filter_by_state(states) + |> select_merge([vs], %{ + blocks_validated: + fragment( + "SELECT count(*) FROM blocks WHERE miner_hash = ?", + vs.address_hash + ) + }) + |> Chain.join_associations(necessity_by_association) + |> SortingHelper.apply_sorting(sorting, @default_sorting) + |> SortingHelper.page_with_sorting(paging_options, sorting, @default_sorting) + |> Chain.select_repo(options).all() + end + + defp apply_filter_by_state(query, []), do: query + + defp apply_filter_by_state(query, states) do + query + |> where([vs], vs.state in ^states) + end + + @doc """ + Get all validators + """ + @spec get_all_validators(keyword()) :: [t()] + def get_all_validators(options \\ []) do + __MODULE__ + |> Chain.select_repo(options).all() + end + + @get_active_validator_list_abi %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [%{"type" => "address[]", "name" => "", "internalType" => "address[]"}], + "name" => "getActiveValidatorList", + "inputs" => [] + } + + @get_validator_list_abi %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [%{"type" => "address[]", "name" => "", "internalType" => "address[]"}], + "name" => "getValidatorList", + "inputs" => [] + } + + @get_validator_missing_blocks_abi %{ + "inputs" => [ + %{ + "internalType" => "address", + "name" => "validator", + "type" => "address" + } + ], + "name" => "getValidatorMissingBlocks", + "outputs" => [ + %{ + "internalType" => "uint256", + "name" => "", + "type" => "uint256" + } + ], + "stateMutability" => "view", + "type" => "function" + } + + @get_active_validator_list_method_id "a5aa7380" + @get_validator_list_method_id "e35c0f7d" + @get_validator_missing_blocks_method_id "41ee9a53" + + @stability_validator_controller_contract "0x0000000000000000000000000000000000000805" + + @doc """ + Do batch eth_call of `getValidatorList` and `getActiveValidatorList` methods to `@stability_validator_controller_contract`. + Returns a map with two lists: `active` and `all`, or nil if error. + """ + @spec fetch_validators_lists :: nil | %{active: list(binary()), all: list(binary())} + def fetch_validators_lists do + abi = [@get_active_validator_list_abi, @get_validator_list_abi] + params = %{@get_validator_list_method_id => [], @get_active_validator_list_method_id => []} + + case Reader.query_contract(@stability_validator_controller_contract, abi, params, false) do + %{ + @get_active_validator_list_method_id => {:ok, [active_validators_list]}, + @get_validator_list_method_id => {:ok, [validators_list]} + } -> + %{active: active_validators_list, all: validators_list} + + error -> + Logger.warn(fn -> ["Error on getting validator lists: #{inspect(error)}"] end) + nil + end + end + + @doc """ + Do batch eth_call of `getValidatorMissingBlocks` method to #{@stability_validator_controller_contract}. + Accept: list of validator address hashes + Returns a map: validator_address_hash => missing_blocks_number + """ + @spec fetch_missing_blocks_numbers(list(binary())) :: map() + def fetch_missing_blocks_numbers(validators_address_hashes) do + validators_address_hashes + |> Enum.map(&format_request_missing_blocks_number/1) + |> Reader.query_contracts([@get_validator_missing_blocks_abi]) + |> Enum.zip_reduce(validators_address_hashes, %{}, fn response, address_hash, acc -> + result = + case format_missing_blocks_result(response) do + {:error, message} -> + Logger.warn(fn -> ["Error on getValidatorMissingBlocks for #{validators_address_hashes}: #{message}"] end) + nil + + amount -> + amount + end + + Map.put(acc, address_hash, result) + end) + end + + defp format_missing_blocks_result({:ok, [amount]}) do + amount + end + + defp format_missing_blocks_result({:error, error_message}) do + {:error, error_message} + end + + defp format_request_missing_blocks_number(address_hash) do + %{ + contract_address: @stability_validator_controller_contract, + method_id: @get_validator_missing_blocks_method_id, + args: [address_hash] + } + end + + @doc """ + Convert missing block number to state + """ + @spec missing_block_number_to_state(integer()) :: atom() + def missing_block_number_to_state(integer) when integer > 0, do: :probation + def missing_block_number_to_state(integer) when integer == 0, do: :active + def missing_block_number_to_state(_), do: nil + + @doc """ + Delete validators by address hashes + """ + @spec delete_validators_by_address_hashes([binary() | HashAddress.t()]) :: {non_neg_integer(), nil | []} | :ignore + def delete_validators_by_address_hashes(list) when is_list(list) and length(list) > 0 do + __MODULE__ + |> where([vs], vs.address_hash in ^list) + |> Repo.delete_all() + end + + def delete_validators_by_address_hashes(_), do: :ignore + + @doc """ + Insert validators + """ + @spec insert_validators([map()]) :: {non_neg_integer(), nil | []} + def insert_validators(validators) do + Repo.insert_all(__MODULE__, validators, + on_conflict: {:replace_all_except, [:inserted_at]}, + conflict_target: [:address_hash] + ) + end + + @doc """ + Append timestamps (:inserted_at, :updated_at) + """ + @spec append_timestamps(map()) :: map() + def append_timestamps(validator) do + Map.merge(validator, Import.timestamps()) + end + + @doc """ + Derive next page params from %Explorer.Chain.Stability.Validator{} + """ + @spec next_page_params(t()) :: map() + def next_page_params(%__MODULE__{state: state, address_hash: address_hash, blocks_validated: blocks_validated}) do + %{"state" => state, "address_hash" => address_hash, "blocks_validated" => blocks_validated} + end + + @doc """ + Returns state enum + """ + @spec state_enum() :: Keyword.t() + def state_enum, do: @state_enum + + @doc """ + Returns dynamic query for validated blocks count. Needed for SortingHelper + """ + @spec dynamic_validated_blocks() :: Ecto.Query.dynamic_expr() + def dynamic_validated_blocks do + dynamic( + [vs], + fragment( + "SELECT count(*) FROM blocks WHERE miner_hash = ?", + vs.address_hash + ) + ) + end + + @doc """ + Returns total count of validators. + """ + @spec count_validators() :: integer() + def count_validators do + Repo.aggregate(__MODULE__, :count, :address_hash) + end + + @doc """ + Returns count of new validators (inserted withing last 24h). + """ + @spec count_new_validators() :: integer() + def count_new_validators do + __MODULE__ + |> where([vs], vs.inserted_at >= ago(1, "day")) + |> Repo.aggregate(:count, :address_hash) + end + + @doc """ + Returns count of active validators. + """ + @spec count_active_validators() :: integer() + def count_active_validators do + __MODULE__ + |> where([vs], vs.state == :active) + |> Repo.aggregate(:count, :address_hash) + end +end diff --git a/apps/explorer/lib/explorer/repo.ex b/apps/explorer/lib/explorer/repo.ex index 2e457379d8b5..9dbbb9f9d40f 100644 --- a/apps/explorer/lib/explorer/repo.ex +++ b/apps/explorer/lib/explorer/repo.ex @@ -240,4 +240,14 @@ defmodule Explorer.Repo do ConfigHelper.init_repo_module(__MODULE__, opts) end end + + defmodule Stability do + use Ecto.Repo, + otp_app: :explorer, + adapter: Ecto.Adapters.Postgres + + def init(_, opts) do + ConfigHelper.init_repo_module(__MODULE__, opts) + end + end end diff --git a/apps/explorer/priv/stability/migrations/20240203091010_add_stability_validators.exs b/apps/explorer/priv/stability/migrations/20240203091010_add_stability_validators.exs new file mode 100644 index 000000000000..67e5580fef13 --- /dev/null +++ b/apps/explorer/priv/stability/migrations/20240203091010_add_stability_validators.exs @@ -0,0 +1,14 @@ +defmodule Explorer.Repo.Stability.Migrations.AddStabilityValidators do + use Ecto.Migration + + def change do + create table(:validators_stability, primary_key: false) do + add(:address_hash, :bytea, null: false, primary_key: true) + add(:state, :integer, default: 0) + + timestamps() + end + + create_if_not_exists(index(:validators_stability, ["state ASC", "address_hash ASC"])) + end +end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 7474c1c40e07..37302050684f 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -23,6 +23,7 @@ defmodule Explorer.Factory do alias Explorer.Admin.Administrator alias Explorer.Chain.Beacon.{Blob, BlobTransaction} alias Explorer.Chain.Block.{EmissionReward, Range, Reward} + alias Explorer.Chain.Stability.Validator, as: ValidatorStability alias Explorer.Chain.{ Address, @@ -1116,4 +1117,13 @@ defmodule Explorer.Factory do end def random_bool, do: Enum.random([true, false]) + + def validator_stability_factory do + address = insert(:address) + + %ValidatorStability{ + address_hash: address.hash, + state: Enum.random(0..2) + } + end end diff --git a/apps/explorer/test/test_helper.exs b/apps/explorer/test/test_helper.exs index 882fb7a26c1a..90d21435e5d6 100644 --- a/apps/explorer/test/test_helper.exs +++ b/apps/explorer/test/test_helper.exs @@ -21,6 +21,7 @@ Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Beacon, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.BridgedTokens, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Filecoin, :auto) +Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Stability, :auto) Mox.defmock(Explorer.ExchangeRates.Source.TestSource, for: Explorer.ExchangeRates.Source) Mox.defmock(Explorer.Market.History.Source.Price.TestSource, for: Explorer.Market.History.Source.Price) diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index 61e86cf1220a..7f1219ef2c96 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -130,14 +130,18 @@ defmodule Indexer.Block.Realtime.Fetcher do new_previous_number = case EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments) do {:ok, number} when is_nil(previous_number) or number != previous_number -> - if abnormal_gap?(number, previous_number) do - new_number = max(number, previous_number) - start_fetch_and_import(new_number, block_fetcher, previous_number) - new_number - else - start_fetch_and_import(number, block_fetcher, previous_number) - number - end + number = + if abnormal_gap?(number, previous_number) do + new_number = max(number, previous_number) + start_fetch_and_import(new_number, block_fetcher, previous_number) + new_number + else + start_fetch_and_import(number, block_fetcher, previous_number) + number + end + + fetch_validators_async() + number _ -> previous_number @@ -158,6 +162,16 @@ defmodule Indexer.Block.Realtime.Fetcher do {:noreply, state} end + if Application.compile_env(:explorer, :chain_type) == "stability" do + defp fetch_validators_async do + GenServer.cast(Indexer.Fetcher.Stability.Validator, :update_validators_list) + end + else + defp fetch_validators_async do + :ignore + end + end + defp subscribe_to_new_heads(%__MODULE__{subscription: nil} = state, subscribe_named_arguments) when is_list(subscribe_named_arguments) do case EthereumJSONRPC.subscribe("newHeads", subscribe_named_arguments) do diff --git a/apps/indexer/lib/indexer/fetcher/stability/validator.ex b/apps/indexer/lib/indexer/fetcher/stability/validator.ex new file mode 100644 index 000000000000..70851e8d8a20 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/stability/validator.ex @@ -0,0 +1,70 @@ +defmodule Indexer.Fetcher.Stability.Validator do + @moduledoc """ + GenServer responsible for updating the list of stability validators in the database. + """ + use GenServer + + alias Explorer.Chain.Hash.Address, as: AddressHash + alias Explorer.Chain.Stability.Validator, as: ValidatorStability + + def start_link(_) do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + def init(state) do + GenServer.cast(__MODULE__, :update_validators_list) + + {:ok, state} + end + + def handle_cast(:update_validators_list, state) do + validators_from_db = ValidatorStability.get_all_validators() + + case ValidatorStability.fetch_validators_lists() do + %{active: active_validator_addresses_list, all: validator_addresses_list} -> + validators_map = Enum.reduce(validator_addresses_list, %{}, fn address, map -> Map.put(map, address, true) end) + + active_validators_map = + Enum.reduce(active_validator_addresses_list, %{}, fn address, map -> Map.put(map, address, true) end) + + address_hashes_to_drop_from_db = + Enum.flat_map(validators_from_db, fn validator -> + (is_nil(validators_map[validator.address_hash.bytes]) && [validator.address_hash]) || [] + end) + + grouped = + Enum.group_by(validator_addresses_list, fn validator_address -> active_validators_map[validator_address] end) + + inactive = + Enum.map(grouped[nil] || [], fn address_hash -> + {:ok, address_hash} = AddressHash.load(address_hash) + + %{address_hash: address_hash, state: :inactive} |> ValidatorStability.append_timestamps() + end) + + validators_to_missing_blocks_numbers = ValidatorStability.fetch_missing_blocks_numbers(grouped[true] || []) + + active = + Enum.map(grouped[true] || [], fn address_hash_init -> + {:ok, address_hash} = AddressHash.load(address_hash_init) + + %{ + address_hash: address_hash, + state: + ValidatorStability.missing_block_number_to_state( + validators_to_missing_blocks_numbers[address_hash_init] + ) + } + |> ValidatorStability.append_timestamps() + end) + + ValidatorStability.insert_validators(active ++ inactive) + ValidatorStability.delete_validators_by_address_hashes(address_hashes_to_drop_from_db) + + _ -> + nil + end + + {:noreply, state} + end +end diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index b1018baa39a4..6d585a2f741c 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -20,6 +20,7 @@ defmodule Indexer.Supervisor do alias Indexer.Block.Realtime, as: BlockRealtime alias Indexer.Fetcher.CoinBalance.Catchup, as: CoinBalanceCatchup alias Indexer.Fetcher.CoinBalance.Realtime, as: CoinBalanceRealtime + alias Indexer.Fetcher.Stability.Validator, as: ValidatorStability alias Indexer.Fetcher.TokenInstance.LegacySanitize, as: TokenInstanceLegacySanitize alias Indexer.Fetcher.TokenInstance.Realtime, as: TokenInstanceRealtime alias Indexer.Fetcher.TokenInstance.Retry, as: TokenInstanceRetry @@ -203,7 +204,10 @@ defmodule Indexer.Supervisor do ] |> List.flatten() - all_fetchers = maybe_add_bridged_tokens_fetchers(basic_fetchers) + all_fetchers = + basic_fetchers + |> maybe_add_bridged_tokens_fetchers() + |> add_chain_type_dependent_fetchers() Supervisor.init( all_fetchers, @@ -228,6 +232,16 @@ defmodule Indexer.Supervisor do end end + defp add_chain_type_dependent_fetchers(fetchers) do + case Application.get_env(:explorer, :chain_type) do + "stability" -> + [{ValidatorStability, []} | fetchers] + + _ -> + fetchers + end + end + defp configure(process, opts) do if Application.get_env(:indexer, process)[:enabled] do [{process, opts}] diff --git a/apps/indexer/test/indexer/fetcher/stability/validator_test.exs b/apps/indexer/test/indexer/fetcher/stability/validator_test.exs new file mode 100644 index 000000000000..8aae393a347a --- /dev/null +++ b/apps/indexer/test/indexer/fetcher/stability/validator_test.exs @@ -0,0 +1,270 @@ +defmodule Indexer.Fetcher.Stability.ValidatorTest do + use EthereumJSONRPC.Case + use Explorer.DataCase + + import Mox + + alias Explorer.Chain.Stability.Validator, as: ValidatorStability + alias EthereumJSONRPC.Encoder + + setup :verify_on_exit! + setup :set_mox_global + + @accepts_list_of_addresses %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [%{"type" => "address[]", "name" => "", "internalType" => "address[]"}], + "name" => "getActiveValidatorList", + "inputs" => [%{"type" => "address[]", "name" => "", "internalType" => "address[]"}] + } + + @accepts_integer %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [ + %{ + "internalType" => "uint256", + "name" => "", + "type" => "uint256" + } + ], + "name" => "getActiveValidatorList", + "inputs" => [ + %{ + "internalType" => "uint256", + "name" => "", + "type" => "uint256" + } + ] + } + + if Application.compile_env(:explorer, :chain_type) == "stability" do + describe "check update_validators_list" do + test "deletes absent validators" do + _validator = insert(:validator_stability) + _validator_active = insert(:validator_stability, state: :active) + _validator_inactive = insert(:validator_stability, state: :inactive) + _validator_probation = insert(:validator_stability, state: :probation) + + start_supervised!({Indexer.Fetcher.Stability.Validator, name: Indexer.Fetcher.Stability.Validator}) + + EthereumJSONRPC.Mox + |> expect(:json_rpc, 1, fn + [ + %{ + id: id_1, + jsonrpc: "2.0", + method: "eth_call", + params: [%{data: "0xa5aa7380", to: "0x0000000000000000000000000000000000000805"}, "latest"] + }, + %{ + id: id_2, + jsonrpc: "2.0", + method: "eth_call", + params: [%{data: "0xe35c0f7d", to: "0x0000000000000000000000000000000000000805"}, "latest"] + } + ], + _ -> + <<"0x", _method_id::binary-size(8), result::binary>> = + [@accepts_list_of_addresses] + |> ABI.parse_specification() + |> Enum.at(0) + |> Encoder.encode_function_call([[]]) + + {:ok, + [ + %{ + id: id_1, + jsonrpc: "2.0", + result: "0x" <> result + }, + %{ + id: id_2, + jsonrpc: "2.0", + result: "0x" <> result + } + ]} + end) + + :timer.sleep(100) + assert ValidatorStability.get_all_validators() == [] + end + + test "updates validators" do + validator_active1 = insert(:validator_stability, state: :active) + validator_active2 = insert(:validator_stability, state: :active) + _validator_active3 = insert(:validator_stability, state: :active) + + validator_inactive1 = insert(:validator_stability, state: :inactive) + validator_inactive2 = insert(:validator_stability, state: :inactive) + _validator_inactive3 = insert(:validator_stability, state: :inactive) + + validator_probation1 = insert(:validator_stability, state: :probation) + validator_probation2 = insert(:validator_stability, state: :probation) + _validator_probation3 = insert(:validator_stability, state: :probation) + + start_supervised!({Indexer.Fetcher.Stability.Validator, name: Indexer.Fetcher.Stability.Validator}) + + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn + [ + %{ + id: id_1, + jsonrpc: "2.0", + method: "eth_call", + params: [%{data: "0xa5aa7380", to: "0x0000000000000000000000000000000000000805"}, "latest"] + }, + %{ + id: id_2, + jsonrpc: "2.0", + method: "eth_call", + params: [%{data: "0xe35c0f7d", to: "0x0000000000000000000000000000000000000805"}, "latest"] + } + ], + _ -> + <<"0x", _method_id::binary-size(8), result_all::binary>> = + [@accepts_list_of_addresses] + |> ABI.parse_specification() + |> Enum.at(0) + |> Encoder.encode_function_call([ + [ + validator_active1.address_hash.bytes, + validator_active2.address_hash.bytes, + validator_inactive1.address_hash.bytes, + validator_inactive2.address_hash.bytes, + validator_probation1.address_hash.bytes, + validator_probation2.address_hash.bytes + ] + ]) + + <<"0x", _method_id::binary-size(8), result_active::binary>> = + [@accepts_list_of_addresses] + |> ABI.parse_specification() + |> Enum.at(0) + |> Encoder.encode_function_call([ + [ + validator_active1.address_hash.bytes, + validator_inactive1.address_hash.bytes, + validator_probation1.address_hash.bytes + ] + ]) + + {:ok, + [ + %{ + id: id_1, + jsonrpc: "2.0", + result: "0x" <> result_active + }, + %{ + id: id_2, + jsonrpc: "2.0", + result: "0x" <> result_all + } + ]} + end) + + "0x" <> address_1 = to_string(validator_active1.address_hash) + "0x" <> address_2 = to_string(validator_inactive1.address_hash) + "0x" <> address_3 = to_string(validator_probation1.address_hash) + + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn + [ + %{ + id: id_1, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: "0x41ee9a53000000000000000000000000" <> ^address_1, + to: "0x0000000000000000000000000000000000000805" + }, + "latest" + ] + }, + %{ + id: id_2, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: "0x41ee9a53000000000000000000000000" <> ^address_2, + to: "0x0000000000000000000000000000000000000805" + }, + "latest" + ] + }, + %{ + id: id_3, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: "0x41ee9a53000000000000000000000000" <> ^address_3, + to: "0x0000000000000000000000000000000000000805" + }, + "latest" + ] + } + ], + _ -> + <<"0x", _method_id::binary-size(8), result_1::binary>> = + [@accepts_integer] + |> ABI.parse_specification() + |> Enum.at(0) + |> Encoder.encode_function_call([10]) + + <<"0x", _method_id::binary-size(8), result_2::binary>> = + [@accepts_integer] + |> ABI.parse_specification() + |> Enum.at(0) + |> Encoder.encode_function_call([1]) + + <<"0x", _method_id::binary-size(8), result_3::binary>> = + [@accepts_integer] + |> ABI.parse_specification() + |> Enum.at(0) + |> Encoder.encode_function_call([0]) + + {:ok, + [ + %{ + id: id_1, + jsonrpc: "2.0", + result: "0x" <> result_1 + }, + %{ + id: id_2, + jsonrpc: "2.0", + result: "0x" <> result_2 + }, + %{ + id: id_3, + jsonrpc: "2.0", + result: "0x" <> result_3 + } + ]} + end) + + :timer.sleep(100) + validators = ValidatorStability.get_all_validators() + + assert Enum.count(validators) == 6 + + map = + Enum.reduce(validators, %{}, fn validator, map -> Map.put(map, validator.address_hash.bytes, validator) end) + + assert %ValidatorStability{state: :inactive} = map[validator_active2.address_hash.bytes] + assert %ValidatorStability{state: :inactive} = map[validator_inactive2.address_hash.bytes] + assert %ValidatorStability{state: :inactive} = map[validator_probation2.address_hash.bytes] + + assert %ValidatorStability{state: :probation} = map[validator_active1.address_hash.bytes] + assert %ValidatorStability{state: :probation} = map[validator_inactive1.address_hash.bytes] + assert %ValidatorStability{state: :active} = map[validator_probation1.address_hash.bytes] + end + end + end +end diff --git a/config/config_helper.exs b/config/config_helper.exs index a5c0a2e67fbf..5c2eab9dd5e6 100644 --- a/config/config_helper.exs +++ b/config/config_helper.exs @@ -17,6 +17,7 @@ defmodule ConfigHelper do "shibarium" -> base_repos ++ [Explorer.Repo.Shibarium] "suave" -> base_repos ++ [Explorer.Repo.Suave] "filecoin" -> base_repos ++ [Explorer.Repo.Filecoin] + "stability" -> base_repos ++ [Explorer.Repo.Stability] _ -> base_repos end diff --git a/config/runtime/dev.exs b/config/runtime/dev.exs index 12b0d3bde838..d889a4bbd6dd 100644 --- a/config/runtime/dev.exs +++ b/config/runtime/dev.exs @@ -147,6 +147,15 @@ config :explorer, Explorer.Repo.Filecoin, url: System.get_env("DATABASE_URL"), pool_size: 1 +# Configures Stability database +config :explorer, Explorer.Repo.Stability, + database: database, + hostname: hostname, + url: System.get_env("DATABASE_URL"), + # actually this repo is not started, and its pool size remains unused. + # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type + pool_size: 1 + variant = Variant.get() Code.require_file("#{variant}.exs", "apps/explorer/config/dev") diff --git a/config/runtime/prod.exs b/config/runtime/prod.exs index abf8c2137dc4..3fc64ea80b1b 100644 --- a/config/runtime/prod.exs +++ b/config/runtime/prod.exs @@ -113,6 +113,14 @@ config :explorer, Explorer.Repo.Filecoin, pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() +# Configures Stability database +config :explorer, Explorer.Repo.Stability, + url: System.get_env("DATABASE_URL"), + # actually this repo is not started, and its pool size remains unused. + # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type + pool_size: 1, + ssl: ExplorerConfigHelper.ssl_enabled?() + variant = Variant.get() Code.require_file("#{variant}.exs", "apps/explorer/config/prod") From a14630855c888f76196e65848cc88d715ae3f761 Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Mon, 4 Mar 2024 21:02:00 +0300 Subject: [PATCH 531/607] ETH JSON RPC extension (#9409) * Add new ETH JSON RPC methods * Add new ETH JSON RPC API methods; Add ETH_JSON_RPC_MAX_BATCH_SIZE env * Process review comments --- CHANGELOG.md | 1 + .../controllers/api/eth_rpc/eth_controller.ex | 23 +- .../block_scout_web/views/api/eth_rpc/view.ex | 16 + apps/explorer/lib/explorer/bloom_filter.ex | 78 ++ .../explorer/chain/cache/gas_price_oracle.ex | 4 +- apps/explorer/lib/explorer/chain/wei.ex | 6 +- apps/explorer/lib/explorer/eth_rpc.ex | 843 +++++++++++++++++- apps/explorer/lib/explorer/eth_rpc_helper.ex | 121 +++ .../test/explorer/bloom_filter_test.exs | 63 ++ config/runtime.exs | 3 +- 10 files changed, 1138 insertions(+), 20 deletions(-) create mode 100644 apps/explorer/lib/explorer/bloom_filter.ex create mode 100644 apps/explorer/lib/explorer/eth_rpc_helper.ex create mode 100644 apps/explorer/test/explorer/bloom_filter_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index dfeaa7ecacb5..4e3e97ece568 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [#9490](https://github.com/blockscout/blockscout/pull/9490) - Add blob transaction counter and filter in block view - [#9461](https://github.com/blockscout/blockscout/pull/9461) - Fetch blocks without internal transactions backwards - [#9460](https://github.com/blockscout/blockscout/pull/9460) - Optimism chain type +- [#9409](https://github.com/blockscout/blockscout/pull/9409) - ETH JSON RPC extension - [#9390](https://github.com/blockscout/blockscout/pull/9390) - Add stability validators - [#8702](https://github.com/blockscout/blockscout/pull/8702) - Add OP withdrawal status to transaction page in API - [#7200](https://github.com/blockscout/blockscout/pull/7200) - Add Optimism BedRock Deposits to the main page in API diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/eth_rpc/eth_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/eth_rpc/eth_controller.ex index 2a4287cf98d6..f67ef4afb9dd 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/eth_rpc/eth_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/eth_rpc/eth_controller.ex @@ -3,20 +3,29 @@ defmodule BlockScoutWeb.API.EthRPC.EthController do alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.API.EthRPC.View, as: EthRPCView + alias BlockScoutWeb.API.RPC.RPCView alias Explorer.EthRPC def eth_request(%{body_params: %{"_json" => requests}} = conn, _) when is_list(requests) do - case AccessHelper.check_rate_limit(conn) do - :ok -> - responses = EthRPC.responses(requests) + eth_json_rpc_max_batch_size = Application.get_env(:block_scout_web, :api_rate_limit)[:eth_json_rpc_max_batch_size] - conn - |> put_status(200) - |> put_view(EthRPCView) - |> render("responses.json", %{responses: responses}) + with :ok <- AccessHelper.check_rate_limit(conn), + {:batch_size, true} <- {:batch_size, Enum.count(requests) <= eth_json_rpc_max_batch_size} do + responses = EthRPC.responses(requests) + conn + |> put_status(200) + |> put_view(EthRPCView) + |> render("responses.json", %{responses: responses}) + else :rate_limit_reached -> AccessHelper.handle_rate_limit_deny(conn) + + {:batch_size, _} -> + conn + |> put_status(413) + |> put_view(RPCView) + |> render(:error, %{:error => "Payload Too Large. Max batch size is #{eth_json_rpc_max_batch_size}"}) end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/eth_rpc/view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/eth_rpc/view.ex index 16f85b303ff2..d2b29c0117c6 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/eth_rpc/view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/eth_rpc/view.ex @@ -59,6 +59,14 @@ defmodule BlockScoutWeb.API.EthRPC.View do """ end + def encode(%BlockScoutWeb.API.EthRPC.View{id: id, error: error}, _options) when is_map(error) do + error = Poison.encode!(error) + + """ + {"jsonrpc":"2.0","error": #{error},"id": #{id}} + """ + end + def encode(%BlockScoutWeb.API.EthRPC.View{id: id, error: error}, _options) do """ {"jsonrpc":"2.0","error": "#{error}","id": #{id}} @@ -75,6 +83,14 @@ defmodule BlockScoutWeb.API.EthRPC.View do """ end + def encode(%BlockScoutWeb.API.EthRPC.View{id: id, error: error}, _options) when is_map(error) do + error = Jason.encode!(error) + + """ + {"jsonrpc":"2.0","error": #{error},"id": #{id}} + """ + end + def encode(%BlockScoutWeb.API.EthRPC.View{id: id, error: error}, _options) do """ {"jsonrpc":"2.0","error": "#{error}","id": #{id}} diff --git a/apps/explorer/lib/explorer/bloom_filter.ex b/apps/explorer/lib/explorer/bloom_filter.ex new file mode 100644 index 000000000000..a5b636207a9a --- /dev/null +++ b/apps/explorer/lib/explorer/bloom_filter.ex @@ -0,0 +1,78 @@ +defmodule Explorer.BloomFilter do + @moduledoc """ + Eth Bloom filter realization. Reference: https://github.com/NethermindEth/nethermind/blob/d61c78af6de2d0a89bd4efd6bfed62cb6b774f59/src/Nethermind/Nethermind.Core/Bloom.cs + """ + import Bitwise + + alias Explorer.BloomFilter + alias Explorer.Chain.Log + + @bloom_byte_length 256 + @bloom_bit_length 8 * @bloom_byte_length + + defstruct filter: <<0::2048>> + + @doc """ + Computes bloom filter from list of logs + """ + @spec logs_bloom([Log.t()]) :: <<_::2048>> + def logs_bloom(logs) do + logs + |> Enum.reduce(%BloomFilter{}, fn log, acc -> + topics = + Enum.reject( + [log.first_topic, log.second_topic, log.third_topic, log.fourth_topic], + &is_nil/1 + ) + + acc_new = + acc + |> add(log.address_hash.bytes) + + Enum.reduce(topics, acc_new, fn topic, acc -> add(acc, topic.bytes) end) + end) + |> Map.get(:filter) + end + + defp add(%BloomFilter{filter: filter} = bloom, element) do + {i1, i2, i3} = get_extract(element) + + new_filter = + filter + |> set_index(i1) + |> set_index(i2) + |> set_index(i3) + + %BloomFilter{bloom | filter: new_filter} + end + + defp hash_function(data), do: ExKeccak.hash_256(data) + + defp set_index(filter, index) do + byte_position = div(index, 8) + shift = rem(index, 8) + + byte = :binary.at(filter, byte_position) + value = set_bit(byte, shift) + + <> = filter + + <> + end + + defp set_bit(byte, bit) do + mask = 1 <<< (7 - bit) + + bor(byte, mask) + end + + defp get_extract(bytes) do + hash = hash_function(bytes) + + {get_index(hash, 0, 1), get_index(hash, 2, 3), get_index(hash, 4, 5)} + end + + defp get_index(bytes, index_1, index_2) do + @bloom_bit_length - 1 - rem((:binary.at(bytes, index_1) <<< 8) + :binary.at(bytes, index_2), 2048) + end +end diff --git a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex index 05471ac2887e..163f24eb5763 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex @@ -333,7 +333,9 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do time: time && time |> Decimal.to_float(), fiat_price: fiat_fee(fee, exchange_rate_from_db), base_fee: base_fee |> format_wei(), - priority_fee: base_fee && priority_fee && priority_fee |> Decimal.new() |> Wei.from(:wei) |> format_wei() + priority_fee: base_fee && priority_fee && priority_fee |> Decimal.new() |> Wei.from(:wei) |> format_wei(), + priority_fee_wei: base_fee && priority_fee && priority_fee |> Decimal.new() |> Decimal.round(), + wei: fee |> Wei.to(:wei) |> Decimal.round() } end diff --git a/apps/explorer/lib/explorer/chain/wei.ex b/apps/explorer/lib/explorer/chain/wei.ex index 0a3f23fb4ffe..8fba9b05ef0a 100644 --- a/apps/explorer/lib/explorer/chain/wei.ex +++ b/apps/explorer/lib/explorer/chain/wei.ex @@ -118,8 +118,12 @@ defmodule Explorer.Chain.Wei do @wei_per_ether Decimal.new(1_000_000_000_000_000_000) @wei_per_gwei Decimal.new(1_000_000_000) - @spec hex_format(Wei.t()) :: String.t() + @spec hex_format(Wei.t() | Decimal.t()) :: String.t() def hex_format(%Wei{value: decimal}) do + hex_format(decimal) + end + + def hex_format(%Decimal{} = decimal) do hex = decimal |> Decimal.to_integer() diff --git a/apps/explorer/lib/explorer/eth_rpc.ex b/apps/explorer/lib/explorer/eth_rpc.ex index c3b8a80af44a..0487a60c619f 100644 --- a/apps/explorer/lib/explorer/eth_rpc.ex +++ b/apps/explorer/lib/explorer/eth_rpc.ex @@ -2,13 +2,27 @@ defmodule Explorer.EthRPC do @moduledoc """ Ethereum JSON RPC methods logic implementation. """ + import Explorer.EthRpcHelper alias Ecto.Type, as: EctoType - alias Explorer.{Chain, Repo} - alias Explorer.Chain.{Block, Data, Hash, Hash.Address, Wei} - alias Explorer.Chain.Cache.BlockNumber + alias Explorer.{BloomFilter, Chain, Helper, Repo} + + alias Explorer.Chain.{ + Block, + Data, + DenormalizationHelper, + Hash, + Hash.Address, + Transaction, + Transaction.Status, + Wei + } + + alias Explorer.Chain.Cache.{BlockNumber, GasPriceOracle} alias Explorer.Etherscan.{Blocks, Logs, RPC} + @nil_gas_price_message "Gas price is not estimated yet" + @methods %{ "eth_blockNumber" => %{ action: :eth_block_number, @@ -84,6 +98,553 @@ defmodule Explorer.EthRPC do }] } """ + }, + "eth_gasPrice" => %{ + action: :eth_gas_price, + notes: """ + Returns the average gas price per gas in wei. + """, + example: """ + {"jsonrpc": "2.0", "id": 4, "method": "eth_gasPrice", "params": []} + """, + params: [], + result: """ + {"jsonrpc": "2.0", "id": 4, "result": "0xbf69c09bb"} + """ + }, + "eth_getTransactionByHash" => %{ + action: :eth_get_transaction_by_hash, + notes: """ + """, + example: """ + {"jsonrpc": "2.0", "id": 4, "method": "eth_getTransactionByHash", "params": ["0x98318a5a22e363928d4565382c1022a8aed169b6a657f639c2f5c6e2c5114e4c"]} + """, + params: [ + %{ + name: "Data", + description: "32 Bytes - transaction hash to get", + type: "string", + default: nil, + required: true + } + ], + result: """ + { + "jsonrpc": "2.0", + "result": { + "blockHash": "0x33c4ddb4478395b9d73aad2eb8640004a4a312da29ebccbaa33933a43edda019", + "blockNumber": "0x87855e", + "chainId": "0x5", + "from": "0xe38ecdf3cfbaf5cf347e6a3d6490eb34e3a0119d", + "gas": "0x186a0", + "gasPrice": "0x195d", + "hash": "0xfe524295c6c01ab25645035a228387bf0e64c8af429f3dd9d6ef2e3b05337839", + "input": "0xe9e05c42000000000000000000000000e38ecdf3cfbaf5cf347e6a3d6490eb34e3a0119d0000000000000000000000000000000000000000000000000001c6bf5263400000000000000000000000000000000000000000000000000000000000000186a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": null, + "maxPriorityFeePerGas": null, + "nonce": "0x1", + "r": "0xf2a3f18fd456ef9a9d6201cf622b5ad14db9cfc6786ba574e036037f80a15d61", + "s": "0x4cbb018dc0a966cd15a6bf5f3d432c72127639314d6aeb7a6bbb36000d86dc08", + "to": "0xe93c8cd0d409341205a592f8c4ac1a5fe5585cfa", + "transactionIndex": "0x7f", + "type": "0x0", + "v": "0x2d", + "value": "0x1c6bf52634000" + }, + "id": 4 + } + """ + }, + "eth_getTransactionReceipt" => %{ + action: :eth_get_transaction_receipt, + notes: """ + """, + example: """ + {"jsonrpc": "2.0","id": 0,"method": "eth_getTransactionReceipt","params": ["0xFE524295C6C01AB25645035A228387BF0E64C8AF429F3DD9D6EF2E3B05337839"]} + """, + params: [ + %{ + name: "Data", + description: "32 Bytes - transaction hash to get", + type: "string", + default: nil, + required: true + } + ], + result: """ + { + "jsonrpc": "2.0", + "result": { + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000040000000000000000000000000002000000000000000000000000000000000000000000000000030000000000000000000800000000000000000000000000000000000000000000000002000000008000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000002000000000000000080000000000000000000000", + "blockHash": "0x33c4ddb4478395b9d73aad2eb8640004a4a312da29ebccbaa33933a43edda019", + "blockNumber": "0x87855e", + "contractAddress": null, + "cumulativeGasUsed": "0x15a9b84", + "effectiveGasPrice": "0x195d", + "from": "0xe38ecdf3cfbaf5cf347e6a3d6490eb34e3a0119d", + "gasUsed": "0x9821", + "logs": [ + { + "address": "0xe93c8cd0d409341205a592f8c4ac1a5fe5585cfa", + "blockHash": "0x33c4ddb4478395b9d73aad2eb8640004a4a312da29ebccbaa33933a43edda019", + "blockNumber": "0x87855e", + "data": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000490000000000000000000000000000000000000000000000000001c6bf526340000000000000000000000000000000000000000000000000000001c6bf5263400000000000000186a0000000000000000000000000000000000000000000000000", + "logIndex": "0xdf", + "removed": false, + "topics": [ + "0xb3813568d9991fc951961fcb4c784893574240a28925604d09fc577c55bb7c32", + "0x000000000000000000000000e38ecdf3cfbaf5cf347e6a3d6490eb34e3a0119d", + "0x000000000000000000000000e38ecdf3cfbaf5cf347e6a3d6490eb34e3a0119d", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "transactionHash": "0xfe524295c6c01ab25645035a228387bf0e64c8af429f3dd9d6ef2e3b05337839", + "transactionIndex": "0x7f" + } + ], + "status": "0x1", + "to": "0xe93c8cd0d409341205a592f8c4ac1a5fe5585cfa", + "transactionHash": "0xfe524295c6c01ab25645035a228387bf0e64c8af429f3dd9d6ef2e3b05337839", + "transactionIndex": "0x7f", + "type": "0x0" + }, + "id": 0 + } + """ + }, + "eth_chainId" => %{ + action: :eth_chain_id, + notes: """ + """, + example: """ + {"jsonrpc": "2.0","id": 0,"method": "eth_chainId","params": []} + """, + params: [], + result: """ + { + "jsonrpc": "2.0", + "id": 0, + "result": "0x5" + } + """ + }, + "eth_maxPriorityFeePerGas" => %{ + action: :eth_max_priority_fee_per_gas, + notes: """ + """, + example: """ + {"jsonrpc": "2.0","id": 0,"method": "eth_maxPriorityFeePerGas","params": []} + """, + params: [], + result: """ + { + "jsonrpc": "2.0", + "id": 0, + "result": "0x3b9aca00" + } + """ + } + } + + @proxy_methods %{ + "eth_getTransactionCount" => %{ + arity: 2, + params_validators: [&address_hash_validator/1, &block_validator/1], + example: """ + {"id": 0, "jsonrpc": "2.0", "method": "eth_getTransactionCount", "params": ["0x0000000000000000000000000000000000000007", "latest"]} + """, + result: """ + {"id": 0, "jsonrpc": "2.0", "result": "0x2"} + """ + }, + "eth_getCode" => %{ + arity: 2, + params_validators: [&address_hash_validator/1, &block_validator/1], + example: """ + {"jsonrpc":"2.0","id": 0,"method":"eth_getCode","params":["0x1BF313AADe1e1f76295943f40B558Eb13Db7aA99", "latest"]} + """, + result: """ + { + "jsonrpc": "2.0", + "result": "0x60806040523661001357610011610017565b005b6100115b610027610022610067565b61009f565b565b606061004e838360405180606001604052806027815260200161026b602791396100c3565b9392505050565b6001600160a01b03163b151590565b90565b600061009a7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b3660008037600080366000845af43d6000803e8080156100be573d6000f35b3d6000fd5b6060600080856001600160a01b0316856040516100e0919061021b565b600060405180830381855af49150503d806000811461011b576040519150601f19603f3d011682016040523d82523d6000602084013e610120565b606091505b50915091506101318683838761013b565b9695505050505050565b606083156101af5782516000036101a8576001600160a01b0385163b6101a85760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064015b60405180910390fd5b50816101b9565b6101b983836101c1565b949350505050565b8151156101d15781518083602001fd5b8060405162461bcd60e51b815260040161019f9190610237565b60005b838110156102065781810151838201526020016101ee565b83811115610215576000848401525b50505050565b6000825161022d8184602087016101eb565b9190910192915050565b60208152600082518060208401526102568160408501602087016101eb565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220ef6e0977d993c1b69ec75a2f9fd6a53122d4ad4f9d71477641195afb6a6a45dd64736f6c634300080f0033", + "id": 0 + } + """ + }, + "eth_getStorageAt" => %{ + arity: 3, + params_validators: [&address_hash_validator/1, &integer_validator/1, &block_validator/1], + example: """ + {"jsonrpc":"2.0","id":4,"method":"eth_getStorageAt","params":["0x1643E812aE58766192Cf7D2Cf9567dF2C37e9B7F", "0x", "latest"]} + """, + result: """ + { + "jsonrpc": "2.0", + "result": "0x0000000000000000000000000000000000000000000000000000000000000000", + "id": 4 + } + """ + }, + "eth_estimateGas" => %{ + arity: 2, + params_validators: [ð_call_validator/1, &block_validator/1], + example: """ + {"jsonrpc":"2.0","id": 0,"method":"eth_estimateGas","params":[{"to": "0x1643E812aE58766192Cf7D2Cf9567dF2C37e9B7F", "input": "0xd4aae0c4", "from": "0x1643E812aE58766192Cf7D2Cf9567dF2C37e9B7F"}, "latest"]} + """, + result: """ + { + "jsonrpc": "2.0", + "result": "0x5bb6", + "id": 0 + } + """ + }, + "eth_getBlockByNumber" => %{ + arity: 2, + params_validators: [&block_validator/1, &bool_validator/1], + example: """ + {"jsonrpc":"2.0","id": 0,"method":"eth_getBlockByNumber","params":["latest", false]} + """, + result: """ + { + "jsonrpc": "2.0", + "result": { + "baseFeePerGas": "0x7", + "blobGasUsed": "0x0", + "difficulty": "0x0", + "excessBlobGas": "0x4bc0000", + "extraData": "0xd883010d0a846765746888676f312e32312e35856c696e7578", + "gasLimit": "0x1c9c380", + "gasUsed": "0x29b80d", + "hash": "0xbc2e3a9caf7364d306fe4af34d2e9f0a3d478ed1a8e135bf7cd0845646c858f5", + "logsBloom": "0x022100021800180480000040e0008004001044020100000204080000a20001100100100002000802c00020194040204000020010000200400000020004212000804100a4242020041800108d0228082402000040090000c80001040080000080000600000224a0b00000d88000004803000000220008014000204010100040008000804408000004200000250010400004001481a80001080080404104114040032000307022969010004000840040000322400002010108490180088040205030055002481208004903100400070000104000002008001008080010002020001818002020a04000501101080000000000201004000001400040880000000000", + "miner": "0x94750381be1aba0504c666ee1db118f68f0780d4", + "mixHash": "0xd6b01921b81abdec5eccc9f5e17246be9dfec6d3bdbf59503bdeee2db3f97a57", + "nonce": "0x0000000000000000", + "number": "0xa0ff94", + "parentBeaconBlockRoot": "0xbd4670ba8503146561cb96962185fc251e2040eed07fccc749a26b8edbfd2d1c", + "parentHash": "0x7d4de3172a22e4549b28492ab9ffe6b5bf050b82d2c9b744133657aa7ae4385d", + "receiptsRoot": "0x13ae8ce96a643074f94bc1358b1ac1a3e3660856df943b9c6b60d499386e580d", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x2d07", + "stateRoot": "0x68510947af6edb94d0d1852d881589001318872b5bad832006c569e1a4f26871", + "timestamp": "0x65d07350", + "totalDifficulty": "0xa4a470", + "transactions": [ + "0xa3bb1b7bb5ee2d04114d47bbca1d8597c390e7c8ccfe04b5bfe96f6dfe897ec7", + "0xb6f680d4ba7e258e5e306744e61be1abb9b6cd005eb9423badc0b3603eb4ad5c", + "0x3c97b4ee54827e95ebf915dafdba9059ba5f4013c0371d443fe934a644725c60", + "0xd91e6db89992030da48d92825220053b1ea39f6d8d619c0f3fbc9a9e059c903e", + "0xbf763dc0a81dd2ef44f19673f001de560bce4db1499b7c0461c208afd863a62c", + "0xadd61d6e79560df74dc72891b2b19c83586d7857e313c0fdea9edbe1bfb11866", + "0xc66df09eaefac0348f48ce9e3f79e27a537bc8f274c525dd884f285d5e05bf31", + "0x217a26e8e407638e68364c2edebdae35f2a55eae080caa9ab31be430247a06e7", + "0xcae598dde02f35993cc4dad6f431596d8326a69b8f6563156edc3e970d6736d6", + "0xcfa79201e7574bce217f3f790f99bee8e0af45cffcd75ad17a9742630664df3f", + "0xe1e9b3b32e1098b3e08786407043410a6142481c1076818341cb05d7ebb3aaa3", + "0x7ce2eb696fd7c60e443bfeeeb39c2011d968b7bcfa40c20613549963f11e30a6", + "0xb4b5db2b4397e89b068ca01fc1b6bf8494a7fcd60e39e7059baef2968e874ba4", + "0x877e4ce429f4b64a095e0648b5ee69c31591116a697d03fddc5ff069302c944d", + "0xa5b8f358a3210221551250369c8dc2584c79fb424af1dd134bdab3a125eb1ea8", + "0x1c1d3df874c3ff9b84195bfe0bd5dbd50677443ff9a429bd01de4a18ccaf9293", + "0x79be4e1a433f250a35b7898916a0611f957fb7ca522836354eebfa421b2c8c99", + "0xd1c7fc2537a6627d0056e70a23bf90f988eabc518a31cd3d7520ec4ca0f9f9f0", + "0xb8c0b577257a0a184bf53454b68ce612a7567bcce48a64ee10e8b3d899c6ee16", + "0x26e81d1ba0109e5f50da13cb03d70a4fd5ffc97a0dad8e0c33fa7a8856db1480", + "0x4667088b1ab61818ebd08810a354dbe2f1ce9a4cb3f735aa692efcc8f15c7e5f" + ], + "transactionsRoot": "0x9d4d5a21e9ae6294a2a197c6d051a184c109882a7f74b7e63aaf3e64e4a77a33", + "uncles": [], + "withdrawals": [ + { + "address": "0x46e77b9485b13b4d401dac9ad3f59700a5200aeb", + "amount": "0x13d378", + "index": "0x1cdf824", + "validatorIndex": "0xa6d24" + }, + { + "address": "0x46e77b9485b13b4d401dac9ad3f59700a5200aeb", + "amount": "0x124012", + "index": "0x1cdf825", + "validatorIndex": "0xa6d25" + }, + { + "address": "0x46e77b9485b13b4d401dac9ad3f59700a5200aeb", + "amount": "0x175e6f", + "index": "0x1cdf826", + "validatorIndex": "0xa6d26" + }, + { + "address": "0x46e77b9485b13b4d401dac9ad3f59700a5200aeb", + "amount": "0x16b5fe", + "index": "0x1cdf827", + "validatorIndex": "0xa6d27" + }, + { + "address": "0x46e77b9485b13b4d401dac9ad3f59700a5200aeb", + "amount": "0x1660d2", + "index": "0x1cdf828", + "validatorIndex": "0xa6d28" + }, + { + "address": "0x46e77b9485b13b4d401dac9ad3f59700a5200aeb", + "amount": "0x145405", + "index": "0x1cdf829", + "validatorIndex": "0xa6d29" + }, + { + "address": "0x46e77b9485b13b4d401dac9ad3f59700a5200aeb", + "amount": "0x16246d", + "index": "0x1cdf82a", + "validatorIndex": "0xa6d2a" + }, + { + "address": "0x46e77b9485b13b4d401dac9ad3f59700a5200aeb", + "amount": "0x14a5a1", + "index": "0x1cdf82b", + "validatorIndex": "0xa6d2b" + }, + { + "address": "0x46e77b9485b13b4d401dac9ad3f59700a5200aeb", + "amount": "0x142199", + "index": "0x1cdf82c", + "validatorIndex": "0xa6d2c" + }, + { + "address": "0x46e77b9485b13b4d401dac9ad3f59700a5200aeb", + "amount": "0x182250", + "index": "0x1cdf82d", + "validatorIndex": "0xa6d2d" + }, + { + "address": "0x46e77b9485b13b4d401dac9ad3f59700a5200aeb", + "amount": "0x18b97e", + "index": "0x1cdf82e", + "validatorIndex": "0xa6d2e" + }, + { + "address": "0x46e77b9485b13b4d401dac9ad3f59700a5200aeb", + "amount": "0x151536", + "index": "0x1cdf82f", + "validatorIndex": "0xa6d2f" + }, + { + "address": "0x46e77b9485b13b4d401dac9ad3f59700a5200aeb", + "amount": "0x14bc4a", + "index": "0x1cdf830", + "validatorIndex": "0xa6d30" + }, + { + "address": "0x46e77b9485b13b4d401dac9ad3f59700a5200aeb", + "amount": "0x162f06", + "index": "0x1cdf831", + "validatorIndex": "0xa6d31" + }, + { + "address": "0x46e77b9485b13b4d401dac9ad3f59700a5200aeb", + "amount": "0x13563b", + "index": "0x1cdf832", + "validatorIndex": "0xa6d32" + }, + { + "address": "0x46e77b9485b13b4d401dac9ad3f59700a5200aeb", + "amount": "0x148d8b", + "index": "0x1cdf833", + "validatorIndex": "0xa6d33" + } + ], + "withdrawalsRoot": "0xc6a4b2cace2cc78c3a304731165b848e455fc7a3bf876837048cb4974a62c25f" + }, + "id": 0 + } + """ + }, + "eth_getBlockByHash" => %{ + arity: 2, + params_validators: [&hash_validator/1, &bool_validator/1], + example: """ + {"jsonrpc":"2.0","id": 0,"method":"eth_getBlockByHash","params":["0x2980314632a35ff83ef1f26a2a972259dca49353ed9368a04f21bcd7a5512231", false]} + """, + result: """ + { + "jsonrpc": "2.0", + "id": 0, + "result": { + "baseFeePerGas": "0x7", + "blobGasUsed": "0xc0000", + "difficulty": "0x0", + "excessBlobGas": "0x4b40000", + "extraData": "0x496c6c756d696e61746520446d6f63726174697a6520447374726962757465", + "gasLimit": "0x1c9c380", + "gasUsed": "0x2ff140", + "hash": "0x2980314632a35ff83ef1f26a2a972259dca49353ed9368a04f21bcd7a5512231", + "logsBloom": "0x40200000202018800808200040082040800001000040000000200984800600000200000000000810000020014000200028000000200000010530034004202010000440800d00a0000100000800000820020100009040808c80004000000040000017000003040610800002002800081a405800080060140080004a100000000308220100000400020002000100004000040412020010020000018040000000010700804008040088108001020004110008026280800021824180002c00008200a01440120000223009022014801001120080000020080000090100020000281004102000802802a1820024000c00020008000290151802004000080000000804", + "miner": "0xb64a30399f7f6b0c154c2e7af0a3ec7b0a5b131a", + "mixHash": "0xe5cf393a9e4b40800fd4e4a1d2be0de08e7aabc83de5fd16ff719680d7a04253", + "nonce": "0x0000000000000000", + "number": "0xa21bc8", + "parentBeaconBlockRoot": "0xca280fd409ee503ae331931d64ee7fc29da9ed566cba6dfc4212a2f2f8004c41", + "parentHash": "0xc2fc3c51d15a2fe6f219079694865ffd9f8fe56e714d9bc49e9451e1c430acf9", + "receiptsRoot": "0x7941071eea76cec0eb4541854bd7820ef36c4d44ae51413063b45d9ea127313d", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x34cf", + "stateRoot": "0xcff5056271c6e6f6bf04d2e82392fa3ebcf2bc4aca9fe8801edcfcc261ddb557", + "timestamp": "0x65e324a4", + "totalDifficulty": "0xa4a470", + "transactions": [ + "0x204c69c327e3202adba5cfb1e15b99e63fe104905e19a2359d827788f24b0579", + "0x2d07b3bc722c139ffe2ed6a32fc56e944569cc47511fef6e0351dc1da9a23562", + "0x87772fafc7eee41d723a2dcdf2ceaae3726d40a9588ae1f7802f03dce6902fbc", + "0x2e18d4a8bd9b4d0d70651097e44b25f29b4c013ff548ea6e5f3eb975b2bdfb78", + "0xf54ef7af503a7031dc01696339cfcfee3066979a63e3ed626c15bb8282273cea", + "0xb71201da6ad30304942a308e3a7666198394f1916accc9db72d03c1b508c8065", + "0xdc854518c44ae0c3fb80b8b9fdde5da72445552356f79bbfc45d7503a32a23f8", + "0x8b866e254a609a1a4163484cf330bdfc6c6a1878cd35dcc9fbfe2256f324a626", + "0xbc7448bf0c34c0a358ead13e8d3687cbccbbb7fe4048d005cb6648c897bc9254", + "0xa01733dc6d416a59d69fe17dc9d6960dbeb013b0dab2cd59b72cf84b371d19c1", + "0x1c194a1bca34deb14e93e9007de6f971856c43a65208393254f0b2e6f99deab3", + "0x9e9c8a6094300ed29a22892e87ab7fe33d19630ccdd85a0cce72ce6095d0c7da", + "0xa1d6c2fe6a937e437cda199cc0f6891727c0f3ca810a262fb3179fd961cb95c4", + "0xc4b55bada8c0c044f1e8bbd7fb57cd3a46844848e273720fc7bbc757d8e68665", + "0x8e971964ef06896d541d5cefef7cebc79d60d6746aae2fa39e954608e2c49824", + "0x6d120cf3998a767e567ef1b6615e5a14c380103b287c92d1da229cabc49ebb77", + "0x95d1b8d32a80809d79f4a0246e960fec11b59c07f1a33207485dda0b356b3c2c", + "0xa19b4b6372a9c8145e03d62e91536468169350790162508c0f07c66849fde86d", + "0x55348af743327b1377082b9fccddfcdefe7300b65e7ed32575c09d881ece711d", + "0xaaf03a35d70aa96582889565d1211e32fc395c9e63ce82d25cc23518e38aa4bc", + "0x85054ea8eddd5b1e8d010f8aac77693484c5863d3355756a64bd0225124c8fca" + ], + "transactionsRoot": "0x22309b0cc7df445160ca2c6ca344e63296231fad2e9322989477851d38c0eea0", + "uncles": [], + "withdrawals": [ + { + "index": "0x1dfb534", + "validatorIndex": "0xaef81", + "address": "0xe2e336478e97bfd6a84c0e246f1b8695dd4e990d", + "amount": "0x1e2a5b" + }, + { + "index": "0x1dfb535", + "validatorIndex": "0xaef82", + "address": "0xe2e336478e97bfd6a84c0e246f1b8695dd4e990d", + "amount": "0x1f9526" + }, + { + "index": "0x1dfb536", + "validatorIndex": "0xaef83", + "address": "0xe2e336478e97bfd6a84c0e246f1b8695dd4e990d", + "amount": "0x1fa60c" + }, + { + "index": "0x1dfb537", + "validatorIndex": "0xaef84", + "address": "0xe2e336478e97bfd6a84c0e246f1b8695dd4e990d", + "amount": "0x1e806a" + }, + { + "index": "0x1dfb538", + "validatorIndex": "0xaef85", + "address": "0xe2e336478e97bfd6a84c0e246f1b8695dd4e990d", + "amount": "0x1eb4e4" + }, + { + "index": "0x1dfb539", + "validatorIndex": "0xaef86", + "address": "0xe2e336478e97bfd6a84c0e246f1b8695dd4e990d", + "amount": "0x2054a0" + }, + { + "index": "0x1dfb53a", + "validatorIndex": "0xaef87", + "address": "0xe2e336478e97bfd6a84c0e246f1b8695dd4e990d", + "amount": "0x1d984a" + }, + { + "index": "0x1dfb53b", + "validatorIndex": "0xaef88", + "address": "0xe2e336478e97bfd6a84c0e246f1b8695dd4e990d", + "amount": "0x1fa4d4" + }, + { + "index": "0x1dfb53c", + "validatorIndex": "0xaef89", + "address": "0xe2e336478e97bfd6a84c0e246f1b8695dd4e990d", + "amount": "0x203a98" + }, + { + "index": "0x1dfb53d", + "validatorIndex": "0xaef8a", + "address": "0xe2e336478e97bfd6a84c0e246f1b8695dd4e990d", + "amount": "0x1fec28" + }, + { + "index": "0x1dfb53e", + "validatorIndex": "0xaef8b", + "address": "0xe2e336478e97bfd6a84c0e246f1b8695dd4e990d", + "amount": "0x2025a5" + }, + { + "index": "0x1dfb53f", + "validatorIndex": "0xaef8c", + "address": "0xe2e336478e97bfd6a84c0e246f1b8695dd4e990d", + "amount": "0x1fdb08" + }, + { + "index": "0x1dfb540", + "validatorIndex": "0xaef8d", + "address": "0xe2e336478e97bfd6a84c0e246f1b8695dd4e990d", + "amount": "0x200a11" + }, + { + "index": "0x1dfb541", + "validatorIndex": "0xaef8e", + "address": "0xe2e336478e97bfd6a84c0e246f1b8695dd4e990d", + "amount": "0x1f03d5" + }, + { + "index": "0x1dfb542", + "validatorIndex": "0xaef8f", + "address": "0xe2e336478e97bfd6a84c0e246f1b8695dd4e990d", + "amount": "0x200804" + }, + { + "index": "0x1dfb543", + "validatorIndex": "0xaef90", + "address": "0xe2e336478e97bfd6a84c0e246f1b8695dd4e990d", + "amount": "0x1dd0bb" + } + ], + "withdrawalsRoot": "0xcba66455c17861d36575f98adedc90b1fc56bbef7982992cab6914528dbd0100" + } + } + """ + }, + "eth_sendRawTransaction" => %{ + arity: 1, + params_validators: [&hex_data_validator/1], + example: """ + {"jsonrpc":"2.0","id": 0,"method":"eth_sendRawTransaction","params":["0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"]} + """, + result: """ + { + "jsonrpc": "2.0", + "id": 0, + "result": "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331" + } + """ + }, + "eth_call" => %{ + arity: 2, + params_validators: [ð_call_validator/1, &block_validator/1], + example: """ + {"jsonrpc":"2.0","id": 0,"method":"eth_call","params":[{"to": "0x1643E812aE58766192Cf7D2Cf9567dF2C37e9B7F", "input": "0xd4aae0c4", "from": "0x1643E812aE58766192Cf7D2Cf9567dF2C37e9B7F"}, "latest"]} + """, + result: """ + { + "jsonrpc": "2.0", + "result": "0x0000000000000000000000001dd91b354ebd706ab3ac7c727455c7baa164945a", + "id": 0 + } + """ } } @@ -94,18 +655,125 @@ defmodule Explorer.EthRPC do 3 => "fourth" } + @incorrect_number_of_params "Incorrect number of params." + + @spec responses([map()]) :: [map()] def responses(requests) do - Enum.map(requests, fn request -> - with {:id, {:ok, id}} <- {:id, Map.fetch(request, "id")}, + requests = + requests + |> Enum.with_index() + + proxy_requests = + requests + |> Enum.reduce(%{}, fn {request, index}, acc -> + case proxy_method?(request) do + true -> + Map.put(acc, index, request) + + {:error, _reason} = error -> + Map.put(acc, index, error) + + false -> + acc + end + end) + |> json_rpc() + + Enum.map(requests, fn {request, index} -> + with {:proxy, nil} <- {:proxy, proxy_requests[index]}, + {:id, {:ok, id}} <- {:id, Map.fetch(request, "id")}, {:request, {:ok, result}} <- {:request, do_eth_request(request)} do format_success(result, id) else {:id, :error} -> format_error("id is a required field", 0) {:request, {:error, message}} -> format_error(message, Map.get(request, "id")) + {:proxy, {:error, message}} -> format_error(message, Map.get(request, "id")) + {:proxy, %{result: result}} -> format_success(result, Map.get(request, "id")) + {:proxy, %{error: error}} -> format_error(error, Map.get(request, "id")) end end) end + defp proxy_method?(%{"jsonrpc" => "2.0", "method" => method, "params" => params, "id" => id}) + when is_list(params) and (is_number(id) or is_binary(id) or is_nil(id)) do + with method_definition when not is_nil(method_definition) <- @proxy_methods[method], + {:arity, true} <- {:arity, method_definition[:arity] == length(params)}, + :ok <- validate_params(method_definition[:params_validators], params) do + true + else + {:error, _reason} = error -> + error + + {:arity, false} -> + {:error, @incorrect_number_of_params} + + _ -> + false + end + end + + defp proxy_method?(_), do: false + + defp validate_params(validators, params) do + validators + |> Enum.zip(params) + |> Enum.reduce_while(:ok, fn + {validator_func, param}, :ok -> + {:cont, validator_func.(param)} + + _, error -> + {:halt, error} + end) + end + + defp json_rpc(map) when is_map(map) do + to_request = + Enum.flat_map(Map.values(map), fn + {:error, _} -> + [] + + map when is_map(map) -> + [request_to_elixir(map)] + end) + + with [_ | _] = to_request <- to_request, + {:ok, responses} <- + EthereumJSONRPC.json_rpc(to_request, Application.get_env(:explorer, :json_rpc_named_arguments)) do + {map, []} = + Enum.map_reduce(map, responses, fn + {_index, {:error, _}} = elem, responses -> + {elem, responses} + + {index, _request}, [response | other_responses] -> + {{index, response}, other_responses} + end) + + Enum.into(map, %{}) + else + [] -> + map + + {:error, _reason} = error -> + map + |> Enum.map(fn + {_index, {:error, _}} = elem -> + elem + + {index, _request} -> + {index, error} + end) + |> Enum.into(%{}) + end + end + + defp request_to_elixir(%{"jsonrpc" => json_rpc, "method" => method, "params" => params, "id" => id}) do + %{jsonrpc: json_rpc, method: method, params: params, id: id} + end + + @doc """ + Handles `eth_blockNumber` method + """ + @spec eth_block_number() :: {:ok, String.t()} def eth_block_number do max_block_number = BlockNumber.get_max() @@ -116,6 +784,10 @@ defmodule Explorer.EthRPC do {:ok, max_block_number_hex} end + @doc """ + Handles `eth_getBalance` method + """ + @spec eth_get_balance(String.t(), String.t() | nil) :: {:ok, String.t()} | {:error, String.t()} def eth_get_balance(address_param, block_param \\ nil) do with {:address, {:ok, address}} <- {:address, Chain.string_to_address_hash(address_param)}, {:block, {:ok, block}} <- {:block, block_param(block_param)}, @@ -133,6 +805,126 @@ defmodule Explorer.EthRPC do end end + @doc """ + Handles `eth_gasPrice` method + """ + @spec eth_gas_price() :: {:ok, String.t()} | {:error, String.t()} + def eth_gas_price do + case GasPriceOracle.get_gas_prices() do + {:ok, gas_prices} -> + {:ok, Wei.hex_format(gas_prices[:average][:wei])} + + _ -> + {:error, @nil_gas_price_message} + end + end + + @doc """ + Handles `eth_maxPriorityFeePerGas` method + """ + @spec eth_max_priority_fee_per_gas() :: {:ok, String.t()} | {:error, String.t()} + def eth_max_priority_fee_per_gas do + case GasPriceOracle.get_gas_prices() do + {:ok, gas_prices} -> + {:ok, Wei.hex_format(gas_prices[:average][:priority_fee_wei])} + + _ -> + {:error, @nil_gas_price_message} + end + end + + @doc """ + Handles `eth_chainId` method + """ + @spec eth_chain_id() :: {:ok, String.t() | nil} + def eth_chain_id do + {:ok, chain_id()} + end + + @doc """ + Handles `eth_getTransactionByHash` method + """ + @spec eth_get_transaction_by_hash(String.t()) :: {:ok, map() | nil} | {:error, String.t()} + def eth_get_transaction_by_hash(transaction_hash_string) do + validate_and_render_transaction(transaction_hash_string, &render_transaction/1, api?: true) + end + + defp render_transaction(transaction) do + {:ok, + %{ + "blockHash" => transaction.block_hash, + "blockNumber" => encode_quantity(transaction.block_number), + "from" => transaction.from_address_hash, + "gas" => encode_quantity(transaction.gas), + "gasPrice" => transaction.gas_price |> Wei.to(:wei) |> encode_quantity(), + "maxPriorityFeePerGas" => transaction.max_priority_fee_per_gas |> Wei.to(:wei) |> encode_quantity(), + "maxFeePerGas" => transaction.max_fee_per_gas |> Wei.to(:wei) |> encode_quantity(), + "hash" => transaction.hash, + "input" => transaction.input, + "nonce" => encode_quantity(transaction.nonce), + "to" => transaction.to_address_hash, + "transactionIndex" => encode_quantity(transaction.index), + "value" => transaction.value |> Wei.to(:wei) |> encode_quantity(), + "type" => encode_quantity(transaction.type), + "chainId" => chain_id(), + "v" => encode_quantity(transaction.v), + "r" => encode_quantity(transaction.r), + "s" => encode_quantity(transaction.s) + }} + end + + @doc """ + Handles `eth_getTransactionReceipt` method + """ + @spec eth_get_transaction_receipt(String.t()) :: {:ok, map() | nil} | {:error, String.t()} + def eth_get_transaction_receipt(transaction_hash_string) do + necessity_by_association = %{block: :optional, logs: :optional} + + validate_and_render_transaction(transaction_hash_string, &render_transaction_receipt/1, + api?: true, + necessity_by_association: necessity_by_association + ) + end + + defp render_transaction_receipt(transaction) do + {:ok, status} = Status.dump(transaction.status) + + {:ok, + %{ + "blockHash" => transaction.block_hash, + "blockNumber" => encode_quantity(transaction.block_number), + "contractAddress" => transaction.created_contract_address_hash, + "cumulativeGasUsed" => encode_quantity(transaction.cumulative_gas_used), + "effectiveGasPrice" => + (transaction.gas_price || transaction |> Transaction.effective_gas_price()) + |> Wei.to(:wei) + |> encode_quantity(), + "from" => transaction.from_address_hash, + "gasUsed" => encode_quantity(transaction.gas_used), + "logs" => Enum.map(transaction.logs, &render_log(&1, transaction)), + 'logsBloom' => "0x" <> (transaction.logs |> BloomFilter.logs_bloom() |> Base.encode16(case: :lower)), + "status" => encode_quantity(status), + "to" => transaction.to_address_hash, + "transactionHash" => transaction.hash, + "transactionIndex" => encode_quantity(transaction.index), + "type" => encode_quantity(transaction.type) + }} + end + + defp validate_and_render_transaction(transaction_hash_string, render_func, params) do + with {:transaction_hash, {:ok, transaction_hash}} <- + {:transaction_hash, Chain.string_to_transaction_hash(transaction_hash_string)}, + {:transaction, {:ok, transaction}} <- {:transaction, Chain.hash_to_transaction(transaction_hash, params)} do + render_func.(transaction) + else + {:transaction_hash, :error} -> + {:error, "Transaction hash is invalid"} + + {:transaction, _} -> + {:ok, nil} + end + end + def eth_get_logs(filter_options) do with {:ok, address_or_topic_params} <- address_or_topic_params(filter_options), {:ok, from_block_param, to_block_param} <- logs_blocks_filter(filter_options), @@ -164,11 +956,7 @@ defmodule Explorer.EthRPC do end defp render_log(log) do - topics = - Enum.reject( - [log.first_topic, log.second_topic, log.third_topic, log.fourth_topic], - &is_nil/1 - ) + topics = prepare_topics(log) %{ "address" => to_string(log.address_hash), @@ -189,6 +977,37 @@ defmodule Explorer.EthRPC do } end + defp render_log(log, transaction) do + topics = prepare_topics(log) + + %{ + "address" => log.address_hash, + "blockHash" => log.block_hash, + "blockNumber" => encode_quantity(log.block_number), + "data" => log.data, + "logIndex" => encode_quantity(log.index), + "removed" => transaction_consensus(transaction) == false, + "topics" => topics, + "transactionHash" => log.transaction_hash, + "transactionIndex" => encode_quantity(transaction.index) + } + end + + defp transaction_consensus(transaction) do + if DenormalizationHelper.transactions_denormalization_finished?() do + transaction.block_consensus + else + transaction.block.consensus + end + end + + defp prepare_topics(log) do + Enum.reject( + [log.first_topic, log.second_topic, log.third_topic, log.fourth_topic], + &is_nil/1 + ) + end + defp cast_block("0x" <> hexadecimal_digits = input) do case Integer.parse(hexadecimal_digits, 16) do {integer, ""} -> {:ok, integer} @@ -429,6 +1248,8 @@ defmodule Explorer.EthRPC do defp block_param(nil), do: {:ok, :latest} defp block_param(_), do: :error + def encode_quantity(%Decimal{} = decimal), do: encode_quantity(Decimal.to_integer(decimal)) + def encode_quantity(binary) when is_binary(binary) do hex_binary = Base.encode16(binary, case: :lower) @@ -450,4 +1271,6 @@ defmodule Explorer.EthRPC do end def methods, do: @methods + + defp chain_id, do: :block_scout_web |> Application.get_env(:chain_id) |> Helper.parse_integer() |> encode_quantity() end diff --git a/apps/explorer/lib/explorer/eth_rpc_helper.ex b/apps/explorer/lib/explorer/eth_rpc_helper.ex new file mode 100644 index 000000000000..2cecf52be106 --- /dev/null +++ b/apps/explorer/lib/explorer/eth_rpc_helper.ex @@ -0,0 +1,121 @@ +defmodule Explorer.EthRpcHelper do + @moduledoc """ + Helper module for Explorer.EthRPC. Mostly contains functions to validate input args + """ + + alias Explorer.Chain.{Data, Hash.Address} + alias Explorer.Chain.Hash.Full, as: Hash + + @invalid_address "Invalid address" + @invalid_block_number "Invalid block number" + @invalid_integer "Invalid integer" + @missed_to_address "Missed `to` address" + @invalid_bool "Invalid bool" + @invalid_hash "Invalid hash" + @invalid_hex_data "Invalid hex data" + @doc """ + Validates if address is valid + """ + @spec address_hash_validator(binary(), String.t()) :: :ok | {:error, String.t()} + def address_hash_validator(address_hash, message \\ @invalid_address) do + case Address.cast(address_hash) do + {:ok, _} -> :ok + :error -> {:error, message} + end + end + + @doc """ + Validates if hash is valid + """ + @spec hash_validator(binary()) :: :ok | {:error, String.t()} + def hash_validator(hash) do + case Hash.cast(hash) do + {:ok, _} -> :ok + :error -> {:error, @invalid_hash} + end + end + + @doc """ + Validates if hex data is valid + """ + @spec hex_data_validator(binary()) :: :ok | {:error, String.t()} + def hex_data_validator(hex_data) do + case Data.cast(hex_data) do + {:ok, _} -> :ok + _ -> {:error, @invalid_hex_data} + end + end + + @doc """ + Validates if block is valid + """ + @spec block_validator(binary()) :: :ok | {:error, String.t()} + def block_validator(block_tag) when block_tag in ["latest", "earliest", "pending"], do: :ok + + def block_validator(block_number) do + parse_integer(block_number) || {:error, @invalid_block_number} + end + + def integer_validator(hex) do + parse_integer(hex) || {:error, @invalid_integer} + end + + @doc """ + Validates eth_call map + """ + @spec eth_call_validator(map()) :: :ok | {:error, String.t()} + def eth_call_validator(%{"to" => to_address} = eth_call) do + with :ok <- address_hash_validator(to_address, "Invalid `to` address"), + :ok <- validate_optional_address(eth_call["from"], "from"), + :ok <- validate_optional_integer(eth_call["gas"], "gas"), + :ok <- validate_optional_integer(eth_call["gasPrice"], "gasPrice"), + :ok <- validate_optional_integer(eth_call["value"], "value"), + :ok <- validate_optional_hex_data(eth_call["input"], "input") do + :ok + else + error -> + error + end + end + + def eth_call_validator(_), do: {:error, @missed_to_address} + + @doc """ + Validates if bool is valid + """ + @spec bool_validator(boolean()) :: :ok | {:error, String.t()} + def bool_validator(bool) when is_boolean(bool), do: :ok + def bool_validator(_), do: {:error, @invalid_bool} + + defp validate_optional_address(nil, _), do: :ok + + defp validate_optional_address(address_hash, field_name) do + address_hash_validator(address_hash, "Invalid `#{field_name}` address") + end + + defp validate_optional_integer(nil, _), do: :ok + + defp validate_optional_integer(integer, field_name) do + parse_integer(integer) || {:error, "Invalid `#{field_name}` quantity"} + end + + defp parse_integer("0x"), do: :ok + + defp parse_integer("0x" <> hex_integer) do + case Integer.parse(hex_integer, 16) do + {_integer, ""} -> :ok + _ -> nil + end + end + + defp parse_integer(_), do: nil + + defp validate_optional_hex_data(nil, _), do: :ok + + defp validate_optional_hex_data(data, field_name) do + case Data.cast(data) do + {:ok, _} -> :ok + _ -> {:error, "Invalid `#{field_name}` data"} + end + end +end diff --git a/apps/explorer/test/explorer/bloom_filter_test.exs b/apps/explorer/test/explorer/bloom_filter_test.exs new file mode 100644 index 000000000000..e9ec4a2c5106 --- /dev/null +++ b/apps/explorer/test/explorer/bloom_filter_test.exs @@ -0,0 +1,63 @@ +defmodule Explorer.BloomFilterTest do + use Explorer.DataCase + + alias Explorer.BloomFilter + + describe "Test bloom filter for random Goerli transactions" do + # {"jsonrpc":"2.0","id": 0,"method":"eth_getTransactionReceipt","params":["0xFE524295C6C01AB25645035A228387BF0E64C8AF429F3DD9D6EF2E3B05337839"]} + test "#1 (0xFE524295C6C01AB25645035A228387BF0E64C8AF429F3DD9D6EF2E3B05337839)" do + log_1 = + insert(:log, + first_topic: "0xb3813568d9991fc951961fcb4c784893574240a28925604d09fc577c55bb7c32", + second_topic: "0x000000000000000000000000e38ecdf3cfbaf5cf347e6a3d6490eb34e3a0119d", + third_topic: "0x000000000000000000000000e38ecdf3cfbaf5cf347e6a3d6490eb34e3a0119d", + fourth_topic: "0x0000000000000000000000000000000000000000000000000000000000000000", + address_hash: "0xe93c8cd0d409341205a592f8c4ac1a5fe5585cfa", + address: nil + ) + + assert BloomFilter.logs_bloom([log_1]) |> Base.encode16(case: :lower) == + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000040000000000000000000000000002000000000000000000000000000000000000000000000000030000000000000000000800000000000000000000000000000000000000000000000002000000008000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000002000000000000000080000000000000000000000" + end + + test "#2 (0x9f44d7080354147fc42ee0eb62c8f77d0477e7686d18debcb81f90b0d54ea1d1)" do + log_1 = + insert(:log, + first_topic: "0x9866f8ddfe70bb512b2f2b28b49d4017c43f7ba775f1a20c61c13eea8cdac111", + second_topic: nil, + third_topic: nil, + fourth_topic: nil, + address_hash: "0xd5c325d183c592c94998000c5e0eed9e6655c020", + address: nil + ) + + log_2 = + insert(:log, + first_topic: "0xd342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576c", + second_topic: nil, + third_topic: nil, + fourth_topic: nil, + address_hash: "0xd5c325d183c592c94998000c5e0eed9e6655c020", + address: nil + ) + + assert BloomFilter.logs_bloom([log_1, log_2]) |> Base.encode16(case: :lower) == + "00000000010002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000800000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000020000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000" + end + + test "#3 (0x2548b6514211bafdfeff37dc184c4700c8ca7056ac2a119bef5a98f8a79662cc)" do + log_1 = + insert(:log, + first_topic: "0x1a37b94876a9c4d5697c33a6fc124022beba6ce60e84469f41d49536d2a55924", + second_topic: "0x000000000000000000000000000000000000000000000000000000000001ba63", + third_topic: "0x00000000000000000000000000000000000000000000000000f8b0a10e470000", + fourth_topic: "0x0000000000000000000000000000000000000000000000000000000000000002", + address_hash: "0x2a5ccc8813d89119263b49f567c541e925c75f13", + address: nil + ) + + assert BloomFilter.logs_bloom([log_1]) |> Base.encode16(case: :lower) == + "04000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000420000000000000000000000800000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000020000000000000000000000000000000100000000000100000000000002010000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000010000000000000" + end + end +end diff --git a/config/runtime.exs b/config/runtime.exs index 7f17424b8b38..9d879a1ce79a 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -103,7 +103,8 @@ config :block_scout_web, :api_rate_limit, whitelisted_ips: System.get_env("API_RATE_LIMIT_WHITELISTED_IPS"), is_blockscout_behind_proxy: ConfigHelper.parse_bool_env_var("API_RATE_LIMIT_IS_BLOCKSCOUT_BEHIND_PROXY"), api_v2_ui_limit: ConfigHelper.parse_integer_env_var("API_RATE_LIMIT_UI_V2_WITH_TOKEN", 5), - api_v2_token_ttl_seconds: ConfigHelper.parse_integer_env_var("API_RATE_LIMIT_UI_V2_TOKEN_TTL_IN_SECONDS", 18000) + api_v2_token_ttl_seconds: ConfigHelper.parse_integer_env_var("API_RATE_LIMIT_UI_V2_TOKEN_TTL_IN_SECONDS", 18000), + eth_json_rpc_max_batch_size: ConfigHelper.parse_integer_env_var("ETH_JSON_RPC_MAX_BATCH_SIZE", 5) # Configures History price_chart_config = From 6bcb231226f8a80bd625e45241f000ff407e51c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:24:02 +0000 Subject: [PATCH 532/607] Bump logger_json from 5.1.3 to 5.1.4 Bumps [logger_json](https://github.com/Nebo15/logger_json) from 5.1.3 to 5.1.4. - [Release notes](https://github.com/Nebo15/logger_json/releases) - [Commits](https://github.com/Nebo15/logger_json/compare/5.1.3...5.1.4) --- updated-dependencies: - dependency-name: logger_json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 8908d5bde698..bf037c310675 100644 --- a/mix.lock +++ b/mix.lock @@ -75,7 +75,7 @@ "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm", "fc3499fed7a726995aa659143a248534adc754ebd16ccd437cd93b649a95091f"}, "junit_formatter": {:hex, :junit_formatter, "3.3.1", "c729befb848f1b9571f317d2fefa648e9d4869befc4b2980daca7c1edc468e40", [:mix], [], "hexpm", "761fc5be4b4c15d8ba91a6dafde0b2c2ae6db9da7b8832a55b5a1deb524da72b"}, "logger_file_backend": {:hex, :logger_file_backend, "0.0.13", "df07b14970e9ac1f57362985d76e6f24e3e1ab05c248055b7d223976881977c2", [:mix], [], "hexpm", "71a453a7e6e899ae4549fb147b1c6621f4233f8f48f58ca10a64ec67b6c50018"}, - "logger_json": {:hex, :logger_json, "5.1.3", "fe931b54826e7ba3b1233ede5c13d87cd670a23563f8d146d0ee22985549dbb5", [:mix], [{:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "ecc67e24f9ccf1688c5e48c3d6b7889a0ab5d398fe32a7fec69c461303a9b89c"}, + "logger_json": {:hex, :logger_json, "5.1.4", "9e30a4f2e31a8b9e402bdc20bd37cf9b67d3a31f19d0b33082a19a06b4c50f6d", [:mix], [{:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "3f20eea58e406a33d3eb7814c7dff5accb503bab2ee8601e84da02976fa3934c"}, "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"}, From d052fc1c8e68c50ca35b35ed93dcc82128b2afe3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:24:42 +0000 Subject: [PATCH 533/607] Bump credo from 1.7.4 to 1.7.5 Bumps [credo](https://github.com/rrrene/credo) from 1.7.4 to 1.7.5. - [Release notes](https://github.com/rrrene/credo/releases) - [Changelog](https://github.com/rrrene/credo/blob/master/CHANGELOG.md) - [Commits](https://github.com/rrrene/credo/compare/v1.7.4...v1.7.5) --- updated-dependencies: - dependency-name: credo dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 8908d5bde698..b44d16c18fd4 100644 --- a/mix.lock +++ b/mix.lock @@ -27,7 +27,7 @@ "cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"}, "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, - "credo": {:hex, :credo, "1.7.4", "68ca5cf89071511c12fd9919eb84e388d231121988f6932756596195ccf7fd35", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9cf776d062c78bbe0f0de1ecaee183f18f2c3ec591326107989b054b7dddefc2"}, + "credo": {:hex, :credo, "1.7.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"}, "csv": {:hex, :csv, "2.5.0", "c47b5a5221bf2e56d6e8eb79e77884046d7fd516280dc7d9b674251e0ae46246", [:mix], [{:parallel_stream, "~> 1.0.4 or ~> 1.1.0", [hex: :parallel_stream, repo: "hexpm", optional: false]}], "hexpm", "e821f541487045c7591a1963eeb42afff0dfa99bdcdbeb3410795a2f59c77d34"}, "dataloader": {:hex, :dataloader, "1.0.11", "49bbfc7dd8a1990423c51000b869b1fecaab9e3ccd6b29eab51616ae8ad0a2f5", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba0b0ec532ec68e9d033d03553561d693129bd7cbd5c649dc7903f07ffba08fe"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, From 5c211af0c9d05b47897d7d9d41318ddc5920e8d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:25:17 +0000 Subject: [PATCH 534/607] Bump floki from 0.35.4 to 0.36.0 Bumps [floki](https://github.com/philss/floki) from 0.35.4 to 0.36.0. - [Release notes](https://github.com/philss/floki/releases) - [Changelog](https://github.com/philss/floki/blob/main/CHANGELOG.md) - [Commits](https://github.com/philss/floki/compare/v0.35.4...v0.36.0) --- updated-dependencies: - dependency-name: floki dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 8908d5bde698..e9084cecf644 100644 --- a/mix.lock +++ b/mix.lock @@ -60,7 +60,7 @@ "exvcr": {:hex, :exvcr, "0.15.1", "772db4d065f5136c6a984c302799a79e4ade3e52701c95425fa2229dd6426886", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.16", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "de4fc18b1d672d9b72bc7468735e19779aa50ea963a1f859ef82cd9e294b13e3"}, "file_info": {:hex, :file_info, "0.0.4", "2e0e77f211e833f38ead22cb29ce53761d457d80b3ffe0ffe0eb93880b0963b2", [:mix], [{:mimetype_parser, "~> 0.1.2", [hex: :mimetype_parser, repo: "hexpm", optional: false]}], "hexpm", "50e7ad01c2c8b9339010675fe4dc4a113b8d6ca7eddce24d1d74fd0e762781a5"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "floki": {:hex, :floki, "0.35.4", "cc947b446024732c07274ac656600c5c4dc014caa1f8fb2dfff93d275b83890d", [:mix], [], "hexpm", "27fa185d3469bd8fc5947ef0f8d5c4e47f0af02eb6b070b63c868f69e3af0204"}, + "floki": {:hex, :floki, "0.36.0", "544d5dd8a3107f660633226b5805e47c2ac1fabd782fae86e3b22b02849b20f9", [:mix], [], "hexpm", "ab1ca4b1efb0db00df9a8e726524e2c85be88cf65ac092669186e1674d34d74c"}, "flow": {:hex, :flow, "1.2.4", "1dd58918287eb286656008777cb32714b5123d3855956f29aa141ebae456922d", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "874adde96368e71870f3510b91e35bc31652291858c86c0e75359cbdd35eb211"}, "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, From 364f47c56285dae4b47ec087e3cd95349699ba32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:25:48 +0000 Subject: [PATCH 535/607] Bump postgrex from 0.17.4 to 0.17.5 Bumps [postgrex](https://github.com/elixir-ecto/postgrex) from 0.17.4 to 0.17.5. - [Release notes](https://github.com/elixir-ecto/postgrex/releases) - [Changelog](https://github.com/elixir-ecto/postgrex/blob/master/CHANGELOG.md) - [Commits](https://github.com/elixir-ecto/postgrex/compare/v0.17.4...v0.17.5) --- updated-dependencies: - dependency-name: postgrex dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 8908d5bde698..dc9d2360f376 100644 --- a/mix.lock +++ b/mix.lock @@ -109,7 +109,7 @@ "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, - "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, + "postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"}, "prometheus": {:hex, :prometheus, "4.11.0", "b95f8de8530f541bd95951e18e355a840003672e5eda4788c5fa6183406ba29a", [:mix, :rebar3], [{:quantile_estimator, "~> 0.2.1", [hex: :quantile_estimator, repo: "hexpm", optional: false]}], "hexpm", "719862351aabf4df7079b05dc085d2bbcbe3ac0ac3009e956671b1d5ab88247d"}, "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"}, "prometheus_ex": {:git, "https://github.com/lanodan/prometheus.ex", "31f7fbe4b71b79ba27efc2a5085746c4011ceb8f", [branch: "fix/elixir-1.14"]}, From f23854a1b6c2f54565e6ea61fe0c04931eabf6d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:27:17 +0000 Subject: [PATCH 536/607] Bump phoenix_ecto from 4.4.3 to 4.5.0 Bumps [phoenix_ecto](https://github.com/phoenixframework/phoenix_ecto) from 4.4.3 to 4.5.0. - [Changelog](https://github.com/phoenixframework/phoenix_ecto/blob/main/CHANGELOG.md) - [Commits](https://github.com/phoenixframework/phoenix_ecto/compare/v4.4.3...v4.5.0) --- updated-dependencies: - dependency-name: phoenix_ecto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 8908d5bde698..202fcbc93048 100644 --- a/mix.lock +++ b/mix.lock @@ -100,7 +100,7 @@ "parallel_stream": {:hex, :parallel_stream, "1.1.0", "f52f73eb344bc22de335992377413138405796e0d0ad99d995d9977ac29f1ca9", [:mix], [], "hexpm", "684fd19191aedfaf387bbabbeb8ff3c752f0220c8112eb907d797f4592d6e871"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "phoenix": {:hex, :phoenix, "1.5.14", "2d5db884be496eefa5157505ec0134e66187cb416c072272420c5509d67bf808", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "207f1aa5520320cbb7940d7ff2dde2342162cf513875848f88249ea0ba02fef7"}, - "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.3", "86e9878f833829c3f66da03d75254c155d91d72a201eb56ae83482328dc7ca93", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d36c401206f3011fefd63d04e8ef626ec8791975d9d107f9a0817d426f61ac07"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.5.0", "1a1f841ccda19b15f1d82968840a5b895c5f687b6734e430e4b2dbe035ca1837", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "13990570fde09e16959ef214501fe2813e1192d62ca753ec8798980580436f94"}, "phoenix_html": {:hex, :phoenix_html, "3.0.4", "232d41884fe6a9c42d09f48397c175cd6f0d443aaa34c7424da47604201df2e1", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "ce17fd3cf815b2ed874114073e743507704b1f5288bb03c304a77458485efc8b"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, From dad4264d1af7f85c2835d7bf1d0346d7f2d9d44e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:27:46 +0000 Subject: [PATCH 537/607] Bump ueberauth from 0.10.7 to 0.10.8 Bumps [ueberauth](https://github.com/ueberauth/ueberauth) from 0.10.7 to 0.10.8. - [Release notes](https://github.com/ueberauth/ueberauth/releases) - [Changelog](https://github.com/ueberauth/ueberauth/blob/master/CHANGELOG.md) - [Commits](https://github.com/ueberauth/ueberauth/compare/v0.10.7...v0.10.8) --- updated-dependencies: - dependency-name: ueberauth dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 8908d5bde698..6433b4397a76 100644 --- a/mix.lock +++ b/mix.lock @@ -137,7 +137,7 @@ "toml": {:hex, :toml, "0.6.2", "38f445df384a17e5d382befe30e3489112a48d3ba4c459e543f748c2f25dd4d1", [:mix], [], "hexpm", "d013e45126d74c0c26a38d31f5e8e9b83ea19fc752470feb9a86071ca5a672fa"}, "typed_ecto_schema": {:hex, :typed_ecto_schema, "0.4.1", "a373ca6f693f4de84cde474a67467a9cb9051a8a7f3f615f1e23dc74b75237fa", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "85c6962f79d35bf543dd5659c6adc340fd2480cacc6f25d2cc2933ea6e8fcb3b"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, - "ueberauth": {:hex, :ueberauth, "0.10.7", "5a31cbe11e7ce5c7484d745dc9e1f11948e89662f8510d03c616de03df581ebd", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "0bccf73e2ffd6337971340832947ba232877aa8122dba4c95be9f729c8987377"}, + "ueberauth": {:hex, :ueberauth, "0.10.8", "ba78fbcbb27d811a6cd06ad851793aaf7d27c3b30c9e95349c2c362b344cd8f0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f2d3172e52821375bccb8460e5fa5cb91cfd60b19b636b6e57e9759b6f8c10c1"}, "ueberauth_auth0": {:hex, :ueberauth_auth0, "2.1.0", "0632d5844049fa2f26823f15e1120aa32f27df6f27ce515a4b04641736594bf4", [:mix], [{:oauth2, "~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "8d3b30fa27c95c9e82c30c4afb016251405706d2e9627e603c3c9787fd1314fc"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "varint": {:hex, :varint, "1.4.0", "b7405c8a99db7b95d4341fa9cb15e7c3af6c8dda43e21bbe1c4a9cdff50b6502", [:mix], [], "hexpm", "0fd461901b7120c03467530dff3c58fa3475328fd75ba72c7d3cbf13bce6b0d2"}, From 90366deea0a8836331af5b07bf2d6cb90c3cc6a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:41:29 +0000 Subject: [PATCH 538/607] Bump @babel/core from 7.23.9 to 7.24.0 in /apps/block_scout_web/assets Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.23.9 to 7.24.0. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.24.0/packages/babel-core) --- updated-dependencies: - dependency-name: "@babel/core" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 122 +++++++++--------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index ad3d2da7c65f..c1231f8ad303 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -71,7 +71,7 @@ "xss": "^1.0.14" }, "devDependencies": { - "@babel/core": "^7.23.9", + "@babel/core": "^7.24.0", "@babel/preset-env": "^7.23.9", "autoprefixer": "^10.4.17", "babel-loader": "^9.1.3", @@ -249,20 +249,20 @@ } }, "node_modules/@babel/core": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", - "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", + "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.9", - "@babel/parser": "^7.23.9", - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9", + "@babel/helpers": "^7.24.0", + "@babel/parser": "^7.24.0", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.0", + "@babel/types": "^7.24.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -602,13 +602,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", - "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz", + "integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==", "dependencies": { - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9" + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.0", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -628,9 +628,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", + "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1946,22 +1946,22 @@ } }, "node_modules/@babel/template": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", - "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", "dependencies": { "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9" + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", - "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz", + "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", "dependencies": { "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", @@ -1969,8 +1969,8 @@ "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1979,9 +1979,9 @@ } }, "node_modules/@babel/types": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", - "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -18069,20 +18069,20 @@ "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==" }, "@babel/core": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", - "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", + "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.9", - "@babel/parser": "^7.23.9", - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9", + "@babel/helpers": "^7.24.0", + "@babel/parser": "^7.24.0", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.0", + "@babel/types": "^7.24.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -18332,13 +18332,13 @@ } }, "@babel/helpers": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", - "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz", + "integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==", "requires": { - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9" + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.0", + "@babel/types": "^7.24.0" } }, "@babel/highlight": { @@ -18352,9 +18352,9 @@ } }, "@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==" + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", + "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.23.3", @@ -19240,19 +19240,19 @@ } }, "@babel/template": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", - "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", "requires": { "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9" + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" } }, "@babel/traverse": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", - "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz", + "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", "requires": { "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", @@ -19260,16 +19260,16 @@ "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0", "debug": "^4.3.1", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", - "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "requires": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 610542626cd6..e6c33445458e 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -83,7 +83,7 @@ "xss": "^1.0.14" }, "devDependencies": { - "@babel/core": "^7.23.9", + "@babel/core": "^7.24.0", "@babel/preset-env": "^7.23.9", "autoprefixer": "^10.4.17", "babel-loader": "^9.1.3", From 0770e86037c3c242d539bd7b2528e96b09dcb920 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:41:53 +0000 Subject: [PATCH 539/607] Bump chart.js from 4.4.1 to 4.4.2 in /apps/block_scout_web/assets Bumps [chart.js](https://github.com/chartjs/Chart.js) from 4.4.1 to 4.4.2. - [Release notes](https://github.com/chartjs/Chart.js/releases) - [Commits](https://github.com/chartjs/Chart.js/compare/v4.4.1...v4.4.2) --- updated-dependencies: - dependency-name: chart.js dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 16 ++++++++-------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index ad3d2da7c65f..d87f206bbe67 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -14,7 +14,7 @@ "assert": "^2.1.0", "bignumber.js": "^9.1.2", "bootstrap": "^4.6.0", - "chart.js": "^4.4.1", + "chart.js": "^4.4.2", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", "core-js": "^3.36.0", @@ -5570,14 +5570,14 @@ } }, "node_modules/chart.js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", - "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz", + "integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==", "dependencies": { "@kurkle/color": "^0.3.0" }, "engines": { - "pnpm": ">=7" + "pnpm": ">=8" } }, "node_modules/chartjs-adapter-luxon": { @@ -22009,9 +22009,9 @@ "dev": true }, "chart.js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", - "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz", + "integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==", "requires": { "@kurkle/color": "^0.3.0" } diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 610542626cd6..0ac7eefb4e53 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -26,7 +26,7 @@ "assert": "^2.1.0", "bignumber.js": "^9.1.2", "bootstrap": "^4.6.0", - "chart.js": "^4.4.1", + "chart.js": "^4.4.2", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", "core-js": "^3.36.0", From 389bfb587d8b43ed16ce921bfd078544808b0f22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:42:18 +0000 Subject: [PATCH 540/607] Bump mini-css-extract-plugin in /apps/block_scout_web/assets Bumps [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) from 2.8.0 to 2.8.1. - [Release notes](https://github.com/webpack-contrib/mini-css-extract-plugin/releases) - [Changelog](https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/mini-css-extract-plugin/compare/v2.8.0...v2.8.1) --- updated-dependencies: - dependency-name: mini-css-extract-plugin dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index ad3d2da7c65f..6a019a303016 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -86,7 +86,7 @@ "file-loader": "^6.2.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "mini-css-extract-plugin": "^2.8.0", + "mini-css-extract-plugin": "^2.8.1", "postcss": "^8.4.35", "postcss-loader": "^8.1.0", "sass": "^1.71.1", @@ -12885,9 +12885,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.0.tgz", - "integrity": "sha512-CxmUYPFcTgET1zImteG/LZOy/4T5rTojesQXkSNBiquhydn78tfbCE9sjIjnJ/UcjNjOC1bphTCCW5rrS7cXAg==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz", + "integrity": "sha512-/1HDlyFRxWIZPI1ZpgqlZ8jMw/1Dp/dl3P0L1jtZ+zVcHqwPhGwaJwKL00WVgfnBy6PWCde9W65or7IIETImuA==", "dev": true, "dependencies": { "schema-utils": "^4.0.0", @@ -27655,9 +27655,9 @@ } }, "mini-css-extract-plugin": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.0.tgz", - "integrity": "sha512-CxmUYPFcTgET1zImteG/LZOy/4T5rTojesQXkSNBiquhydn78tfbCE9sjIjnJ/UcjNjOC1bphTCCW5rrS7cXAg==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz", + "integrity": "sha512-/1HDlyFRxWIZPI1ZpgqlZ8jMw/1Dp/dl3P0L1jtZ+zVcHqwPhGwaJwKL00WVgfnBy6PWCde9W65or7IIETImuA==", "dev": true, "requires": { "schema-utils": "^4.0.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 610542626cd6..a8bee2c25f17 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -98,7 +98,7 @@ "file-loader": "^6.2.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "mini-css-extract-plugin": "^2.8.0", + "mini-css-extract-plugin": "^2.8.1", "postcss": "^8.4.35", "postcss-loader": "^8.1.0", "sass": "^1.71.1", From fedca9678915152b226b85a4127e053ba5f56af2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:43:36 +0000 Subject: [PATCH 541/607] Bump postcss-loader from 8.1.0 to 8.1.1 in /apps/block_scout_web/assets Bumps [postcss-loader](https://github.com/webpack-contrib/postcss-loader) from 8.1.0 to 8.1.1. - [Release notes](https://github.com/webpack-contrib/postcss-loader/releases) - [Changelog](https://github.com/webpack-contrib/postcss-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/postcss-loader/compare/v8.1.0...v8.1.1) --- updated-dependencies: - dependency-name: postcss-loader dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index ad3d2da7c65f..c947701d7df3 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -88,7 +88,7 @@ "jest-environment-jsdom": "^29.7.0", "mini-css-extract-plugin": "^2.8.0", "postcss": "^8.4.35", - "postcss-loader": "^8.1.0", + "postcss-loader": "^8.1.1", "sass": "^1.71.1", "sass-loader": "^14.1.1", "style-loader": "^3.3.4", @@ -13920,9 +13920,9 @@ } }, "node_modules/postcss-loader": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.0.tgz", - "integrity": "sha512-AbperNcX3rlob7Ay7A/HQcrofug1caABBkopoFeOQMspZBqcqj6giYn1Bwey/0uiOPAcR+NQD0I2HC7rXzk91w==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", "dev": true, "dependencies": { "cosmiconfig": "^9.0.0", @@ -28396,9 +28396,9 @@ "requires": {} }, "postcss-loader": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.0.tgz", - "integrity": "sha512-AbperNcX3rlob7Ay7A/HQcrofug1caABBkopoFeOQMspZBqcqj6giYn1Bwey/0uiOPAcR+NQD0I2HC7rXzk91w==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", "dev": true, "requires": { "cosmiconfig": "^9.0.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 610542626cd6..1b2a1fc849ea 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -100,7 +100,7 @@ "jest-environment-jsdom": "^29.7.0", "mini-css-extract-plugin": "^2.8.0", "postcss": "^8.4.35", - "postcss-loader": "^8.1.0", + "postcss-loader": "^8.1.1", "sass": "^1.71.1", "sass-loader": "^14.1.1", "style-loader": "^3.3.4", From 9c2efc2c93fbc034b2ebf59d25d5b1243579ab70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:44:29 +0000 Subject: [PATCH 542/607] Bump @amplitude/analytics-browser in /apps/block_scout_web/assets Bumps [@amplitude/analytics-browser](https://github.com/amplitude/Amplitude-TypeScript) from 2.5.1 to 2.5.2. - [Release notes](https://github.com/amplitude/Amplitude-TypeScript/releases) - [Commits](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@2.5.1...@amplitude/analytics-browser@2.5.2) --- updated-dependencies: - dependency-name: "@amplitude/analytics-browser" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 30 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index ad3d2da7c65f..d9884bfee7ba 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -7,7 +7,7 @@ "name": "blockscout", "license": "GPL-3.0", "dependencies": { - "@amplitude/analytics-browser": "^2.5.1", + "@amplitude/analytics-browser": "^2.5.2", "@fortawesome/fontawesome-free": "^6.5.1", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", @@ -116,14 +116,14 @@ } }, "node_modules/@amplitude/analytics-browser": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.5.1.tgz", - "integrity": "sha512-WH31CyL5qPUSs7BY+udxDwWXmlBYfNJzCTwquWt/zUmd4aLJ7ec21rdN04RO4TCh68KpTGSb3l8K85J69fiVGw==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.5.2.tgz", + "integrity": "sha512-SjYjOEXO2it0dylVHv3n812ReA62c7GmxoeZCMMg9IFzgM54J4XIUTXYTDhQI/uGawjWZcsiuSqFgS/7LcwuSg==", "dependencies": { "@amplitude/analytics-client-common": "^2.1.0", "@amplitude/analytics-core": "^2.2.1", "@amplitude/analytics-types": "^2.5.0", - "@amplitude/plugin-page-view-tracking-browser": "^2.2.1", + "@amplitude/plugin-page-view-tracking-browser": "^2.2.2", "@amplitude/plugin-web-attribution-browser": "^2.1.3", "tslib": "^2.4.1" } @@ -174,9 +174,9 @@ "integrity": "sha512-aY69WxUvVlaCU+9geShjTsAYdUTvegEXH9i4WK/97kNbNLl4/7qUuIPe4hNireDeKLuQA9SA3H7TKynuNomDxw==" }, "node_modules/@amplitude/plugin-page-view-tracking-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.2.1.tgz", - "integrity": "sha512-KBB3nLFPns53haa4VaoWnomwxTX813mtoIYoq4pads+LjBvwUdgwZHsj/zEwYY7duolTUinxB9+Sx9lO6Ale2Q==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.2.2.tgz", + "integrity": "sha512-onsguK2fugi58vhW/NvuAqiQ7TG7IGmUKTg9Zfn39Y2IjudgT8N68LXz6esIY0XKQhZFHVsBT3pqYgEVK5I6Tg==", "dependencies": { "@amplitude/analytics-client-common": "^2.1.0", "@amplitude/analytics-types": "^2.5.0", @@ -17936,14 +17936,14 @@ "dev": true }, "@amplitude/analytics-browser": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.5.1.tgz", - "integrity": "sha512-WH31CyL5qPUSs7BY+udxDwWXmlBYfNJzCTwquWt/zUmd4aLJ7ec21rdN04RO4TCh68KpTGSb3l8K85J69fiVGw==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.5.2.tgz", + "integrity": "sha512-SjYjOEXO2it0dylVHv3n812ReA62c7GmxoeZCMMg9IFzgM54J4XIUTXYTDhQI/uGawjWZcsiuSqFgS/7LcwuSg==", "requires": { "@amplitude/analytics-client-common": "^2.1.0", "@amplitude/analytics-core": "^2.2.1", "@amplitude/analytics-types": "^2.5.0", - "@amplitude/plugin-page-view-tracking-browser": "^2.2.1", + "@amplitude/plugin-page-view-tracking-browser": "^2.2.2", "@amplitude/plugin-web-attribution-browser": "^2.1.3", "tslib": "^2.4.1" }, @@ -18000,9 +18000,9 @@ "integrity": "sha512-aY69WxUvVlaCU+9geShjTsAYdUTvegEXH9i4WK/97kNbNLl4/7qUuIPe4hNireDeKLuQA9SA3H7TKynuNomDxw==" }, "@amplitude/plugin-page-view-tracking-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.2.1.tgz", - "integrity": "sha512-KBB3nLFPns53haa4VaoWnomwxTX813mtoIYoq4pads+LjBvwUdgwZHsj/zEwYY7duolTUinxB9+Sx9lO6Ale2Q==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.2.2.tgz", + "integrity": "sha512-onsguK2fugi58vhW/NvuAqiQ7TG7IGmUKTg9Zfn39Y2IjudgT8N68LXz6esIY0XKQhZFHVsBT3pqYgEVK5I6Tg==", "requires": { "@amplitude/analytics-client-common": "^2.1.0", "@amplitude/analytics-types": "^2.5.0", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 610542626cd6..a76d19bd55d0 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^6.5.1", - "@amplitude/analytics-browser": "^2.5.1", + "@amplitude/analytics-browser": "^2.5.2", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", "assert": "^2.1.0", From 294931b3ad1f9b666ab7b721d1e6c940887470dc Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Mon, 4 Mar 2024 22:05:32 +0300 Subject: [PATCH 543/607] Update dependabot.yml --- .github/dependabot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b5eaa004db36..7ff4b3ed8af5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,7 +10,7 @@ updates: directory: "/apps/block_scout_web/assets" open-pull-requests-limit: 10 schedule: - interval: "weekly" + interval: "monthly" ignore: - dependency-name: "bootstrap" - dependency-name: "web3" @@ -20,4 +20,4 @@ updates: directory: "/apps/explorer" open-pull-requests-limit: 10 schedule: - interval: "weekly" + interval: "monthly" From c41b13365844e17dd2feff746095efa375ddf980 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 19:08:08 +0000 Subject: [PATCH 544/607] Bump @babel/preset-env in /apps/block_scout_web/assets Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.23.9 to 7.24.0. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.24.0/packages/babel-preset-env) --- updated-dependencies: - dependency-name: "@babel/preset-env" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 58 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index c1231f8ad303..031948a04856 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -72,7 +72,7 @@ }, "devDependencies": { "@babel/core": "^7.24.0", - "@babel/preset-env": "^7.23.9", + "@babel/preset-env": "^7.24.0", "autoprefixer": "^10.4.17", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", @@ -488,9 +488,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", "engines": { "node": ">=6.9.0" } @@ -1446,14 +1446,14 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", - "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.0.tgz", + "integrity": "sha512-y/yKMm7buHpFFXfxVFS4Vk1ToRJDilIa6fKRioB9Vjichv58TDGXTvqV0dN7plobAmTW5eSEGXDngE+Mm+uO+w==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-transform-parameters": "^7.23.3" }, @@ -1779,14 +1779,14 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.9.tgz", - "integrity": "sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.0.tgz", + "integrity": "sha512-ZxPEzV9IgvGn73iK0E6VB9/95Nd7aMFpbE0l8KQFDG70cOV9IxRP7Y2FUPmlK0v6ImlLqYX50iuZ3ZTVhOF2lA==", "dev": true, "dependencies": { "@babel/compat-data": "^7.23.5", "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-validator-option": "^7.23.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", @@ -1839,7 +1839,7 @@ "@babel/plugin-transform-new-target": "^7.23.3", "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", "@babel/plugin-transform-numeric-separator": "^7.23.4", - "@babel/plugin-transform-object-rest-spread": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.24.0", "@babel/plugin-transform-object-super": "^7.23.3", "@babel/plugin-transform-optional-catch-binding": "^7.23.4", "@babel/plugin-transform-optional-chaining": "^7.23.4", @@ -18254,9 +18254,9 @@ } }, "@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==" + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==" }, "@babel/helper-remap-async-to-generator": { "version": "7.22.20", @@ -18880,14 +18880,14 @@ } }, "@babel/plugin-transform-object-rest-spread": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", - "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.0.tgz", + "integrity": "sha512-y/yKMm7buHpFFXfxVFS4Vk1ToRJDilIa6fKRioB9Vjichv58TDGXTvqV0dN7plobAmTW5eSEGXDngE+Mm+uO+w==", "dev": true, "requires": { - "@babel/compat-data": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-transform-parameters": "^7.23.3" } @@ -19092,14 +19092,14 @@ } }, "@babel/preset-env": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.9.tgz", - "integrity": "sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.0.tgz", + "integrity": "sha512-ZxPEzV9IgvGn73iK0E6VB9/95Nd7aMFpbE0l8KQFDG70cOV9IxRP7Y2FUPmlK0v6ImlLqYX50iuZ3ZTVhOF2lA==", "dev": true, "requires": { "@babel/compat-data": "^7.23.5", "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-validator-option": "^7.23.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", @@ -19152,7 +19152,7 @@ "@babel/plugin-transform-new-target": "^7.23.3", "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", "@babel/plugin-transform-numeric-separator": "^7.23.4", - "@babel/plugin-transform-object-rest-spread": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.24.0", "@babel/plugin-transform-object-super": "^7.23.3", "@babel/plugin-transform-optional-catch-binding": "^7.23.4", "@babel/plugin-transform-optional-chaining": "^7.23.4", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index e6c33445458e..e87a5044b3b8 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -84,7 +84,7 @@ }, "devDependencies": { "@babel/core": "^7.24.0", - "@babel/preset-env": "^7.23.9", + "@babel/preset-env": "^7.24.0", "autoprefixer": "^10.4.17", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", From 0313fbb6d3b151216da05ad7cbd1946bdceb9f56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 19:08:36 +0000 Subject: [PATCH 545/607] Bump xss from 1.0.14 to 1.0.15 in /apps/block_scout_web/assets Bumps [xss](https://github.com/leizongmin/js-xss) from 1.0.14 to 1.0.15. - [Changelog](https://github.com/leizongmin/js-xss/blob/master/CHANGELOG.md) - [Commits](https://github.com/leizongmin/js-xss/compare/v1.0.14...v1.0.15) --- updated-dependencies: - dependency-name: xss dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 14 +++++++------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index c1231f8ad303..ae550c5219a1 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -68,7 +68,7 @@ "viewerjs": "^1.11.6", "web3": "^1.10.4", "web3modal": "^1.9.12", - "xss": "^1.0.14" + "xss": "^1.0.15" }, "devDependencies": { "@babel/core": "^7.24.0", @@ -17833,9 +17833,9 @@ "dev": true }, "node_modules/xss": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz", - "integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz", + "integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==", "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" @@ -31314,9 +31314,9 @@ "dev": true }, "xss": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz", - "integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz", + "integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==", "requires": { "commander": "^2.20.3", "cssfilter": "0.0.10" diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index e6c33445458e..e81dcd0502d1 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -80,7 +80,7 @@ "viewerjs": "^1.11.6", "web3": "^1.10.4", "web3modal": "^1.9.12", - "xss": "^1.0.14" + "xss": "^1.0.15" }, "devDependencies": { "@babel/core": "^7.24.0", From 31ba82001034a228e248cae835e5c79a15db0cf9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Mar 2024 05:43:31 +0000 Subject: [PATCH 546/607] Bump autoprefixer in /apps/block_scout_web/assets Bumps [autoprefixer](https://github.com/postcss/autoprefixer) from 10.4.17 to 10.4.18. - [Release notes](https://github.com/postcss/autoprefixer/releases) - [Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/autoprefixer/compare/10.4.17...10.4.18) --- updated-dependencies: - dependency-name: autoprefixer dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- apps/block_scout_web/assets/package-lock.json | 66 +++++++++---------- apps/block_scout_web/assets/package.json | 2 +- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index c2a4e175da77..f157ec0e8f50 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -73,7 +73,7 @@ "devDependencies": { "@babel/core": "^7.24.0", "@babel/preset-env": "^7.24.0", - "autoprefixer": "^10.4.17", + "autoprefixer": "^10.4.18", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", "css-loader": "^6.10.0", @@ -4634,9 +4634,9 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "node_modules/autoprefixer": { - "version": "10.4.17", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", - "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==", + "version": "10.4.18", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz", + "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==", "dev": true, "funding": [ { @@ -4653,8 +4653,8 @@ } ], "dependencies": { - "browserslist": "^4.22.2", - "caniuse-lite": "^1.0.30001578", + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001591", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", @@ -5237,9 +5237,9 @@ ] }, "node_modules/browserslist": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", - "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "funding": [ { "type": "opencollective", @@ -5255,8 +5255,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, @@ -5508,9 +5508,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001579", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz", - "integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==", + "version": "1.0.30001593", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001593.tgz", + "integrity": "sha512-UWM1zlo3cZfkpBysd7AS+z+v007q9G1+fLTUU42rQnY6t2axoogPW/xol6T7juU5EUoOhML4WgBIdG+9yYqAjQ==", "funding": [ { "type": "opencollective", @@ -6936,9 +6936,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.609", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.609.tgz", - "integrity": "sha512-ihiCP7PJmjoGNuLpl7TjNA8pCQWu09vGyjlPYw1Rqww4gvNuCcmvl+44G+2QyJ6S2K4o+wbTS++Xz0YN8Q9ERw==" + "version": "1.4.692", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.692.tgz", + "integrity": "sha512-d5rZRka9n2Y3MkWRN74IoAsxR0HK3yaAt7T50e3iT9VZmCCQDT3geXUO5ZRMhDToa1pkCeQXuNo+0g+NfDOVPA==" }, "node_modules/elliptic": { "version": "6.5.4", @@ -21321,13 +21321,13 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "autoprefixer": { - "version": "10.4.17", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", - "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==", + "version": "10.4.18", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz", + "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==", "dev": true, "requires": { - "browserslist": "^4.22.2", - "caniuse-lite": "^1.0.30001578", + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001591", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", @@ -21772,12 +21772,12 @@ } }, "browserslist": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", - "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "requires": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" } @@ -21970,9 +21970,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001579", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz", - "integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==" + "version": "1.0.30001593", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001593.tgz", + "integrity": "sha512-UWM1zlo3cZfkpBysd7AS+z+v007q9G1+fLTUU42rQnY6t2axoogPW/xol6T7juU5EUoOhML4WgBIdG+9yYqAjQ==" }, "caseless": { "version": "0.12.0", @@ -23022,9 +23022,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { - "version": "1.4.609", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.609.tgz", - "integrity": "sha512-ihiCP7PJmjoGNuLpl7TjNA8pCQWu09vGyjlPYw1Rqww4gvNuCcmvl+44G+2QyJ6S2K4o+wbTS++Xz0YN8Q9ERw==" + "version": "1.4.692", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.692.tgz", + "integrity": "sha512-d5rZRka9n2Y3MkWRN74IoAsxR0HK3yaAt7T50e3iT9VZmCCQDT3geXUO5ZRMhDToa1pkCeQXuNo+0g+NfDOVPA==" }, "elliptic": { "version": "6.5.4", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 0137b1b643b0..9033d43c5912 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -85,7 +85,7 @@ "devDependencies": { "@babel/core": "^7.24.0", "@babel/preset-env": "^7.24.0", - "autoprefixer": "^10.4.17", + "autoprefixer": "^10.4.18", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", "css-loader": "^6.10.0", From 627750338c7b7f239af0251e9ccc6f8325ee42ea Mon Sep 17 00:00:00 2001 From: MASDXI Date: Tue, 5 Mar 2024 20:44:05 +0700 Subject: [PATCH 547/607] - change txpool_besuTransactions to txpool_besuPendingTransactions - change runtime config enable pending transaction for besu client --- CHANGELOG.md | 1 + .../lib/ethereum_jsonrpc/pending_transaction.ex | 2 +- config/runtime.exs | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e3e97ece568..eb9e949043a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ### Fixes +- [#](https://github.com/blockscout/blockscout/pull/) - Fix fetch pending transaction for hyperledger besu client - [#9514](https://github.com/blockscout/blockscout/pull/9514) - Fix missing `0x` prefix for `blockNumber`, `logIndex`, `transactionIndex` and remove `transactionLogIndex` in `eth_getLogs` response. - [#9512](https://github.com/blockscout/blockscout/pull/9512) - Docker-compose 2.24.6 compatibility - [#9262](https://github.com/blockscout/blockscout/pull/9262) - Fix withdrawal status diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex index 90de6961f18f..246787876134 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex @@ -61,7 +61,7 @@ defmodule EthereumJSONRPC.PendingTransaction do {:ok, [Transaction.params()]} | {:error, reason :: term} def fetch_pending_transactions_besu(json_rpc_named_arguments) do with {:ok, transactions} <- - %{id: 1, method: "txpool_besuTransactions", params: []} + %{id: 1, method: "txpool_besuPendingTransactions", params: [512]} |> request() |> json_rpc(json_rpc_named_arguments) do transactions_params = diff --git a/config/runtime.exs b/config/runtime.exs index 9d879a1ce79a..95c4a65bd94e 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -561,8 +561,7 @@ config :indexer, Indexer.Fetcher.TransactionAction, config :indexer, Indexer.Fetcher.PendingTransaction.Supervisor, disabled?: - System.get_env("ETHEREUM_JSONRPC_VARIANT") == "besu" || - ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER") + ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER") config :indexer, Indexer.Fetcher.Token, concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_CONCURRENCY", 10) From 754e6f26cdc07793ecc4ac3e596c8db00a24f307 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Wed, 6 Mar 2024 11:31:43 +0700 Subject: [PATCH 548/607] adding comment in pending transaction besu --- .../lib/ethereum_jsonrpc/pending_transaction.ex | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex index 246787876134..f07e53396e56 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex @@ -61,7 +61,14 @@ defmodule EthereumJSONRPC.PendingTransaction do {:ok, [Transaction.params()]} | {:error, reason :: term} def fetch_pending_transactions_besu(json_rpc_named_arguments) do with {:ok, transactions} <- - %{id: 1, method: "txpool_besuPendingTransactions", params: [512]} + # `txpool_besuPendingTransactions` required parameter `numResults` for number of maximum pending transaction to return. + # + # TODO: Remove fix value when hyperledger besu client change `numResults` from required to optional parameter. + # Current fix value set to `256000` can handle pending transaction in Ethereum mainnet. + # according to https://etherscan.io/chart/pendingtx + # + # https://besu.hyperledger.org/public-networks/reference/api#txpool_besupendingtransactions + %{id: 1, method: "txpool_besuPendingTransactions", params: [256000]} |> request() |> json_rpc(json_rpc_named_arguments) do transactions_params = From c164e52098366428d6bd14e9f2dd6413b302fe1e Mon Sep 17 00:00:00 2001 From: MASDXI Date: Wed, 6 Mar 2024 13:35:55 +0700 Subject: [PATCH 549/607] add PR tag --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb9e949043a7..e0ec3add5a4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ ### Fixes -- [#](https://github.com/blockscout/blockscout/pull/) - Fix fetch pending transaction for hyperledger besu client +- [#9560](https://github.com/blockscout/blockscout/pull/9560) - Fix fetch pending transaction for hyperledger besu client - [#9514](https://github.com/blockscout/blockscout/pull/9514) - Fix missing `0x` prefix for `blockNumber`, `logIndex`, `transactionIndex` and remove `transactionLogIndex` in `eth_getLogs` response. - [#9512](https://github.com/blockscout/blockscout/pull/9512) - Docker-compose 2.24.6 compatibility - [#9262](https://github.com/blockscout/blockscout/pull/9262) - Fix withdrawal status From 534feaacb2d7dee3c1c4b049a8d8e0e9200e272e Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 6 Mar 2024 11:01:36 +0300 Subject: [PATCH 550/607] Add cancun evm version --- CHANGELOG.md | 1 + config/runtime.exs | 4 ++-- docker-compose/envs/common-blockscout.env | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e3e97ece568..881138ad4f74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ ### Chore +- [#9562](https://github.com/blockscout/blockscout/pull/9562) - Add cancun evm version - [#9260](https://github.com/blockscout/blockscout/pull/9260) - Optimism Delta upgrade support by Indexer.Fetcher.OptimismTxnBatch module - [#8740](https://github.com/blockscout/blockscout/pull/8740) - Add delay to Indexer.Fetcher.OptimismTxnBatch module initialization diff --git a/config/runtime.exs b/config/runtime.exs index 9d879a1ce79a..314485db1062 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -193,10 +193,10 @@ config :explorer, coin_name: System.get_env("COIN_NAME") || exchange_rates_coin || "ETH", allowed_solidity_evm_versions: System.get_env("CONTRACT_VERIFICATION_ALLOWED_SOLIDITY_EVM_VERSIONS") || - "homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,paris,shanghai,default", + "homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,paris,shanghai,cancun,default", allowed_vyper_evm_versions: System.get_env("CONTRACT_VERIFICATION_ALLOWED_VYPER_EVM_VERSIONS") || - "byzantium,constantinople,petersburg,istanbul,berlin,paris,shanghai,default", + "byzantium,constantinople,petersburg,istanbul,berlin,paris,shanghai,cancun,default", include_uncles_in_average_block_time: ConfigHelper.parse_bool_env_var("UNCLES_IN_AVERAGE_BLOCK_TIME"), healthy_blocks_period: ConfigHelper.parse_time_env_var("HEALTHY_BLOCKS_PERIOD", "5m"), realtime_events_sender: diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 5738154d2bda..959db856b6f4 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -91,8 +91,8 @@ CACHE_ADDRESS_TRANSACTIONS_COUNTER_PERIOD=1800 CACHE_ADDRESS_TOKENS_USD_SUM_PERIOD=3600 CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD=1800 TOKEN_METADATA_UPDATE_INTERVAL=172800 -CONTRACT_VERIFICATION_ALLOWED_SOLIDITY_EVM_VERSIONS=homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,paris,shanghai,default -CONTRACT_VERIFICATION_ALLOWED_VYPER_EVM_VERSIONS=byzantium,constantinople,petersburg,istanbul,berlin,paris,shanghai,default +CONTRACT_VERIFICATION_ALLOWED_SOLIDITY_EVM_VERSIONS=homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,paris,shanghai,cancun,default +CONTRACT_VERIFICATION_ALLOWED_VYPER_EVM_VERSIONS=byzantium,constantinople,petersburg,istanbul,berlin,paris,shanghai,cancun,default # CONTRACT_VERIFICATION_MAX_LIBRARIES=10 CONTRACT_MAX_STRING_LENGTH_WITHOUT_TRIMMING=2040 # CONTRACT_DISABLE_INTERACTION= From 07ca08cf676d47f15a82cbdefae458b9ac172923 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Thu, 7 Mar 2024 10:48:11 +0700 Subject: [PATCH 551/607] Change value txpool_besuPendingTransactions to 512 --- .../lib/ethereum_jsonrpc/pending_transaction.ex | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex index f07e53396e56..14bf1d878323 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex @@ -64,11 +64,14 @@ defmodule EthereumJSONRPC.PendingTransaction do # `txpool_besuPendingTransactions` required parameter `numResults` for number of maximum pending transaction to return. # # TODO: Remove fix value when hyperledger besu client change `numResults` from required to optional parameter. - # Current fix value set to `256000` can handle pending transaction in Ethereum mainnet. - # according to https://etherscan.io/chart/pendingtx + # Current fix value set to `512` bonsai storage default value is 512. + # to handle pending transaction in Ethereum mainnet require more than 100000. + # reference: + # https://etherscan.io/chart/pendingtx + # https://besu.hyperledger.org/public-networks/reference/cli/options#bonsai-historical-block-limit # # https://besu.hyperledger.org/public-networks/reference/api#txpool_besupendingtransactions - %{id: 1, method: "txpool_besuPendingTransactions", params: [256000]} + %{id: 1, method: "txpool_besuPendingTransactions", params: [512]} |> request() |> json_rpc(json_rpc_named_arguments) do transactions_params = From 539d40fc1bedb46275fa82515a54c8cac5a44a29 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Thu, 7 Mar 2024 10:56:49 +0300 Subject: [PATCH 552/607] Fix get_blocks_by_events function --- apps/indexer/lib/indexer/helper.ex | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/indexer/lib/indexer/helper.ex b/apps/indexer/lib/indexer/helper.ex index 9f4983799e47..08552d626795 100644 --- a/apps/indexer/lib/indexer/helper.ex +++ b/apps/indexer/lib/indexer/helper.ex @@ -240,13 +240,7 @@ defmodule Indexer.Helper do def get_blocks_by_events(events, json_rpc_named_arguments, retries) do events |> Enum.reduce(%{}, fn event, acc -> - block_number = - if is_map(event) do - event.block_number - else - event["blockNumber"] - end - + block_number = Map.get(event, :block_number, event["blockNumber"]) Map.put(acc, block_number, 0) end) |> Stream.map(fn {block_number, _} -> %{number: block_number} end) From 168c349109b63bc938a7f5d5148e28f109ff5f30 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:03:05 +0300 Subject: [PATCH 553/607] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 881138ad4f74..726b9752144e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ### Fixes +- [#9572](https://github.com/blockscout/blockscout/pull/9572) - Fix Shibarium L1 fetcher - [#9514](https://github.com/blockscout/blockscout/pull/9514) - Fix missing `0x` prefix for `blockNumber`, `logIndex`, `transactionIndex` and remove `transactionLogIndex` in `eth_getLogs` response. - [#9512](https://github.com/blockscout/blockscout/pull/9512) - Docker-compose 2.24.6 compatibility - [#9262](https://github.com/blockscout/blockscout/pull/9262) - Fix withdrawal status From c81f9004ab6f696f589bf6f1ed5cb4fbbd2934f2 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 8 Mar 2024 12:38:55 +0300 Subject: [PATCH 554/607] Prerelease workflow for Ethereum --- .github/workflows/prerelease.yml | 45 +++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index b47c998c8a35..7eba93b933d0 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -25,18 +25,15 @@ jobs: docker-username: ${{ secrets.DOCKER_USERNAME }} docker-password: ${{ secrets.DOCKER_PASSWORD }} - - name: Build & Push Docker image + - name: Build and push Docker image for Ethereum uses: docker/build-push-action@v5 with: context: . file: ./docker/Dockerfile push: true - cache-from: type=registry,ref=blockscout/blockscout:buildcache - cache-to: type=registry,ref=blockscout/blockscout:buildcache,mode=max - tags: blockscout/blockscout:master, blockscout/blockscout:latest, blockscout/blockscout:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + tags: blockscout/blockscout-ethereum:latest, blockscout/blockscout-ethereum:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} platforms: | linux/amd64 - linux/arm64/v8 build-args: | CACHE_EXCHANGE_RATES_PERIOD= API_V1_READ_METHODS_DISABLED=false @@ -44,14 +41,10 @@ jobs: API_V1_WRITE_METHODS_DISABLED=false CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= ADMIN_PANEL_ENABLED=false - DECODE_NOT_A_CONTRACT_CALLS=false - MIXPANEL_URL= - MIXPANEL_TOKEN= - AMPLITUDE_URL= - AMPLITUDE_API_KEY= CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= - BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=ethereum - name: Build & Push Docker image for Shibarium uses: docker/build-push-action@v5 @@ -74,4 +67,32 @@ jobs: CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=shibarium \ No newline at end of file + CHAIN_TYPE=shibarium + + - name: Build & Push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + cache-from: type=registry,ref=blockscout/blockscout:buildcache + cache-to: type=registry,ref=blockscout/blockscout:buildcache,mode=max + tags: blockscout/blockscout:master, blockscout/blockscout:latest, blockscout/blockscout:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} \ No newline at end of file From 528b64ca8c12ff6706f473ea29c3a10058ac2ea3 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Wed, 6 Mar 2024 11:57:50 +0300 Subject: [PATCH 555/607] Fix timestamp handler for unfinalized zkEVM batches --- .../explorer/chain/polygon_zkevm/transaction_batch.ex | 4 ++-- .../20240306080627_make_timestamp_optional.exs | 9 +++++++++ .../indexer/fetcher/polygon_zkevm/transaction_batch.ex | 9 ++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 apps/explorer/priv/polygon_zkevm/migrations/20240306080627_make_timestamp_optional.exs diff --git a/apps/explorer/lib/explorer/chain/polygon_zkevm/transaction_batch.ex b/apps/explorer/lib/explorer/chain/polygon_zkevm/transaction_batch.ex index b602ff092254..92ca1dd21527 100644 --- a/apps/explorer/lib/explorer/chain/polygon_zkevm/transaction_batch.ex +++ b/apps/explorer/lib/explorer/chain/polygon_zkevm/transaction_batch.ex @@ -6,9 +6,9 @@ defmodule Explorer.Chain.PolygonZkevm.TransactionBatch do alias Explorer.Chain.Hash alias Explorer.Chain.PolygonZkevm.{BatchTransaction, LifecycleTransaction} - @optional_attrs ~w(sequence_id verify_id)a + @optional_attrs ~w(timestamp sequence_id verify_id)a - @required_attrs ~w(number timestamp l2_transactions_count global_exit_root acc_input_hash state_root)a + @required_attrs ~w(number l2_transactions_count global_exit_root acc_input_hash state_root)a @primary_key false typed_schema "polygon_zkevm_transaction_batches" do diff --git a/apps/explorer/priv/polygon_zkevm/migrations/20240306080627_make_timestamp_optional.exs b/apps/explorer/priv/polygon_zkevm/migrations/20240306080627_make_timestamp_optional.exs new file mode 100644 index 000000000000..f813ba8ba735 --- /dev/null +++ b/apps/explorer/priv/polygon_zkevm/migrations/20240306080627_make_timestamp_optional.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.PolygonZkevm.Migrations.MakeTimestampOptional do + use Ecto.Migration + + def change do + alter table("polygon_zkevm_transaction_batches") do + modify(:timestamp, :"timestamp without time zone", null: true) + end + end +end diff --git a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex index 278682628fe8..ff4122020f6d 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex @@ -209,7 +209,14 @@ defmodule Indexer.Fetcher.PolygonZkevm.TransactionBatch do {batches, l2_txs, l1_txs, next_id, hash_to_id} = _acc -> number = quantity_to_integer(Map.get(res.result, "number")) - {:ok, timestamp} = DateTime.from_unix(quantity_to_integer(Map.get(res.result, "timestamp"))) + + # the timestamp is undefined for unfinalized batches + timestamp = + case DateTime.from_unix(quantity_to_integer(Map.get(res.result, "timestamp"))) do + {:ok, ts} -> ts + _ -> nil + end + l2_transaction_hashes = Map.get(res.result, "transactions") global_exit_root = Map.get(res.result, "globalExitRoot") acc_input_hash = Map.get(res.result, "accInputHash") From 59e461435bc3c2183e2b1bab64537f57846bbffb Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Wed, 6 Mar 2024 12:02:10 +0300 Subject: [PATCH 556/607] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 726b9752144e..fff148e54dc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ ### Fixes - [#9572](https://github.com/blockscout/blockscout/pull/9572) - Fix Shibarium L1 fetcher +- [#9563](https://github.com/blockscout/blockscout/pull/9563) - Fix timestamp handler for unfinalized zkEVM batches - [#9514](https://github.com/blockscout/blockscout/pull/9514) - Fix missing `0x` prefix for `blockNumber`, `logIndex`, `transactionIndex` and remove `transactionLogIndex` in `eth_getLogs` response. - [#9512](https://github.com/blockscout/blockscout/pull/9512) - Docker-compose 2.24.6 compatibility - [#9262](https://github.com/blockscout/blockscout/pull/9262) - Fix withdrawal status From dbf9f31115c04b5c9f673f840a82f97bfbb09fde Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Wed, 6 Mar 2024 12:27:31 +0300 Subject: [PATCH 557/607] Handle non-existent timestamp --- .../lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex index ff4122020f6d..0f04d8302a19 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex @@ -212,7 +212,7 @@ defmodule Indexer.Fetcher.PolygonZkevm.TransactionBatch do # the timestamp is undefined for unfinalized batches timestamp = - case DateTime.from_unix(quantity_to_integer(Map.get(res.result, "timestamp"))) do + case DateTime.from_unix(quantity_to_integer(Map.get(res.result, "timestamp", 0xFFFFFFFFFFFFFFFF))) do {:ok, ts} -> ts _ -> nil end From 55503b41929e37366316436d771b7b92c96f367a Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Fri, 8 Mar 2024 21:33:26 +0800 Subject: [PATCH 558/607] update log info --- apps/explorer/lib/explorer/market/history/cataloger.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/market/history/cataloger.ex b/apps/explorer/lib/explorer/market/history/cataloger.ex index ad53c64ff7ed..e52052a711d0 100644 --- a/apps/explorer/lib/explorer/market/history/cataloger.ex +++ b/apps/explorer/lib/explorer/market/history/cataloger.ex @@ -119,7 +119,7 @@ defmodule Explorer.Market.History.Cataloger do # Failed to get records. Try again. @impl GenServer def handle_info({_ref, {:tvl_history, {day_count, failed_attempts, :error}}}, state) do - Logger.warn(fn -> "Failed to fetch market cap history. Trying again." end) + Logger.warn(fn -> "Failed to fetch tvl history. Trying again." end) fetch_tvl_history(day_count, failed_attempts + 1) From cfdd5b6f921ab99ebc0a06b55def501b87f6c27c Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Sun, 10 Mar 2024 17:58:18 +0300 Subject: [PATCH 559/607] Aff fallback for OTP_VERSION and ELIXIR_VERSION in the main CI workflow --- .github/workflows/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index f0dc78055cca..1e74caf41a56 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -33,8 +33,8 @@ on: env: MIX_ENV: test - OTP_VERSION: ${{ vars.OTP_VERSION }} - ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + OTP_VERSION: ${{ vars.OTP_VERSION || '25.3.2.8' }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION || '1.14.5' }} ACCOUNT_AUTH0_DOMAIN: "blockscoutcom.us.auth0.com" jobs: From 7a7a424f9e640c2661f80c8ef1f10f7582a9eb28 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Sun, 10 Mar 2024 18:26:55 +0300 Subject: [PATCH 560/607] Fix format and cspell tests --- .../ethereum_jsonrpc/pending_transaction.ex | 20 +++++++++---------- config/runtime.exs | 3 +-- cspell.json | 1 + 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex index 14bf1d878323..cfce452c8647 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/pending_transaction.ex @@ -60,17 +60,17 @@ defmodule EthereumJSONRPC.PendingTransaction do @spec fetch_pending_transactions_besu(EthereumJSONRPC.json_rpc_named_arguments()) :: {:ok, [Transaction.params()]} | {:error, reason :: term} def fetch_pending_transactions_besu(json_rpc_named_arguments) do + # `txpool_besuPendingTransactions` required parameter `numResults` for number of maximum pending transaction to return. + # + # TODO: Remove fix value when hyperledger besu client change `numResults` from required to optional parameter. + # Current fix value set to `512` bonsai storage default value is 512. + # to handle pending transaction in Ethereum mainnet require more than 100000. + # reference: + # https://etherscan.io/chart/pendingtx + # https://besu.hyperledger.org/public-networks/reference/cli/options#bonsai-historical-block-limit + # + # https://besu.hyperledger.org/public-networks/reference/api#txpool_besupendingtransactions with {:ok, transactions} <- - # `txpool_besuPendingTransactions` required parameter `numResults` for number of maximum pending transaction to return. - # - # TODO: Remove fix value when hyperledger besu client change `numResults` from required to optional parameter. - # Current fix value set to `512` bonsai storage default value is 512. - # to handle pending transaction in Ethereum mainnet require more than 100000. - # reference: - # https://etherscan.io/chart/pendingtx - # https://besu.hyperledger.org/public-networks/reference/cli/options#bonsai-historical-block-limit - # - # https://besu.hyperledger.org/public-networks/reference/api#txpool_besupendingtransactions %{id: 1, method: "txpool_besuPendingTransactions", params: [512]} |> request() |> json_rpc(json_rpc_named_arguments) do diff --git a/config/runtime.exs b/config/runtime.exs index 8f18085665d2..cefab56efcdf 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -560,8 +560,7 @@ config :indexer, Indexer.Fetcher.TransactionAction, ) config :indexer, Indexer.Fetcher.PendingTransaction.Supervisor, - disabled?: - ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER") + disabled?: ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER") config :indexer, Indexer.Fetcher.Token, concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_CONCURRENCY", 10) diff --git a/cspell.json b/cspell.json index e3c50bb6d418..fa02ef75e07e 100644 --- a/cspell.json +++ b/cspell.json @@ -211,6 +211,7 @@ "hljs", "Hodl", "httpoison", + "hyperledger", "ifdef", "ifeq", "Iframe", From 1ab80877ce99c2d8c5cbd0a9662d48bffb0ec77d Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 5 Mar 2024 12:39:23 +0300 Subject: [PATCH 561/607] Fix EIP-1967 beacon proxy pattern detection --- CHANGELOG.md | 1 + .../controllers/smart_contract_controller_test.exs | 2 +- apps/explorer/lib/explorer/chain/smart_contract/proxy.ex | 4 ++-- .../lib/explorer/chain/smart_contract/proxy/eip_1967.ex | 4 ++-- .../lib/explorer/chain/smart_contract/proxy/master_copy.ex | 4 ++-- .../test/explorer/chain/smart_contract/proxy_test.exs | 2 +- apps/explorer/test/explorer/chain/smart_contract_test.exs | 4 ++-- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b349ed60de73..d60943c096f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - [#9572](https://github.com/blockscout/blockscout/pull/9572) - Fix Shibarium L1 fetcher - [#9563](https://github.com/blockscout/blockscout/pull/9563) - Fix timestamp handler for unfinalized zkEVM batches - [#9560](https://github.com/blockscout/blockscout/pull/9560) - Fix fetch pending transaction for hyperledger besu client +- [#9555](https://github.com/blockscout/blockscout/pull/9555) - Fix EIP-1967 beacon proxy pattern detection - [#9514](https://github.com/blockscout/blockscout/pull/9514) - Fix missing `0x` prefix for `blockNumber`, `logIndex`, `transactionIndex` and remove `transactionLogIndex` in `eth_getLogs` response. - [#9512](https://github.com/blockscout/blockscout/pull/9512) - Docker-compose 2.24.6 compatibility - [#9262](https://github.com/blockscout/blockscout/pull/9262) - Fix withdrawal status diff --git a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs index 8d2889876964..5ecc075a44fa 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs @@ -288,7 +288,7 @@ defmodule BlockScoutWeb.SmartContractControllerTest do ] }, _options -> - {:ok, "0xcebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"} + {:ok, "0x000000000000000000000000" <> "cebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"} end) end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex index 845f8f6e08e8..9bec8a284a63 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -149,8 +149,8 @@ defmodule Explorer.Chain.SmartContract.Proxy do when is_burn_signature_or_nil(empty_address_hash_string) -> nil - {:ok, implementation_logic_address_hash_string} -> - implementation_logic_address_hash_string + {:ok, "0x000000000000000000000000" <> implementation_logic_address_hash_string} -> + "0x" <> implementation_logic_address_hash_string _ -> nil diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex index 5cc06c75ed9e..a80f0a94a45f 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex @@ -75,10 +75,10 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do when is_burn_signature_or_nil(empty_address) -> nil - {:ok, beacon_contract_address} -> + {:ok, "0x000000000000000000000000" <> beacon_contract_address} -> case @implementation_signature |> Basic.get_implementation_address_hash_string( - beacon_contract_address, + "0x" <> beacon_contract_address, implementation_method_abi ) do <> -> diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex index 97b927981392..0680ba64ad29 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex @@ -29,8 +29,8 @@ defmodule Explorer.Chain.SmartContract.Proxy.MasterCopy do when is_burn_signature(empty_address) -> {:ok, "0x"} - {:ok, logic_contract_address} -> - {:ok, logic_contract_address} + {:ok, "0x000000000000000000000000" <> logic_contract_address} -> + {:ok, "0x" <> logic_contract_address} _ -> {:ok, nil} diff --git a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs index f5fd83b562a2..582355a9ad45 100644 --- a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs @@ -442,7 +442,7 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do id: _id, method: "eth_call", params: [ - %{data: "0x5c60da1b", to: "0x000000000000000000000000" <> ^beacon_contract_address_hash_string}, + %{data: "0x5c60da1b", to: "0x" <> ^beacon_contract_address_hash_string}, "latest" ] } diff --git a/apps/explorer/test/explorer/chain/smart_contract_test.exs b/apps/explorer/test/explorer/chain/smart_contract_test.exs index f0e5b2075e3f..6eda196e2108 100644 --- a/apps/explorer/test/explorer/chain/smart_contract_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract_test.exs @@ -119,7 +119,7 @@ defmodule Explorer.Chain.SmartContractTest do ] }, _options -> - {:ok, string_implementation_address_hash} + {:ok, "0x000000000000000000000000" <> string_implementation_address_hash} end) assert {^string_implementation_address_hash, "proxy"} = @@ -834,7 +834,7 @@ defmodule Explorer.Chain.SmartContractTest do ] }, _options -> - {:ok, string_implementation_address_hash} + {:ok, "0x000000000000000000000000" <> string_implementation_address_hash} end) end From 70fdae23d0f0c6919cdbc248959a5c4a216b4618 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 5 Mar 2024 21:42:02 +0300 Subject: [PATCH 562/607] Processing of eth_getStorageAt might return less 32 bytes --- .../smart_contract_controller_test.exs | 2 +- .../lib/explorer/chain/smart_contract.ex | 6 +- .../explorer/chain/smart_contract/proxy.ex | 40 ++++++++- .../chain/smart_contract/proxy/eip_1967.ex | 53 ++++++----- .../chain/smart_contract/proxy/master_copy.ex | 5 +- .../chain/smart_contract/proxy_test.exs | 90 +++++++++++++++++++ .../explorer/chain/smart_contract_test.exs | 4 +- 7 files changed, 164 insertions(+), 36 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs index 5ecc075a44fa..8d2889876964 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs @@ -288,7 +288,7 @@ defmodule BlockScoutWeb.SmartContractControllerTest do ] }, _options -> - {:ok, "0x000000000000000000000000" <> "cebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"} + {:ok, "0xcebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"} end) end diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index 7d5ba48d0c96..cb9ac24ea5a6 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -41,9 +41,7 @@ defmodule Explorer.Chain.SmartContract do @burn_address_hash_string "0x0000000000000000000000000000000000000000" @burn_address_hash_string_32 "0x0000000000000000000000000000000000000000000000000000000000000000" - defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string_32] - defguard is_burn_signature_or_nil(term) when is_burn_signature(term) or term == nil - defguard is_burn_signature_extended(term) when is_burn_signature(term) or term == @burn_address_hash_string + defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string, @burn_address_hash_string_32] @doc """ Returns burn address hash @@ -594,7 +592,7 @@ defmodule Explorer.Chain.SmartContract do def save_implementation_data(nil, _, _, _), do: {nil, nil} def save_implementation_data(empty_address_hash_string, proxy_address_hash, metadata_from_verified_twin, options) - when is_burn_signature_extended(empty_address_hash_string) do + when is_burn_signature(empty_address_hash_string) do if is_nil(metadata_from_verified_twin) or !metadata_from_verified_twin do proxy_address_hash |> address_hash_to_smart_contract_without_twin(options) diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex index 9bec8a284a63..a4235c1f703f 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -13,7 +13,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do string_to_address_hash: 1 ] - import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0, is_burn_signature_or_nil: 1] + import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0, is_burn_signature: 1] # supported signatures: # 5c60da1b = keccak256(implementation()) @@ -146,11 +146,11 @@ defmodule Explorer.Chain.SmartContract.Proxy do json_rpc_named_arguments ) do {:ok, empty_address_hash_string} - when is_burn_signature_or_nil(empty_address_hash_string) -> + when is_burn_signature(empty_address_hash_string) -> nil - {:ok, "0x000000000000000000000000" <> implementation_logic_address_hash_string} -> - "0x" <> implementation_logic_address_hash_string + {:ok, "0x" <> storage_value} -> + extract_address_hex_from_storage_pointer(storage_value) _ -> nil @@ -289,4 +289,36 @@ defmodule Explorer.Chain.SmartContract.Proxy do Map.get(input, "name") == name end) end + + @doc """ + Decodes 20 bytes address hex from smart-contract storage pointer value + """ + @spec extract_address_hex_from_storage_pointer(binary) :: binary + def extract_address_hex_from_storage_pointer(storage_value) when is_binary(storage_value) do + hex_bytes = Base.decode16!(storage_value, case: :mixed) + hex_bytes_count = byte_size(hex_bytes) + + cond do + hex_bytes_count == 20 -> + "0x" <> storage_value + + hex_bytes_count > 20 -> + << + reversed_address_hex::binary-size(20), + _right::binary + >> = hex_bytes |> :binary.bin_to_list() |> Enum.reverse() |> :binary.list_to_bin() + + hex = + reversed_address_hex + |> :binary.bin_to_list() + |> Enum.reverse() + |> :binary.list_to_bin() + |> Base.encode16(case: :lower) + + "0x" <> hex + + hex_bytes_count < 20 -> + "0x" <> String.pad_leading(storage_value, 40) + end + end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex index a80f0a94a45f..24549c96a132 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex @@ -7,7 +7,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do alias Explorer.Chain.SmartContract.Proxy alias Explorer.Chain.SmartContract.Proxy.Basic - import Explorer.Chain.SmartContract, only: [is_burn_signature_or_nil: 1] + import Explorer.Chain.SmartContract, only: [is_burn_signature: 1] # supported signatures: # 5c60da1b = keccak256(implementation()) @@ -65,31 +65,38 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do } ] - case Contract.eth_get_storage_at_request( - proxy_address_hash, - storage_slot_beacon_contract_address, - nil, - json_rpc_named_arguments - ) do - {:ok, empty_address} - when is_burn_signature_or_nil(empty_address) -> - nil + beacon_contract_address = + case Contract.eth_get_storage_at_request( + proxy_address_hash, + storage_slot_beacon_contract_address, + nil, + json_rpc_named_arguments + ) do + {:ok, empty_address} + when is_burn_signature(empty_address) -> + nil - {:ok, "0x000000000000000000000000" <> beacon_contract_address} -> - case @implementation_signature - |> Basic.get_implementation_address_hash_string( - "0x" <> beacon_contract_address, - implementation_method_abi - ) do - <> -> - implementation_address + {:ok, "0x" <> storage_value} -> + Proxy.extract_address_hex_from_storage_pointer(storage_value) - _ -> - nil - end + _ -> + nil + end + + if beacon_contract_address do + case @implementation_signature + |> Basic.get_implementation_address_hash_string( + beacon_contract_address, + implementation_method_abi + ) do + <> -> + implementation_address - _ -> - nil + _ -> + nil + end + else + nil end end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex index 0680ba64ad29..ce5a7aed49aa 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex @@ -29,8 +29,9 @@ defmodule Explorer.Chain.SmartContract.Proxy.MasterCopy do when is_burn_signature(empty_address) -> {:ok, "0x"} - {:ok, "0x000000000000000000000000" <> logic_contract_address} -> - {:ok, "0x" <> logic_contract_address} + {:ok, "0x" <> storage_value} -> + logic_contract_address = Proxy.extract_address_hex_from_storage_pointer(storage_value) + {:ok, logic_contract_address} _ -> {:ok, nil} diff --git a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs index 582355a9ad45..01d7101f3a82 100644 --- a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs @@ -466,4 +466,94 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do assert implementation_abi == @implementation_abi end + + test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern (beacon contract) when eth_getStorageAt returns less 32 bytes" do + proxy_contract_address = insert(:contract_address) + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") + + beacon_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: beacon_contract_address.hash, + abi: @beacon_abi, + contract_code_md5: "123" + ) + + beacon_contract_address_hash_string = Base.encode16(beacon_contract_address.hash.bytes, case: :lower) + + implementation_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: implementation_contract_address.hash, + abi: @implementation_abi, + contract_code_md5: "123" + ) + + implementation_contract_address_hash_string = + Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + + EthereumJSONRPC.Mox + |> expect( + :json_rpc, + fn %{ + id: _id, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000"} + end + ) + |> expect( + :json_rpc, + fn %{ + id: _id, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x" <> beacon_contract_address_hash_string} + end + ) + |> expect( + :json_rpc, + fn [ + %{ + id: _id, + method: "eth_call", + params: [ + %{data: "0x5c60da1b", to: "0x" <> ^beacon_contract_address_hash_string}, + "latest" + ] + } + ], + _options -> + { + :ok, + [ + %{ + id: _id, + jsonrpc: "2.0", + result: "0x000000000000000000000000" <> implementation_contract_address_hash_string + } + ] + } + end + ) + + implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, []) + verify!(EthereumJSONRPC.Mox) + + assert implementation_abi == @implementation_abi + end end diff --git a/apps/explorer/test/explorer/chain/smart_contract_test.exs b/apps/explorer/test/explorer/chain/smart_contract_test.exs index 6eda196e2108..f0e5b2075e3f 100644 --- a/apps/explorer/test/explorer/chain/smart_contract_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract_test.exs @@ -119,7 +119,7 @@ defmodule Explorer.Chain.SmartContractTest do ] }, _options -> - {:ok, "0x000000000000000000000000" <> string_implementation_address_hash} + {:ok, string_implementation_address_hash} end) assert {^string_implementation_address_hash, "proxy"} = @@ -834,7 +834,7 @@ defmodule Explorer.Chain.SmartContractTest do ] }, _options -> - {:ok, "0x000000000000000000000000" <> string_implementation_address_hash} + {:ok, string_implementation_address_hash} end) end From ce36a5d71443fba60e680c4b9ad68a2a19c65120 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 5 Mar 2024 22:51:18 +0300 Subject: [PATCH 563/607] Fix case for less 20 bytes in response --- .../explorer/chain/smart_contract/proxy.ex | 2 +- .../chain/smart_contract/proxy_test.exs | 129 +++++++++--------- 2 files changed, 69 insertions(+), 62 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex index a4235c1f703f..22a5ab5d3d24 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -318,7 +318,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do "0x" <> hex hex_bytes_count < 20 -> - "0x" <> String.pad_leading(storage_value, 40) + "0x" <> String.pad_leading(storage_value, 40, "0") end end end diff --git a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs index 01d7101f3a82..c61b10c795a4 100644 --- a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs @@ -404,61 +404,49 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do implementation_contract_address_hash_string = Base.encode16(implementation_contract_address.hash.bytes, case: :lower) - EthereumJSONRPC.Mox - |> expect( - :json_rpc, - fn %{ - id: _id, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end + eip_1967_beacon_proxy_mock_requests( + beacon_contract_address_hash_string, + implementation_contract_address_hash_string, + :full_32 ) - |> expect( - :json_rpc, - fn %{ - id: _id, - method: "eth_getStorageAt", - params: [ - _, - "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", - "latest" - ] - }, - _options -> - {:ok, "0x000000000000000000000000" <> beacon_contract_address_hash_string} - end + + implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, []) + verify!(EthereumJSONRPC.Mox) + + assert implementation_abi == @implementation_abi + end + + test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern (beacon contract) when eth_getStorageAt returns 20 bytes address" do + proxy_contract_address = insert(:contract_address) + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") + + beacon_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: beacon_contract_address.hash, + abi: @beacon_abi, + contract_code_md5: "123" ) - |> expect( - :json_rpc, - fn [ - %{ - id: _id, - method: "eth_call", - params: [ - %{data: "0x5c60da1b", to: "0x" <> ^beacon_contract_address_hash_string}, - "latest" - ] - } - ], - _options -> - { - :ok, - [ - %{ - id: _id, - jsonrpc: "2.0", - result: "0x000000000000000000000000" <> implementation_contract_address_hash_string - } - ] - } - end + + beacon_contract_address_hash_string = Base.encode16(beacon_contract_address.hash.bytes, case: :lower) + + implementation_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: implementation_contract_address.hash, + abi: @implementation_abi, + contract_code_md5: "123" + ) + + implementation_contract_address_hash_string = + Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + + eip_1967_beacon_proxy_mock_requests( + beacon_contract_address_hash_string, + implementation_contract_address_hash_string, + :exact_20 ) implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, []) @@ -467,7 +455,7 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do assert implementation_abi == @implementation_abi end - test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern (beacon contract) when eth_getStorageAt returns less 32 bytes" do + test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern (beacon contract) when eth_getStorageAt returns less 20 bytes address" do proxy_contract_address = insert(:contract_address) smart_contract = @@ -494,6 +482,30 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do implementation_contract_address_hash_string = Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + eip_1967_beacon_proxy_mock_requests( + beacon_contract_address_hash_string, + implementation_contract_address_hash_string, + :short + ) + + implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, []) + verify!(EthereumJSONRPC.Mox) + + assert implementation_abi == @implementation_abi + end + + defp eip_1967_beacon_proxy_mock_requests( + beacon_contract_address_hash_string, + implementation_contract_address_hash_string, + mode + ) do + response = + case mode do + :full_32 -> "0x000000000000000000000000" <> beacon_contract_address_hash_string + :exact_20 -> "0x" <> beacon_contract_address_hash_string + :short -> "0x" <> String.slice(beacon_contract_address_hash_string, 10..-1) + end + EthereumJSONRPC.Mox |> expect( :json_rpc, @@ -507,7 +519,7 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do ] }, _options -> - {:ok, "0x0000000000000000000000000000000000000000"} + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end ) |> expect( @@ -522,7 +534,7 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do ] }, _options -> - {:ok, "0x" <> beacon_contract_address_hash_string} + {:ok, response} end ) |> expect( @@ -550,10 +562,5 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do } end ) - - implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, []) - verify!(EthereumJSONRPC.Mox) - - assert implementation_abi == @implementation_abi end end From 843a9e3657261f3bca20447c9d1b1fc248e69bfe Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 5 Mar 2024 22:58:45 +0300 Subject: [PATCH 564/607] Refactoring --- .../explorer/chain/smart_contract/proxy.ex | 26 ++----------------- 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex index 22a5ab5d3d24..672b17ddd65b 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -295,30 +295,8 @@ defmodule Explorer.Chain.SmartContract.Proxy do """ @spec extract_address_hex_from_storage_pointer(binary) :: binary def extract_address_hex_from_storage_pointer(storage_value) when is_binary(storage_value) do - hex_bytes = Base.decode16!(storage_value, case: :mixed) - hex_bytes_count = byte_size(hex_bytes) + address_hex = storage_value |> String.slice(-40, 40) |> String.pad_leading(40, ["0"]) - cond do - hex_bytes_count == 20 -> - "0x" <> storage_value - - hex_bytes_count > 20 -> - << - reversed_address_hex::binary-size(20), - _right::binary - >> = hex_bytes |> :binary.bin_to_list() |> Enum.reverse() |> :binary.list_to_bin() - - hex = - reversed_address_hex - |> :binary.bin_to_list() - |> Enum.reverse() - |> :binary.list_to_bin() - |> Base.encode16(case: :lower) - - "0x" <> hex - - hex_bytes_count < 20 -> - "0x" <> String.pad_leading(storage_value, 40, "0") - end + "0x" <> address_hex end end From e49c92ca686cb707c9d545c59d0f8b7ca1ffdd72 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Fri, 1 Mar 2024 23:34:55 +0300 Subject: [PATCH 565/607] Fix MultipleResultsError in smart_contract_creation_tx_bytecode/1 --- apps/explorer/lib/explorer/chain.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 9fcd39d96f08..2f8fa0dff1ff 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -3048,7 +3048,9 @@ defmodule Explorer.Chain do join: t in assoc(itx, :transaction), where: itx.created_contract_address_hash == ^address_hash, where: t.status == ^1, - select: %{init: itx.init, created_contract_code: itx.created_contract_code} + select: %{init: itx.init, created_contract_code: itx.created_contract_code}, + order_by: [desc: itx.block_number], + limit: ^1 ) res = creation_int_tx_query |> Repo.one() From 4a260fd1fa42d9eb16676944039d4050e78881c2 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Fri, 1 Mar 2024 23:37:03 +0300 Subject: [PATCH 566/607] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d60943c096f6..89d3ef995cd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - [#9563](https://github.com/blockscout/blockscout/pull/9563) - Fix timestamp handler for unfinalized zkEVM batches - [#9560](https://github.com/blockscout/blockscout/pull/9560) - Fix fetch pending transaction for hyperledger besu client - [#9555](https://github.com/blockscout/blockscout/pull/9555) - Fix EIP-1967 beacon proxy pattern detection +- [#9518](https://github.com/blockscout/blockscout/pull/9518) - Fix MultipleResultsError in `smart_contract_creation_tx_bytecode/1` - [#9514](https://github.com/blockscout/blockscout/pull/9514) - Fix missing `0x` prefix for `blockNumber`, `logIndex`, `transactionIndex` and remove `transactionLogIndex` in `eth_getLogs` response. - [#9512](https://github.com/blockscout/blockscout/pull/9512) - Docker-compose 2.24.6 compatibility - [#9262](https://github.com/blockscout/blockscout/pull/9262) - Fix withdrawal status From ce5e2f54e4976e421cdd55de44e4c6d293b58a9a Mon Sep 17 00:00:00 2001 From: varasev <33550681+varasev@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:45:41 +0300 Subject: [PATCH 567/607] Support Optimism Ecotone upgrade by Indexer.Fetcher.Optimism.TxnBatch module (#9571) * Draft for Ecotone support by Indexer.Fetcher.Optimism.TxnBatch module * Small refactoring * Use RollupL1ReorgMonitor for Optimism * Move EIP-4844 decode function to Explorer.Chain.Optimism.TxnBatch * Small refactoring * Update changelog * Update cspell.json * Extend logs * Fix init_continue in Indexer.Fetcher.Optimism * mix format --------- Co-authored-by: POA <33550681+poa@users.noreply.github.com> --- CHANGELOG.md | 1 + .../lib/ethereum_jsonrpc/transaction.ex | 4 +- .../lib/explorer/chain/events/publisher.ex | 2 +- .../lib/explorer/chain/events/subscriber.ex | 2 +- .../lib/explorer/chain/optimism/txn_batch.ex | 95 +++++++ .../lib/indexer/fetcher/beacon/blob.ex | 14 +- apps/indexer/lib/indexer/fetcher/optimism.ex | 104 +------- .../indexer/fetcher/optimism/output_root.ex | 10 +- .../lib/indexer/fetcher/optimism/txn_batch.ex | 252 ++++++++++++++---- .../fetcher/optimism/withdrawal_event.ex | 10 +- .../fetcher/rollup_l1_reorg_monitor.ex | 32 ++- apps/indexer/lib/indexer/supervisor.ex | 3 +- config/runtime.exs | 2 +- cspell.json | 2 + 14 files changed, 346 insertions(+), 187 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89d3ef995cd8..98e77e1aa93d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ ### Chore +- [#9571](https://github.com/blockscout/blockscout/pull/9571) - Support Optimism Ecotone upgrade by Indexer.Fetcher.Optimism.TxnBatch module - [#9562](https://github.com/blockscout/blockscout/pull/9562) - Add cancun evm version - [#9260](https://github.com/blockscout/blockscout/pull/9260) - Optimism Delta upgrade support by Indexer.Fetcher.OptimismTxnBatch module - [#8740](https://github.com/blockscout/blockscout/pull/8740) - Add delay to Indexer.Fetcher.OptimismTxnBatch module initialization diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex index 67f172c1a695..b3fb9c55ec03 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex @@ -489,9 +489,11 @@ defmodule EthereumJSONRPC.Transaction do ]) "optimism" -> + # we need to put blobVersionedHashes for Indexer.Fetcher.Optimism.TxnBatch module put_if_present(elixir, params, [ {"l1TxOrigin", :l1_tx_origin}, - {"l1BlockNumber", :l1_block_number} + {"l1BlockNumber", :l1_block_number}, + {"blobVersionedHashes", :blob_versioned_hashes} ]) "suave" -> diff --git a/apps/explorer/lib/explorer/chain/events/publisher.ex b/apps/explorer/lib/explorer/chain/events/publisher.ex index 1b45c84c940f..d6e4aa52b766 100644 --- a/apps/explorer/lib/explorer/chain/events/publisher.ex +++ b/apps/explorer/lib/explorer/chain/events/publisher.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.Events.Publisher do Publishes events related to the Chain context. """ - @allowed_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number optimism_deposits optimism_reorg_block token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches eth_bytecode_db_lookup_started smart_contract_was_not_verified)a + @allowed_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number optimism_deposits token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches eth_bytecode_db_lookup_started smart_contract_was_not_verified)a def broadcast(_data, false), do: :ok diff --git a/apps/explorer/lib/explorer/chain/events/subscriber.ex b/apps/explorer/lib/explorer/chain/events/subscriber.ex index c161bb124a52..0a285f73f596 100644 --- a/apps/explorer/lib/explorer/chain/events/subscriber.ex +++ b/apps/explorer/lib/explorer/chain/events/subscriber.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.Events.Subscriber do Subscribes to events related to the Chain context. """ - @allowed_broadcast_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number optimism_deposits optimism_reorg_block token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches eth_bytecode_db_lookup_started smart_contract_was_not_verified)a + @allowed_broadcast_events ~w(addresses address_coin_balances address_token_balances address_current_token_balances blocks block_rewards internal_transactions last_block_number optimism_deposits token_transfers transactions contract_verification_result token_total_supply changed_bytecode smart_contract_was_verified zkevm_confirmed_batches eth_bytecode_db_lookup_started smart_contract_was_not_verified)a @allowed_broadcast_types ~w(catchup realtime on_demand contract_verification_result)a diff --git a/apps/explorer/lib/explorer/chain/optimism/txn_batch.ex b/apps/explorer/lib/explorer/chain/optimism/txn_batch.ex index fea454afd34e..7ba61b2f2ee1 100644 --- a/apps/explorer/lib/explorer/chain/optimism/txn_batch.ex +++ b/apps/explorer/lib/explorer/chain/optimism/txn_batch.ex @@ -12,6 +12,11 @@ defmodule Explorer.Chain.Optimism.TxnBatch do @required_attrs ~w(l2_block_number frame_sequence_id)a + @blob_size 4096 * 32 + @encoding_version 0 + @max_blob_data_size (4 * 31 + 3) * 1024 - 4 + @rounds 1024 + @type t :: %__MODULE__{ l2_block_number: non_neg_integer(), frame_sequence_id: non_neg_integer(), @@ -53,6 +58,96 @@ defmodule Explorer.Chain.Optimism.TxnBatch do |> select_repo(options).all() end + @doc """ + Decodes EIP-4844 blob to the raw data. Returns `nil` if the blob is invalid. + """ + @spec decode_eip4844_blob(binary()) :: binary() | nil + def decode_eip4844_blob(b) do + <> = b + + if version != @encoding_version or output_len > @max_blob_data_size do + raise "Blob version or data size is incorrect" + end + + output = first_output <> :binary.copy(<<0>>, @max_blob_data_size - 27) + + opos = 28 + ipos = 32 + {encoded_byte1, opos, ipos, output} = decode_eip4844_field_element(b, opos, ipos, output) + {encoded_byte2, opos, ipos, output} = decode_eip4844_field_element(b, opos, ipos, output) + {encoded_byte3, opos, ipos, output} = decode_eip4844_field_element(b, opos, ipos, output) + {opos, output} = reassemble_eip4844_bytes(opos, encoded_byte0, encoded_byte1, encoded_byte2, encoded_byte3, output) + + {_opos, ipos, output} = + Enum.reduce_while(Range.new(1, @rounds - 1), {opos, ipos, output}, fn _i, {opos_acc, ipos_acc, output_acc} -> + if opos_acc >= output_len do + {:halt, {opos_acc, ipos_acc, output_acc}} + else + {encoded_byte0, opos_acc, ipos_acc, output_acc} = + decode_eip4844_field_element(b, opos_acc, ipos_acc, output_acc) + + {encoded_byte1, opos_acc, ipos_acc, output_acc} = + decode_eip4844_field_element(b, opos_acc, ipos_acc, output_acc) + + {encoded_byte2, opos_acc, ipos_acc, output_acc} = + decode_eip4844_field_element(b, opos_acc, ipos_acc, output_acc) + + {encoded_byte3, opos_acc, ipos_acc, output_acc} = + decode_eip4844_field_element(b, opos_acc, ipos_acc, output_acc) + + {opos_acc, output_acc} = + reassemble_eip4844_bytes(opos_acc, encoded_byte0, encoded_byte1, encoded_byte2, encoded_byte3, output_acc) + + {:cont, {opos_acc, ipos_acc, output_acc}} + end + end) + + Enum.each(Range.new(output_len, byte_size(output) - 1), fn i -> + <<0>> = binary_part(output, i, 1) + end) + + output = binary_part(output, 0, output_len) + + Enum.each(Range.new(ipos, @blob_size - 1), fn i -> + <<0>> = binary_part(b, i, 1) + end) + + output + rescue + _ -> nil + end + + defp decode_eip4844_field_element(b, opos, ipos, output) do + <<_::binary-size(ipos), ipos_byte::size(8), insert::binary-size(32), _::binary>> = b + + if Bitwise.band(ipos_byte, 0b11000000) == 0 do + <> = output + + {ipos_byte, opos + 32, ipos + 32, output_before_opos <> insert <> rest} + end + end + + defp reassemble_eip4844_bytes(opos, encoded_byte0, encoded_byte1, encoded_byte2, encoded_byte3, output) do + opos = opos - 1 + + x = Bitwise.bor(Bitwise.band(encoded_byte0, 0b00111111), Bitwise.bsl(Bitwise.band(encoded_byte1, 0b00110000), 2)) + y = Bitwise.bor(Bitwise.band(encoded_byte1, 0b00001111), Bitwise.bsl(Bitwise.band(encoded_byte3, 0b00001111), 4)) + z = Bitwise.bor(Bitwise.band(encoded_byte2, 0b00111111), Bitwise.bsl(Bitwise.band(encoded_byte3, 0b00110000), 2)) + + new_output = + output + |> replace_byte(z, opos - 32) + |> replace_byte(y, opos - 32 * 2) + |> replace_byte(x, opos - 32 * 3) + + {opos, new_output} + end + + defp replace_byte(bytes, byte, pos) do + <> = bytes + bytes_before <> <> <> bytes_after + end + defp page_txn_batches(query, %PagingOptions{key: nil}), do: query defp page_txn_batches(query, %PagingOptions{key: {block_number}}) do diff --git a/apps/indexer/lib/indexer/fetcher/beacon/blob.ex b/apps/indexer/lib/indexer/fetcher/beacon/blob.ex index eaa1f7dd095e..ba7282110850 100644 --- a/apps/indexer/lib/indexer/fetcher/beacon/blob.ex +++ b/apps/indexer/lib/indexer/fetcher/beacon/blob.ex @@ -105,11 +105,15 @@ defmodule Indexer.Fetcher.Beacon.Blob do DateTime.to_unix(block_timestamp) end - defp timestamp_to_slot(block_timestamp, %{ - reference_timestamp: reference_timestamp, - reference_slot: reference_slot, - slot_duration: slot_duration - }) do + @doc """ + Converts block timestamp to the slot number. + """ + @spec timestamp_to_slot(non_neg_integer(), map()) :: non_neg_integer() + def timestamp_to_slot(block_timestamp, %{ + reference_timestamp: reference_timestamp, + reference_slot: reference_slot, + slot_duration: slot_duration + }) do ((block_timestamp - reference_timestamp) |> div(slot_duration)) + reference_slot end diff --git a/apps/indexer/lib/indexer/fetcher/optimism.ex b/apps/indexer/lib/indexer/fetcher/optimism.ex index 62d5fe1cd0be..cd367d59f466 100644 --- a/apps/indexer/lib/indexer/fetcher/optimism.ex +++ b/apps/indexer/lib/indexer/fetcher/optimism.ex @@ -20,8 +20,7 @@ defmodule Indexer.Fetcher.Optimism do import Explorer.Helper, only: [parse_integer: 1] alias EthereumJSONRPC.Block.ByNumber - alias Explorer.Chain.Events.{Publisher, Subscriber} - alias Indexer.{BoundQueue, Helper} + alias Indexer.Helper @fetcher_name :optimism @block_check_interval_range_size 100 @@ -46,59 +45,7 @@ defmodule Indexer.Fetcher.Optimism do @impl GenServer def init(_args) do Logger.metadata(fetcher: @fetcher_name) - - modules_using_reorg_monitor = [ - Indexer.Fetcher.Optimism.TxnBatch, - Indexer.Fetcher.Optimism.OutputRoot, - Indexer.Fetcher.Optimism.WithdrawalEvent - ] - - reorg_monitor_not_needed = - modules_using_reorg_monitor - |> Enum.all?(fn module -> - is_nil(Application.get_all_env(:indexer)[module][:start_block_l1]) - end) - - if reorg_monitor_not_needed do - :ignore - else - optimism_l1_rpc = Application.get_all_env(:indexer)[Indexer.Fetcher.Optimism][:optimism_l1_rpc] - - json_rpc_named_arguments = json_rpc_named_arguments(optimism_l1_rpc) - - {:ok, %{}, {:continue, json_rpc_named_arguments}} - end - end - - @impl GenServer - def handle_continue(json_rpc_named_arguments, _state) do - {:ok, block_check_interval, _} = get_block_check_interval(json_rpc_named_arguments) - Process.send(self(), :reorg_monitor, []) - - {:noreply, - %{block_check_interval: block_check_interval, json_rpc_named_arguments: json_rpc_named_arguments, prev_latest: 0}} - end - - @impl GenServer - def handle_info( - :reorg_monitor, - %{ - block_check_interval: block_check_interval, - json_rpc_named_arguments: json_rpc_named_arguments, - prev_latest: prev_latest - } = state - ) do - {:ok, latest} = get_block_number_by_tag("latest", json_rpc_named_arguments, Helper.infinite_retries_number()) - - if latest < prev_latest do - Logger.warning("Reorg detected: previous latest block ##{prev_latest}, current latest block ##{latest}.") - - Publisher.broadcast([{:optimism_reorg_block, latest}], :realtime) - end - - Process.send_after(self(), :reorg_monitor, block_check_interval) - - {:noreply, %{state | prev_latest: latest}} + :ignore end @doc """ @@ -289,7 +236,8 @@ defmodule Indexer.Fetcher.Optimism do end with {:start_block_l1_undefined, false} <- {:start_block_l1_undefined, is_nil(env[:start_block_l1])}, - {:reorg_monitor_started, true} <- {:reorg_monitor_started, !is_nil(Process.whereis(Indexer.Fetcher.Optimism))}, + {:reorg_monitor_started, true} <- + {:reorg_monitor_started, !is_nil(Process.whereis(Indexer.Fetcher.RollupL1ReorgMonitor))}, optimism_l1_rpc = Application.get_all_env(:indexer)[Indexer.Fetcher.Optimism][:optimism_l1_rpc], {:rpc_l1_undefined, false} <- {:rpc_l1_undefined, is_nil(optimism_l1_rpc)}, {:contract_is_valid, true} <- {:contract_is_valid, Helper.address_correct?(contract_address)}, @@ -305,8 +253,6 @@ defmodule Indexer.Fetcher.Optimism do {:ok, block_check_interval, last_safe_block} <- get_block_check_interval(json_rpc_named_arguments) do start_block = max(start_block_l1, last_l1_block_number) - Subscriber.to(:optimism_reorg_block, :realtime) - Process.send(self(), :continue, []) {:noreply, @@ -361,46 +307,4 @@ defmodule Indexer.Fetcher.Optimism do def repeated_request(req, error_message, json_rpc_named_arguments, retries) do Helper.repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) end - - def reorg_block_pop(fetcher_name) do - table_name = reorg_table_name(fetcher_name) - - case BoundQueue.pop_front(reorg_queue_get(table_name)) do - {:ok, {block_number, updated_queue}} -> - :ets.insert(table_name, {:queue, updated_queue}) - block_number - - {:error, :empty} -> - nil - end - end - - def reorg_block_push(fetcher_name, block_number) do - table_name = reorg_table_name(fetcher_name) - {:ok, updated_queue} = BoundQueue.push_back(reorg_queue_get(table_name), block_number) - :ets.insert(table_name, {:queue, updated_queue}) - end - - defp reorg_queue_get(table_name) do - if :ets.whereis(table_name) == :undefined do - :ets.new(table_name, [ - :set, - :named_table, - :public, - read_concurrency: true, - write_concurrency: true - ]) - end - - with info when info != :undefined <- :ets.info(table_name), - [{_, value}] <- :ets.lookup(table_name, :queue) do - value - else - _ -> %BoundQueue{} - end - end - - defp reorg_table_name(fetcher_name) do - :"#{fetcher_name}#{:_reorgs}" - end end diff --git a/apps/indexer/lib/indexer/fetcher/optimism/output_root.ex b/apps/indexer/lib/indexer/fetcher/optimism/output_root.ex index ca35b71a83e0..cbc21a8ddb2a 100644 --- a/apps/indexer/lib/indexer/fetcher/optimism/output_root.ex +++ b/apps/indexer/lib/indexer/fetcher/optimism/output_root.ex @@ -14,7 +14,7 @@ defmodule Indexer.Fetcher.Optimism.OutputRoot do alias Explorer.{Chain, Helper, Repo} alias Explorer.Chain.Optimism.OutputRoot - alias Indexer.Fetcher.Optimism + alias Indexer.Fetcher.{Optimism, RollupL1ReorgMonitor} alias Indexer.Helper, as: IndexerHelper @fetcher_name :optimism_output_roots @@ -105,7 +105,7 @@ defmodule Indexer.Fetcher.Optimism.OutputRoot do ) end - reorg_block = Optimism.reorg_block_pop(@fetcher_name) + reorg_block = RollupL1ReorgMonitor.reorg_block_pop(__MODULE__) if !is_nil(reorg_block) && reorg_block > 0 do {deleted_count, _} = Repo.delete_all(from(r in OutputRoot, where: r.l1_block_number >= ^reorg_block)) @@ -136,12 +136,6 @@ defmodule Indexer.Fetcher.Optimism.OutputRoot do {:noreply, %{state | start_block: new_start_block, end_block: new_end_block}} end - @impl GenServer - def handle_info({:chain_event, :optimism_reorg_block, :realtime, block_number}, state) do - Optimism.reorg_block_push(@fetcher_name, block_number) - {:noreply, state} - end - @impl GenServer def handle_info({ref, _result}, state) do Process.demonitor(ref, [:flush]) diff --git a/apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex b/apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex index 01bc603caeb4..8010861c2d97 100644 --- a/apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex +++ b/apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex @@ -17,11 +17,14 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do alias EthereumJSONRPC.Block.ByHash alias EthereumJSONRPC.Blocks alias Explorer.{Chain, Repo} - alias Explorer.Chain.Block - alias Explorer.Chain.Events.Subscriber + alias Explorer.Chain.Beacon.Blob, as: BeaconBlob + alias Explorer.Chain.{Block, Hash} alias Explorer.Chain.Optimism.FrameSequence alias Explorer.Chain.Optimism.TxnBatch, as: OptimismTxnBatch - alias Indexer.Fetcher.Optimism + alias HTTPoison.Response + alias Indexer.Fetcher.Beacon.Blob + alias Indexer.Fetcher.Beacon.Client, as: BeaconClient + alias Indexer.Fetcher.{Optimism, RollupL1ReorgMonitor} alias Indexer.Helper alias Varint.LEB128 @@ -65,9 +68,10 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do with {:start_block_l1_undefined, false} <- {:start_block_l1_undefined, is_nil(env[:start_block_l1])}, {:genesis_block_l2_invalid, false} <- {:genesis_block_l2_invalid, is_nil(env[:genesis_block_l2]) or env[:genesis_block_l2] < 0}, - {:reorg_monitor_started, true} <- {:reorg_monitor_started, !is_nil(Process.whereis(Indexer.Fetcher.Optimism))}, + {:reorg_monitor_started, true} <- {:reorg_monitor_started, !is_nil(Process.whereis(RollupL1ReorgMonitor))}, optimism_l1_rpc = Application.get_all_env(:indexer)[Indexer.Fetcher.Optimism][:optimism_l1_rpc], {:rpc_l1_undefined, false} <- {:rpc_l1_undefined, is_nil(optimism_l1_rpc)}, + {:blobs_api_url_undefined, false} <- {:blobs_api_url_undefined, is_nil(env[:blobs_api_url])}, {:batch_inbox_valid, true} <- {:batch_inbox_valid, Helper.address_correct?(env[:batch_inbox])}, {:batch_submitter_valid, true} <- {:batch_submitter_valid, Helper.address_correct?(env[:batch_submitter])}, start_block_l1 = parse_integer(env[:start_block_l1]), @@ -83,14 +87,13 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do {:ok, block_check_interval, last_safe_block} <- Optimism.get_block_check_interval(json_rpc_named_arguments) do start_block = max(start_block_l1, last_l1_block_number) - Subscriber.to(:optimism_reorg_block, :realtime) - Process.send(self(), :continue, []) {:noreply, %{ batch_inbox: String.downcase(env[:batch_inbox]), batch_submitter: String.downcase(env[:batch_submitter]), + blobs_api_url: String.trim_trailing(env[:blobs_api_url], "/"), block_check_interval: block_check_interval, start_block: start_block, end_block: last_safe_block, @@ -110,13 +113,20 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do {:stop, :normal, state} {:reorg_monitor_started, false} -> - Logger.error("Cannot start this process as reorg monitor in Indexer.Fetcher.Optimism is not started.") + Logger.error( + "Cannot start this process as reorg monitor in Indexer.Fetcher.RollupL1ReorgMonitor is not started." + ) + {:stop, :normal, state} {:rpc_l1_undefined, true} -> Logger.error("L1 RPC URL is not defined.") {:stop, :normal, state} + {:blobs_api_url_undefined, true} -> + Logger.error("L1 Blockscout Blobs API URL is not defined.") + {:stop, :normal, state} + {:batch_inbox_valid, false} -> Logger.error("Batch Inbox address is invalid or not defined.") {:stop, :normal, state} @@ -157,6 +167,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do %{ batch_inbox: batch_inbox, batch_submitter: batch_submitter, + blobs_api_url: blobs_api_url, block_check_interval: block_check_interval, start_block: start_block, end_block: end_block, @@ -189,8 +200,8 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do batch_submitter, genesis_block_l2, incomplete_channels_acc, - json_rpc_named_arguments, - json_rpc_named_arguments_l2, + {json_rpc_named_arguments, json_rpc_named_arguments_l2}, + blobs_api_url, Helper.infinite_retries_number() ) @@ -217,7 +228,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do incomplete_channels_acc end - reorg_block = Optimism.reorg_block_pop(@fetcher_name) + reorg_block = RollupL1ReorgMonitor.reorg_block_pop(__MODULE__) if !is_nil(reorg_block) && reorg_block > 0 do new_incomplete_channels = handle_l1_reorg(reorg_block, new_incomplete_channels) @@ -251,12 +262,6 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do }} end - @impl GenServer - def handle_info({:chain_event, :optimism_reorg_block, :realtime, block_number}, state) do - Optimism.reorg_block_push(@fetcher_name, block_number) - {:noreply, state} - end - @impl GenServer def handle_info({ref, _result}, state) do Process.demonitor(ref, [:flush]) @@ -356,8 +361,8 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do batch_submitter, genesis_block_l2, incomplete_channels, - json_rpc_named_arguments, - json_rpc_named_arguments_l2, + {json_rpc_named_arguments, json_rpc_named_arguments_l2}, + blobs_api_url, retries_left ) do case fetch_blocks_by_range(block_range, json_rpc_named_arguments) do @@ -368,7 +373,8 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do blocks_params, genesis_block_l2, incomplete_channels, - json_rpc_named_arguments_l2 + json_rpc_named_arguments_l2, + blobs_api_url ) {_, message_or_errors} -> @@ -395,64 +401,178 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do batch_submitter, genesis_block_l2, incomplete_channels, - json_rpc_named_arguments, - json_rpc_named_arguments_l2, + {json_rpc_named_arguments, json_rpc_named_arguments_l2}, + blobs_api_url, retries_left ) end end end + defp blobs_to_input(transaction_hash, blob_versioned_hashes, block_timestamp, blobs_api_url) do + Enum.reduce(blob_versioned_hashes, <<>>, fn blob_hash, acc -> + with {:ok, response} <- http_get_request(blobs_api_url <> "/" <> blob_hash), + blob_data = Map.get(response, "blob_data"), + false <- is_nil(blob_data) do + # read the data from Blockscout API + decoded = + blob_data + |> String.trim_leading("0x") + |> Base.decode16!(case: :lower) + |> OptimismTxnBatch.decode_eip4844_blob() + + if is_nil(decoded) do + Logger.warning("Cannot decode the blob #{blob_hash} taken from the Blockscout Blobs API.") + acc + else + Logger.info("The input for transaction #{transaction_hash} is taken from the Blockscout Blobs API.") + acc <> decoded + end + else + _ -> + # read the data from the fallback source (beacon node) + + beacon_config = + :indexer + |> Application.get_env(Blob) + |> Keyword.take([:reference_slot, :reference_timestamp, :slot_duration]) + |> Enum.into(%{}) + + try do + {:ok, fetched_blobs} = + block_timestamp + |> DateTime.to_unix() + |> Blob.timestamp_to_slot(beacon_config) + |> BeaconClient.get_blob_sidecars() + + blobs = Map.get(fetched_blobs, "data", []) + + if Enum.empty?(blobs) do + raise "Empty data" + end + + decoded_blob_data = + blobs + |> Enum.find(fn b -> + b + |> Map.get("kzg_commitment", "0x") + |> String.trim_leading("0x") + |> Base.decode16!(case: :lower) + |> BeaconBlob.hash() + |> Hash.to_string() + |> Kernel.==(blob_hash) + end) + |> Map.get("blob") + |> String.trim_leading("0x") + |> Base.decode16!(case: :lower) + |> OptimismTxnBatch.decode_eip4844_blob() + + if is_nil(decoded_blob_data) do + raise "Invalid blob" + else + Logger.info("The input for transaction #{transaction_hash} is taken from the Beacon Node.") + acc <> decoded_blob_data + end + rescue + reason -> + Logger.warning( + "Cannot decode the blob #{blob_hash} taken from the Beacon Node. Reason: #{inspect(reason)}" + ) + + acc + end + end + end) + end + defp get_txn_batches_inner( transactions_filtered, blocks_params, genesis_block_l2, incomplete_channels, - json_rpc_named_arguments_l2 + json_rpc_named_arguments_l2, + blobs_api_url ) do transactions_filtered - |> Enum.reduce({:ok, incomplete_channels, [], []}, fn t, {_, incomplete_channels_acc, batches_acc, sequences_acc} -> - frame = input_to_frame(t.input) - - channel = Map.get(incomplete_channels_acc, frame.channel_id, %{frames: %{}}) - - channel_frames = - Map.put(channel.frames, frame.number, %{ - data: frame.data, - is_last: frame.is_last, - block_number: t.block_number, - tx_hash: t.hash - }) - - l1_timestamp = - if frame.is_last do - get_block_timestamp_by_number(t.block_number, blocks_params) + |> Enum.reduce({:ok, incomplete_channels, [], []}, fn tx, + {_, incomplete_channels_acc, batches_acc, sequences_acc} -> + input = + if tx.type == 3 do + # this is EIP-4844 transaction, so we get the input from the blobs + block_timestamp = get_block_timestamp_by_number(tx.block_number, blocks_params) + blobs_to_input(tx.hash, tx.blob_versioned_hashes, block_timestamp, blobs_api_url) else - Map.get(channel, :l1_timestamp) + tx.input end - channel = - channel - |> Map.put_new(:id, frame.channel_id) - |> Map.put(:frames, channel_frames) - |> Map.put(:timestamp, DateTime.utc_now()) - |> Map.put(:l1_timestamp, l1_timestamp) - - if channel_complete?(channel) do - handle_channel( - channel, + if tx.type == 3 and input == <<>> do + # skip this transaction as we cannot find or read its blobs + {:ok, incomplete_channels_acc, batches_acc, sequences_acc} + else + handle_input( + input, + tx, + blocks_params, incomplete_channels_acc, batches_acc, sequences_acc, genesis_block_l2, json_rpc_named_arguments_l2 ) - else - {:ok, Map.put(incomplete_channels_acc, frame.channel_id, channel), batches_acc, sequences_acc} end end) end + defp handle_input( + input, + tx, + blocks_params, + incomplete_channels_acc, + batches_acc, + sequences_acc, + genesis_block_l2, + json_rpc_named_arguments_l2 + ) do + frame = input_to_frame(input) + + channel = Map.get(incomplete_channels_acc, frame.channel_id, %{frames: %{}}) + + channel_frames = + Map.put(channel.frames, frame.number, %{ + data: frame.data, + is_last: frame.is_last, + block_number: tx.block_number, + tx_hash: tx.hash + }) + + l1_timestamp = + if frame.is_last do + get_block_timestamp_by_number(tx.block_number, blocks_params) + else + Map.get(channel, :l1_timestamp) + end + + channel_updated = + channel + |> Map.put_new(:id, frame.channel_id) + |> Map.put(:frames, channel_frames) + |> Map.put(:timestamp, DateTime.utc_now()) + |> Map.put(:l1_timestamp, l1_timestamp) + + if channel_complete?(channel_updated) do + handle_channel( + channel_updated, + incomplete_channels_acc, + batches_acc, + sequences_acc, + genesis_block_l2, + json_rpc_named_arguments_l2 + ) + else + {:ok, Map.put(incomplete_channels_acc, frame.channel_id, channel_updated), batches_acc, sequences_acc} + end + end + defp handle_channel( channel, incomplete_channels_acc, @@ -546,6 +666,30 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do end end + defp http_get_request(url) do + case Application.get_env(:explorer, :http_adapter).get(url) do + {:ok, %Response{body: body, status_code: 200}} -> + Jason.decode(body) + + {:ok, %Response{body: body, status_code: _}} -> + {:error, body} + + {:error, error} -> + old_truncate = Application.get_env(:logger, :truncate) + Logger.configure(truncate: :infinity) + + Logger.error(fn -> + [ + "Error while sending request to Blockscout Blobs API: #{url}: ", + inspect(error, limit: :infinity, printable_limit: :infinity) + ] + end) + + Logger.configure(truncate: old_truncate) + {:error, "Error while sending request to Blockscout Blobs API"} + end + end + defp channel_complete?(channel) do last_frame_number = channel.frames @@ -568,8 +712,12 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do end defp input_to_frame("0x" <> input) do - input_binary = Base.decode16!(input, case: :mixed) + input + |> Base.decode16!(case: :mixed) + |> input_to_frame() + end + defp input_to_frame(input_binary) do # the structure of the input is as follows: # # input = derivation_version ++ channel_id ++ frame_number ++ frame_data_length ++ frame_data ++ is_last diff --git a/apps/indexer/lib/indexer/fetcher/optimism/withdrawal_event.ex b/apps/indexer/lib/indexer/fetcher/optimism/withdrawal_event.ex index 8854f868ba23..fdd8bf04d803 100644 --- a/apps/indexer/lib/indexer/fetcher/optimism/withdrawal_event.ex +++ b/apps/indexer/lib/indexer/fetcher/optimism/withdrawal_event.ex @@ -16,7 +16,7 @@ defmodule Indexer.Fetcher.Optimism.WithdrawalEvent do alias EthereumJSONRPC.Blocks alias Explorer.{Chain, Repo} alias Explorer.Chain.Optimism.WithdrawalEvent - alias Indexer.Fetcher.Optimism + alias Indexer.Fetcher.{Optimism, RollupL1ReorgMonitor} alias Indexer.Helper @fetcher_name :optimism_withdrawal_events @@ -111,7 +111,7 @@ defmodule Indexer.Fetcher.Optimism.WithdrawalEvent do ) end - reorg_block = Optimism.reorg_block_pop(@fetcher_name) + reorg_block = RollupL1ReorgMonitor.reorg_block_pop(__MODULE__) if !is_nil(reorg_block) && reorg_block > 0 do {deleted_count, _} = Repo.delete_all(from(we in WithdrawalEvent, where: we.l1_block_number >= ^reorg_block)) @@ -142,12 +142,6 @@ defmodule Indexer.Fetcher.Optimism.WithdrawalEvent do {:noreply, %{state | start_block: new_start_block, end_block: new_end_block}} end - @impl GenServer - def handle_info({:chain_event, :optimism_reorg_block, :realtime, block_number}, state) do - Optimism.reorg_block_push(@fetcher_name, block_number) - {:noreply, state} - end - @impl GenServer def handle_info({ref, _result}, state) do Process.demonitor(ref, [:flush]) diff --git a/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex b/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex index b9a5e8e4ec67..5da08bf1d717 100644 --- a/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex +++ b/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex @@ -32,6 +32,9 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do Logger.metadata(fetcher: @fetcher_name) modules_can_use_reorg_monitor = [ + Indexer.Fetcher.Optimism.OutputRoot, + Indexer.Fetcher.Optimism.TxnBatch, + Indexer.Fetcher.Optimism.WithdrawalEvent, Indexer.Fetcher.PolygonEdge.Deposit, Indexer.Fetcher.PolygonEdge.WithdrawalExit, Indexer.Fetcher.PolygonZkevm.BridgeL1, @@ -56,14 +59,27 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do module_using_reorg_monitor = Enum.at(modules_using_reorg_monitor, 0) l1_rpc = - if Enum.member?( - [Indexer.Fetcher.PolygonEdge.Deposit, Indexer.Fetcher.PolygonEdge.WithdrawalExit], - module_using_reorg_monitor - ) do - # there can be more than one PolygonEdge.* modules, so we get the common L1 RPC URL for them from Indexer.Fetcher.PolygonEdge - Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonEdge][:polygon_edge_l1_rpc] - else - Application.get_all_env(:indexer)[module_using_reorg_monitor][:rpc] + cond do + Enum.member?( + [Indexer.Fetcher.PolygonEdge.Deposit, Indexer.Fetcher.PolygonEdge.WithdrawalExit], + module_using_reorg_monitor + ) -> + # there can be more than one PolygonEdge.* modules, so we get the common L1 RPC URL for them from Indexer.Fetcher.PolygonEdge + Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonEdge][:polygon_edge_l1_rpc] + + Enum.member?( + [ + Indexer.Fetcher.Optimism.OutputRoot, + Indexer.Fetcher.Optimism.TxnBatch, + Indexer.Fetcher.Optimism.WithdrawalEvent + ], + module_using_reorg_monitor + ) -> + # there can be more than one Optimism.* modules, so we get the common L1 RPC URL for them from Indexer.Fetcher.Optimism + Application.get_all_env(:indexer)[Indexer.Fetcher.Optimism][:optimism_l1_rpc] + + true -> + Application.get_all_env(:indexer)[module_using_reorg_monitor][:rpc] end json_rpc_named_arguments = Helper.json_rpc_named_arguments(l1_rpc) diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index 6d585a2f741c..e46927a0e040 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -138,7 +138,7 @@ defmodule Indexer.Supervisor do {TokenUpdater.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, {ReplacedTransaction.Supervisor, [[memory_monitor: memory_monitor]]}, - configure(Indexer.Fetcher.Optimism.Supervisor, [[memory_monitor: memory_monitor]]), + {Indexer.Fetcher.RollupL1ReorgMonitor.Supervisor, [[memory_monitor: memory_monitor]]}, configure( Indexer.Fetcher.Optimism.TxnBatch.Supervisor, [[memory_monitor: memory_monitor, json_rpc_named_arguments: json_rpc_named_arguments]] @@ -150,7 +150,6 @@ defmodule Indexer.Supervisor do [[memory_monitor: memory_monitor, json_rpc_named_arguments: json_rpc_named_arguments]] ), configure(Indexer.Fetcher.Optimism.WithdrawalEvent.Supervisor, [[memory_monitor: memory_monitor]]), - {Indexer.Fetcher.RollupL1ReorgMonitor.Supervisor, [[memory_monitor: memory_monitor]]}, configure(Indexer.Fetcher.PolygonEdge.Deposit.Supervisor, [[memory_monitor: memory_monitor]]), configure(Indexer.Fetcher.PolygonEdge.DepositExecute.Supervisor, [ [memory_monitor: memory_monitor, json_rpc_named_arguments: json_rpc_named_arguments] diff --git a/config/runtime.exs b/config/runtime.exs index cefab56efcdf..9760b0300549 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -682,7 +682,6 @@ config :indexer, Indexer.Fetcher.CoinBalance.Realtime, batch_size: coin_balances_batch_size, concurrency: coin_balances_concurrency -config :indexer, Indexer.Fetcher.Optimism.Supervisor, enabled: ConfigHelper.chain_type() == "optimism" config :indexer, Indexer.Fetcher.Optimism.TxnBatch.Supervisor, enabled: ConfigHelper.chain_type() == "optimism" config :indexer, Indexer.Fetcher.Optimism.OutputRoot.Supervisor, enabled: ConfigHelper.chain_type() == "optimism" config :indexer, Indexer.Fetcher.Optimism.Deposit.Supervisor, enabled: ConfigHelper.chain_type() == "optimism" @@ -713,6 +712,7 @@ config :indexer, Indexer.Fetcher.Optimism.TxnBatch, batch_inbox: System.get_env("INDEXER_OPTIMISM_L1_BATCH_INBOX"), batch_submitter: System.get_env("INDEXER_OPTIMISM_L1_BATCH_SUBMITTER"), blocks_chunk_size: System.get_env("INDEXER_OPTIMISM_L1_BATCH_BLOCKS_CHUNK_SIZE", "4"), + blobs_api_url: System.get_env("INDEXER_OPTIMISM_L1_BATCH_BLOCKSCOUT_BLOBS_API_URL"), genesis_block_l2: ConfigHelper.parse_integer_or_nil_env_var("INDEXER_OPTIMISM_L2_BATCH_GENESIS_BLOCK_NUMBER") config :indexer, Indexer.Fetcher.Withdrawal.Supervisor, diff --git a/cspell.json b/cspell.json index fa02ef75e07e..d4a8e9fd81d8 100644 --- a/cspell.json +++ b/cspell.json @@ -233,6 +233,7 @@ "invalidstart", "inversed", "ipfs", + "ipos", "itxs", "johnnny", "jsons", @@ -332,6 +333,7 @@ "onclick", "onconnect", "ondisconnect", + "opos", "outcoming", "overengineering", "pawesome", From a8193f151635339ae2b77486d3982902e4f16d2b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:22:57 +0300 Subject: [PATCH 568/607] Bump ex_abi from 0.6.4 to 0.7.0 (#9329) * Bump ex_abi from 0.6.4 to 0.7.0 Bumps [ex_abi](https://github.com/poanetwork/ex_abi) from 0.6.4 to 0.7.0. - [Release notes](https://github.com/poanetwork/ex_abi/releases) - [Changelog](https://github.com/poanetwork/ex_abi/blob/master/CHANGELOG.md) - [Commits](https://github.com/poanetwork/ex_abi/compare/0.6.4...0.7.0) --- updated-dependencies: - dependency-name: ex_abi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Shorten event signatures to first 4 bytes for consistency * Renamed variables and improved documentation for clearness --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Fedor Ivanov --- apps/explorer/lib/explorer/chain/contract_method.ex | 7 ++++++- apps/explorer/lib/explorer/chain/log.ex | 8 ++++++-- mix.lock | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/contract_method.ex b/apps/explorer/lib/explorer/chain/contract_method.ex index e17e233ff4d6..e23c7811f1aa 100644 --- a/apps/explorer/lib/explorer/chain/contract_method.ex +++ b/apps/explorer/lib/explorer/chain/contract_method.ex @@ -81,8 +81,13 @@ defmodule Explorer.Chain.ContractMethod do [selector] -> now = DateTime.utc_now() + # For events, the method_id (signature) is 32 bytes, whereas for methods + # and errors it is 4 bytes. To avoid complications with different sizes, + # we always take only the first 4 bytes of the hash. + <> = selector.method_id + %{ - identifier: selector.method_id, + identifier: first_four_bytes, abi: element, type: Atom.to_string(selector.type), inserted_at: now, diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 444daa88c6c7..2e1cde30ced6 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -219,7 +219,10 @@ defmodule Explorer.Chain.Log do @spec find_and_decode([map()], __MODULE__.t(), Hash.t()) :: {:error, any} | {:ok, ABI.FunctionSelector.t(), any} def find_and_decode(abi, log, transaction_hash) do - with {%FunctionSelector{} = selector, mapping} <- + # For events, the method_id (signature) is 32 bytes, whereas for methods and + # errors it is 4 bytes. To avoid complications with different sizes, we + # always take only the first 4 bytes of the hash. + with {%FunctionSelector{method_id: <>} = selector, mapping} <- abi |> ABI.parse_specification(include_events?: true) |> Event.find_and_decode( @@ -228,7 +231,8 @@ defmodule Explorer.Chain.Log do log.third_topic && log.third_topic.bytes, log.fourth_topic && log.fourth_topic.bytes, log.data.bytes - ) do + ), + selector <- %{selector | method_id: first_four_bytes} do {:ok, selector, mapping} end rescue diff --git a/mix.lock b/mix.lock index fbeaf2807e85..630b1b0d23c7 100644 --- a/mix.lock +++ b/mix.lock @@ -41,7 +41,7 @@ "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_abi": {:hex, :ex_abi, "0.6.4", "f722a38298f176dab511cf94627b2815282669255bc2eb834674f23ca71f5cfb", [:mix], [{:ex_keccak, "~> 0.7.3", [hex: :ex_keccak, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "07eaf39b70dd3beac1286c10368d27091a9a64844830eb26a38f1c8d8b19dfbb"}, + "ex_abi": {:hex, :ex_abi, "0.7.0", "4a2f83c47d98357c75862ca77fcbaa05d9199ba2888248fdb20d4f3befc3e151", [:mix], [{:ex_keccak, "~> 0.7.3", [hex: :ex_keccak, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d35c4ef7d860fd94bcf952ab866b7639df9054c009c098ddc94cf8916d2208d6"}, "ex_cldr": {:hex, :ex_cldr, "2.37.5", "9da6d97334035b961d2c2de167dc6af8cd3e09859301a5b8f49f90bd8b034593", [:mix], [{:cldr_utils, "~> 2.21", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "74ad5ddff791112ce4156382e171a5f5d3766af9d5c4675e0571f081fe136479"}, "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.15.1", "e92ba17c41e7405b7784e0e65f406b5f17cfe313e0e70de9befd653e12854822", [:mix], [{:ex_cldr, "~> 2.34", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "31df8bd37688340f8819bdd770eb17d659652078d34db632b85d4a32864d6a25"}, "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.10.2", "c8dbb3324ca35cea3679a96f4c774cdf4bdd425786a44c4f52aacb57a0cee446", [:mix], [{:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "0cc2124eccffa5438045c2504dd3365490b64065131f58ecc27f344db1edb4b8"}, From 33242ee3a4bdd951b44ebbdf5c2816131d529e29 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 18:20:25 +0000 Subject: [PATCH 569/607] Bump phoenix_ecto from 4.5.0 to 4.5.1 Bumps [phoenix_ecto](https://github.com/phoenixframework/phoenix_ecto) from 4.5.0 to 4.5.1. - [Changelog](https://github.com/phoenixframework/phoenix_ecto/blob/main/CHANGELOG.md) - [Commits](https://github.com/phoenixframework/phoenix_ecto/compare/v4.5.0...v4.5.1) --- updated-dependencies: - dependency-name: phoenix_ecto dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 630b1b0d23c7..dc4c07671088 100644 --- a/mix.lock +++ b/mix.lock @@ -37,7 +37,7 @@ "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "digital_token": {:hex, :digital_token, "0.6.0", "13e6de581f0b1f6c686f7c7d12ab11a84a7b22fa79adeb4b50eec1a2d278d258", [:mix], [{:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "2455d626e7c61a128b02a4a8caddb092548c3eb613ac6f6a85e4cbb6caddc4d1"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, + "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, @@ -100,7 +100,7 @@ "parallel_stream": {:hex, :parallel_stream, "1.1.0", "f52f73eb344bc22de335992377413138405796e0d0ad99d995d9977ac29f1ca9", [:mix], [], "hexpm", "684fd19191aedfaf387bbabbeb8ff3c752f0220c8112eb907d797f4592d6e871"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "phoenix": {:hex, :phoenix, "1.5.14", "2d5db884be496eefa5157505ec0134e66187cb416c072272420c5509d67bf808", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "207f1aa5520320cbb7940d7ff2dde2342162cf513875848f88249ea0ba02fef7"}, - "phoenix_ecto": {:hex, :phoenix_ecto, "4.5.0", "1a1f841ccda19b15f1d82968840a5b895c5f687b6734e430e4b2dbe035ca1837", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "13990570fde09e16959ef214501fe2813e1192d62ca753ec8798980580436f94"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.5.1", "6fdbc334ea53620e71655664df6f33f670747b3a7a6c4041cdda3e2c32df6257", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ebe43aa580db129e54408e719fb9659b7f9e0d52b965c5be26cdca416ecead28"}, "phoenix_html": {:hex, :phoenix_html, "3.0.4", "232d41884fe6a9c42d09f48397c175cd6f0d443aaa34c7424da47604201df2e1", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "ce17fd3cf815b2ed874114073e743507704b1f5288bb03c304a77458485efc8b"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, From 2b315146e6754228cf1bd615b733ec782de74db6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 18:22:26 +0000 Subject: [PATCH 570/607] Bump ex_doc from 0.31.1 to 0.31.2 Bumps [ex_doc](https://github.com/elixir-lang/ex_doc) from 0.31.1 to 0.31.2. - [Release notes](https://github.com/elixir-lang/ex_doc/releases) - [Changelog](https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md) - [Commits](https://github.com/elixir-lang/ex_doc/compare/v0.31.1...v0.31.2) --- updated-dependencies: - dependency-name: ex_doc dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.lock b/mix.lock index 630b1b0d23c7..d6bbf159d745 100644 --- a/mix.lock +++ b/mix.lock @@ -47,7 +47,7 @@ "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.10.2", "c8dbb3324ca35cea3679a96f4c774cdf4bdd425786a44c4f52aacb57a0cee446", [:mix], [{:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "0cc2124eccffa5438045c2504dd3365490b64065131f58ecc27f344db1edb4b8"}, "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.32.4", "5562148dfc631b04712983975093d2aac29df30b3bf2f7257e0c94b85b72e91b", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "6fd5a82f0785418fa8b698c0be2b1845dff92b77f1b3172c763d37868fb503d2"}, "ex_cldr_units": {:hex, :ex_cldr_units, "3.16.4", "fee054e9ebed40ef05cbb405cb0c7e7c9fda201f8f03ec0d1e54e879af413246", [:mix], [{:cldr_utils, "~> 2.24", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.31", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7c15c6357dd555a5bc6c72fdeb243e4706a04065753dbd2f40150f062ca996c7"}, - "ex_doc": {:hex, :ex_doc, "0.31.1", "8a2355ac42b1cc7b2379da9e40243f2670143721dd50748bf6c3b1184dae2089", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "3178c3a407c557d8343479e1ff117a96fd31bafe52a039079593fb0524ef61b0"}, + "ex_doc": {:hex, :ex_doc, "0.31.2", "8b06d0a5ac69e1a54df35519c951f1f44a7b7ca9a5bb7a260cd8a174d6322ece", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "317346c14febaba9ca40fd97b5b5919f7751fb85d399cc8e7e8872049f37e0af"}, "ex_json_schema": {:hex, :ex_json_schema, "0.10.2", "7c4b8c1481fdeb1741e2ce66223976edfb9bccebc8014f6aec35d4efe964fb71", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "37f43be60f8407659d4d0155a7e45e7f406dab1f827051d3d35858a709baf6a6"}, "ex_keccak": {:hex, :ex_keccak, "0.7.3", "33298f97159f6b0acd28f6e96ce5ea975a0f4a19f85fe615b4f4579b88b24d06", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6.1", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "4c5e6d9d5f77b64ab48769a0166a9814180d40ced68ed74ce60a5174ab55b3fc"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, @@ -77,8 +77,8 @@ "logger_file_backend": {:hex, :logger_file_backend, "0.0.13", "df07b14970e9ac1f57362985d76e6f24e3e1ab05c248055b7d223976881977c2", [:mix], [], "hexpm", "71a453a7e6e899ae4549fb147b1c6621f4233f8f48f58ca10a64ec67b6c50018"}, "logger_json": {:hex, :logger_json, "5.1.4", "9e30a4f2e31a8b9e402bdc20bd37cf9b67d3a31f19d0b33082a19a06b4c50f6d", [:mix], [{:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "3f20eea58e406a33d3eb7814c7dff5accb503bab2ee8601e84da02976fa3934c"}, "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"}, "math": {:hex, :math, "0.7.0", "12af548c3892abf939a2e242216c3e7cbfb65b9b2fe0d872d05c6fb609f8127b", [:mix], [], "hexpm", "7987af97a0c6b58ad9db43eb5252a49fc1dfe1f6d98f17da9282e297f594ebc2"}, "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "memento": {:hex, :memento, "0.3.2", "38cfc8ff9bcb1adff7cbd0f3b78a762636b86dff764729d1c82d0464c539bdd0", [:mix], [], "hexpm", "25cf691a98a0cb70262f4a7543c04bab24648cb2041d937eb64154a8d6f8012b"}, From f81423119e5e087a24bdc6b6547609c2f6cf69b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 18:25:56 +0000 Subject: [PATCH 571/607] Bump ecto from 3.11.1 to 3.11.2 Bumps [ecto](https://github.com/elixir-ecto/ecto) from 3.11.1 to 3.11.2. - [Release notes](https://github.com/elixir-ecto/ecto/releases) - [Changelog](https://github.com/elixir-ecto/ecto/blob/master/CHANGELOG.md) - [Commits](https://github.com/elixir-ecto/ecto/compare/v3.11.1...v3.11.2) --- updated-dependencies: - dependency-name: ecto dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 630b1b0d23c7..f6eb87f1de42 100644 --- a/mix.lock +++ b/mix.lock @@ -37,7 +37,7 @@ "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "digital_token": {:hex, :digital_token, "0.6.0", "13e6de581f0b1f6c686f7c7d12ab11a84a7b22fa79adeb4b50eec1a2d278d258", [:mix], [{:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "2455d626e7c61a128b02a4a8caddb092548c3eb613ac6f6a85e4cbb6caddc4d1"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, + "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, From 60c4817893c8dbf99f974a71ed35fa40ce758b3f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 18:28:20 +0000 Subject: [PATCH 572/607] Bump redix from 1.3.0 to 1.4.1 Bumps [redix](https://github.com/whatyouhide/redix) from 1.3.0 to 1.4.1. - [Changelog](https://github.com/whatyouhide/redix/blob/main/CHANGELOG.md) - [Commits](https://github.com/whatyouhide/redix/compare/v1.3.0...v1.4.1) --- updated-dependencies: - dependency-name: redix dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 630b1b0d23c7..fc0f006f6bd8 100644 --- a/mix.lock +++ b/mix.lock @@ -91,7 +91,7 @@ "mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"}, "msgpax": {:hex, :msgpax, "2.4.0", "4647575c87cb0c43b93266438242c21f71f196cafa268f45f91498541148c15d", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "ca933891b0e7075701a17507c61642bf6e0407bb244040d5d0a58597a06369d2"}, "nimble_csv": {:hex, :nimble_csv, "1.2.0", "4e26385d260c61eba9d4412c71cea34421f296d5353f914afe3f2e71cce97722", [:mix], [], "hexpm", "d0628117fcc2148178b034044c55359b26966c6eaa8e2ce15777be3bbc91b12a"}, - "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, + "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "number": {:hex, :number, "1.0.4", "3e6e6032a3c1d4c3760e77a42c580a57a15545dd993af380809da30fe51a032c", [:mix], [{:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "16f7516584ef2be812af4f33f2eaf3f9b9f6ed8892f45853eb93113f83721e42"}, "numbers": {:hex, :numbers, "5.2.4", "f123d5bb7f6acc366f8f445e10a32bd403c8469bdbce8ce049e1f0972b607080", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "eeccf5c61d5f4922198395bf87a465b6f980b8b862dd22d28198c5e6fab38582"}, @@ -121,7 +121,7 @@ "que": {:hex, :que, "0.10.1", "788ed0ec92ed69bdf9cfb29bf41a94ca6355b8d44959bd0669cf706e557ac891", [:mix], [{:ex_utils, "~> 0.1.6", [hex: :ex_utils, repo: "hexpm", optional: false]}, {:memento, "~> 0.3.0", [hex: :memento, repo: "hexpm", optional: false]}], "hexpm", "a737b365253e75dbd24b2d51acc1d851049e87baae08cd0c94e2bc5cd65088d5"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "ratio": {:hex, :ratio, "2.4.2", "c8518f3536d49b1b00d88dd20d49f8b11abb7819638093314a6348139f14f9f9", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:numbers, "~> 5.2.0", [hex: :numbers, repo: "hexpm", optional: false]}], "hexpm", "441ef6f73172a3503de65ccf1769030997b0d533b1039422f1e5e0e0b4cbf89e"}, - "redix": {:hex, :redix, "1.3.0", "f4121163ff9d73bf72157539ff23b13e38422284520bb58c05e014b19d6f0577", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:nimble_options, "~> 0.5.0 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "60d483d320c77329c8cbd3df73007e51b23f3fae75b7693bc31120d83ab26131"}, + "redix": {:hex, :redix, "1.4.1", "8303e13bad38ca80c15bdf79ea9cbd6eb879554c9cbb815b35df1602d7b1549d", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:nimble_options, "~> 0.5.0 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "676b5ce37d7b1d46931d506e3208786bd8334a1625ecb591d87d790b23ffbd1f"}, "remote_ip": {:hex, :remote_ip, "1.1.0", "cb308841595d15df3f9073b7c39243a1dd6ca56e5020295cb012c76fbec50f2d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "616ffdf66aaad6a72fc546dabf42eed87e2a99e97b09cbd92b10cc180d02ed74"}, "rustler_precompiled": {:hex, :rustler_precompiled, "0.6.3", "f838d94bc35e1844973ee7266127b156fdc962e9e8b7ff666c8fb4fed7964d23", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "e18ecca3669a7454b3a2be75ae6c3ef01d550bc9a8cf5fbddcfff843b881d7c6"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, From 4ccca2018404eff85c659b96ab3fd1b729e6d180 Mon Sep 17 00:00:00 2001 From: GimluCom <79271880+GimluCom@users.noreply.github.com> Date: Mon, 11 Mar 2024 22:58:59 +0100 Subject: [PATCH 573/607] Update external-backend.yml Removed "repends_on: backend" --- docker-compose/external-backend.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose/external-backend.yml b/docker-compose/external-backend.yml index cda9d8361fe3..82f03bf14dda 100644 --- a/docker-compose/external-backend.yml +++ b/docker-compose/external-backend.yml @@ -50,7 +50,6 @@ services: stats: depends_on: - stats-db - - backend extends: file: ./services/stats.yml service: stats From 354b7e45bc1f9d60fce4521fa8981c9e75f51bf0 Mon Sep 17 00:00:00 2001 From: Fedor Ivanov Date: Tue, 12 Mar 2024 10:33:32 +0300 Subject: [PATCH 574/607] Fix duplicate read methods (#9591) * Fix the typo in docs * Fix duplicated results in `methods-read` endpoint * Add regression test ensuring read-methods are not duplicated * Update `CHANGELOG.md` --- CHANGELOG.md | 1 + .../api/v2/smart_contract_controller_test.exs | 58 +++++++++++++++++++ .../lib/explorer/smart_contract/helper.ex | 3 +- .../lib/explorer/smart_contract/reader.ex | 1 + 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98e77e1aa93d..d115b9162d2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,7 @@ ### Fixes +- [#9591](https://github.com/blockscout/blockscout/pull/9591) - Fix duplicated results in `methods-read` endpoint - [#9502](https://github.com/blockscout/blockscout/pull/9502) - Add batch_size and concurrency envs for tt token type migration - [#9493](https://github.com/blockscout/blockscout/pull/9493) - Fix API response for unknown blob hashes - [#9484](https://github.com/blockscout/blockscout/pull/9484) - Fix read contract error diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index 6df1eddb73d3..eed9c4b6b742 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -1173,6 +1173,64 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do refute %{"type" => "receive"} in response end + test "ensure read-methods are not duplicated", %{conn: conn} do + abi = [ + %{ + "inputs" => [], + "name" => "test", + "outputs" => [ + %{"internalType" => "uint256", "name" => "", "type" => "uint256"} + ], + "stateMutability" => "pure", + "type" => "function" + } + ] + + id = + abi + |> ABI.parse_specification() + |> Enum.at(0) + |> Map.fetch!(:method_id) + + target_contract = insert(:smart_contract, abi: abi) + + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: "eth_call", params: _params}], _opts -> + {:ok, + [ + %{ + id: id, + jsonrpc: "2.0", + result: "0x00000000000000000000000000000000000000000000009d37020ac9049a8040" + } + ]} + end + ) + + request = get(conn, "/api/v2/smart-contracts/#{target_contract.address_hash}/methods-read") + + assert response = json_response(request, 200) + + assert response == [ + %{ + "type" => "function", + "stateMutability" => "pure", + "outputs" => [ + %{ + "type" => "uint256", + "value" => 2_900_102_562_052_921_000_000 + } + ], + "name" => "test", + "names" => ["uint256"], + "inputs" => [], + "method_id" => Base.encode16(id, case: :lower) + } + ] + end + test "get array of addresses within read-methods", %{conn: conn} do abi = [ %{ diff --git a/apps/explorer/lib/explorer/smart_contract/helper.ex b/apps/explorer/lib/explorer/smart_contract/helper.ex index c1efc76c91c5..27ce4413362e 100644 --- a/apps/explorer/lib/explorer/smart_contract/helper.ex +++ b/apps/explorer/lib/explorer/smart_contract/helper.ex @@ -18,7 +18,8 @@ defmodule Explorer.SmartContract.Helper do def error?(function), do: function["type"] == "error" @doc """ - Checks whether the function which is not queriable can be consider as read function or not. + Checks whether the function which is not queriable can be considered as read + function or not. """ @spec read_with_wallet_method?(%{}) :: true | false def read_with_wallet_method?(function), diff --git a/apps/explorer/lib/explorer/smart_contract/reader.ex b/apps/explorer/lib/explorer/smart_contract/reader.ex index 791782874ed4..c2906986102f 100644 --- a/apps/explorer/lib/explorer/smart_contract/reader.ex +++ b/apps/explorer/lib/explorer/smart_contract/reader.ex @@ -279,6 +279,7 @@ defmodule Explorer.SmartContract.Reader do abi_with_method_id = get_abi_with_method_id(abi) abi_with_method_id + |> Enum.reject(&Helper.queriable_method?(&1)) |> Enum.filter(&Helper.read_with_wallet_method?(&1)) end From 23ba638aec15ae9d7e44fb9dfc244adace730c8f Mon Sep 17 00:00:00 2001 From: carrychair Date: Tue, 12 Mar 2024 17:49:49 +0800 Subject: [PATCH 575/607] chore: remove repetitive words Signed-off-by: carrychair --- apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transport.ex | 2 +- apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/web_socket.ex | 2 +- apps/explorer/lib/explorer/graphql.ex | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transport.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transport.ex index dc7f1839a6b7..829f5816d3f8 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transport.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transport.ex @@ -84,7 +84,7 @@ defmodule EthereumJSONRPC.Transport do * `{:ok, result}` - `result` is the `/result` from JSONRPC response object of format `%{"id" => ..., "result" => result}`. - * `{:error, reason}` - `reason` is the the `/error` from JSONRPC response object of format + * `{:error, reason}` - `reason` is the `/error` from JSONRPC response object of format `%{"id" => ..., "error" => reason}`. The transport can also give any `term()` for `reason` if a more specific reason is possible. diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/web_socket.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/web_socket.ex index 43c890603290..cb89d0bb05fb 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/web_socket.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/web_socket.ex @@ -53,7 +53,7 @@ defmodule EthereumJSONRPC.WebSocket do * `{:ok, result}` - `result` is the `/result` from JSONRPC response object of format `%{"id" => ..., "result" => result}`. - * `{:error, reason}` - `reason` is the the `/error` from JSONRPC response object of format + * `{:error, reason}` - `reason` is the `/error` from JSONRPC response object of format `%{"id" => ..., "error" => reason}`. The transport can also give any `term()` for `reason` if a more specific reason is possible. diff --git a/apps/explorer/lib/explorer/graphql.ex b/apps/explorer/lib/explorer/graphql.ex index aa341e095f9a..a9d0b25ca0bf 100644 --- a/apps/explorer/lib/explorer/graphql.ex +++ b/apps/explorer/lib/explorer/graphql.ex @@ -24,7 +24,7 @@ defmodule Explorer.GraphQL do Returns a query to fetch transactions with a matching `to_address_hash`, `from_address_hash`, or `created_contract_address_hash` field for a given address hash. - Orders transactions by `block_number` and `index` according to to `order` + Orders transactions by `block_number` and `index` according to `order` """ @spec address_to_transactions_query(Hash.Address.t(), :desc | :asc) :: Ecto.Query.t() def address_to_transactions_query(address_hash, order) do From 8c44ca536993c38d28d3f9b11ffe9027c91caac4 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 12 Mar 2024 16:58:23 +0400 Subject: [PATCH 576/607] Fix token instance transform panic (#9601) * fix: token instance transform panic * chore: changelog --- CHANGELOG.md | 1 + .../lib/indexer/transform/token_instances.ex | 25 ++++++++----------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d115b9162d2f..45d6b6b4d3c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ### Fixes +- [#9601](https://github.com/blockscout/blockscout/pull/9601) - Fix token instance transform for some unconventional tokens - [#9572](https://github.com/blockscout/blockscout/pull/9572) - Fix Shibarium L1 fetcher - [#9563](https://github.com/blockscout/blockscout/pull/9563) - Fix timestamp handler for unfinalized zkEVM batches - [#9560](https://github.com/blockscout/blockscout/pull/9560) - Fix fetch pending transaction for hyperledger besu client diff --git a/apps/indexer/lib/indexer/transform/token_instances.ex b/apps/indexer/lib/indexer/transform/token_instances.ex index a9fb4372d2fd..e6f46e1c0d02 100644 --- a/apps/indexer/lib/indexer/transform/token_instances.ex +++ b/apps/indexer/lib/indexer/transform/token_instances.ex @@ -51,21 +51,18 @@ defmodule Indexer.Transform.TokenInstances do current_key = {token_contract_address_hash, token_id} - Map.put( + Map.update( acc, current_key, - Enum.max_by( - [ - params, - acc[current_key] || params - ], - fn %{ - owner_updated_at_block: owner_updated_at_block, - owner_updated_at_log_index: owner_updated_at_log_index - } -> - {owner_updated_at_block, owner_updated_at_log_index} - end - ) + params, + fn current -> + Enum.max_by([params, current], fn ti -> + { + Map.get(ti, :owner_updated_at_block, 0), + Map.get(ti, :owner_updated_at_log_index, 0) + } + end) + end ) end @@ -78,7 +75,7 @@ defmodule Indexer.Transform.TokenInstances do acc ) do Enum.reduce(token_ids, acc, fn id, sub_acc -> - Map.put(sub_acc, {token_contract_address_hash, id}, %{ + Map.put_new(sub_acc, {token_contract_address_hash, id}, %{ token_contract_address_hash: token_contract_address_hash, token_id: id, token_type: "ERC-1155" From f50018b37fdfcf92bbd40bdc522b76465673e049 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Mon, 11 Mar 2024 13:21:21 +0400 Subject: [PATCH 577/607] Update token transfers block_consensus by block_number --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/chain/import/runner/blocks.ex | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45d6b6b4d3c6..234e6b406e8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ ### Fixes - [#9601](https://github.com/blockscout/blockscout/pull/9601) - Fix token instance transform for some unconventional tokens +- [#9597](https://github.com/blockscout/blockscout/pull/9597) - Update token transfers block_consensus by block_number - [#9572](https://github.com/blockscout/blockscout/pull/9572) - Fix Shibarium L1 fetcher - [#9563](https://github.com/blockscout/blockscout/pull/9563) - Fix timestamp handler for unfinalized zkEVM batches - [#9560](https://github.com/blockscout/blockscout/pull/9560) - Fix fetch pending transaction for hyperledger besu client diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex index f2324ffad442..1c2e2e8a628c 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex @@ -377,7 +377,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do or_where: block.number in ^consensus_block_numbers, # we also need to acquire blocks that will be upserted here, for ordering or_where: block.hash in ^hashes, - select: block.hash, + select: %{hash: block.hash, number: block.number}, # Enforce Block ShareLocks order (see docs: sharelocks.md) order_by: [asc: block.hash], lock: "FOR NO KEY UPDATE" @@ -413,7 +413,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do from( token_transfer in TokenTransfer, join: s in subquery(acquire_query), - on: token_transfer.block_hash == s.hash, + on: token_transfer.block_number == s.number, # we don't want to remove consensus from blocks that will be upserted where: token_transfer.block_hash not in ^hashes ), From ca85e7354d4ae115da83df233b3498b81496431a Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Tue, 5 Mar 2024 13:31:31 +0300 Subject: [PATCH 578/607] Fix logging --- CHANGELOG.md | 1 + apps/indexer/lib/indexer/fetcher/block_reward.ex | 2 +- .../lib/indexer/fetcher/token_instance/metadata_retriever.ex | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 234e6b406e8d..a7c6e7513f8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - [#9601](https://github.com/blockscout/blockscout/pull/9601) - Fix token instance transform for some unconventional tokens - [#9597](https://github.com/blockscout/blockscout/pull/9597) - Update token transfers block_consensus by block_number +- [#9596](https://github.com/blockscout/blockscout/pull/9596) - Fix logging - [#9572](https://github.com/blockscout/blockscout/pull/9572) - Fix Shibarium L1 fetcher - [#9563](https://github.com/blockscout/blockscout/pull/9563) - Fix timestamp handler for unfinalized zkEVM batches - [#9560](https://github.com/blockscout/blockscout/pull/9560) - Fix fetch pending transaction for hyperledger besu client diff --git a/apps/indexer/lib/indexer/fetcher/block_reward.ex b/apps/indexer/lib/indexer/fetcher/block_reward.ex index 5b5ebe47fad0..4928cef1c811 100644 --- a/apps/indexer/lib/indexer/fetcher/block_reward.ex +++ b/apps/indexer/lib/indexer/fetcher/block_reward.ex @@ -325,7 +325,7 @@ defmodule Indexer.Fetcher.BlockReward do defp fetched_beneficiary_error_to_iodata(%{code: code, message: message, data: %{block_quantity: block_quantity}}) when is_integer(code) and is_binary(message) and is_binary(block_quantity) do - ["@", quantity_to_integer(block_quantity), ": (", to_string(code), ") ", message, ?\n] + ["@", block_quantity |> quantity_to_integer() |> to_string(), ": (", to_string(code), ") ", message, ?\n] end defp defaults do diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex b/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex index a713282e4c55..1a3a2ed48e2a 100644 --- a/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex +++ b/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex @@ -197,7 +197,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do check_content_type(content_type, uri, hex_token_id, body) {:ok, %Response{body: body, status_code: code}} -> - Logger.warn( + Logger.debug( ["Request to token uri: #{inspect(uri)} failed with code #{code}. Body:", inspect(body)], fetcher: :token_instances ) From 4fac356ed06f5c7fe448929e153e13ac55694f0e Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Fri, 8 Mar 2024 17:43:04 +0400 Subject: [PATCH 579/607] Fix Geth block internal transactions fetching --- CHANGELOG.md | 1 + .../lib/ethereum_jsonrpc/geth.ex | 19 ++- .../test/ethereum_jsonrpc/geth_test.exs | 154 ++++++++++++++++++ 3 files changed, 167 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7c6e7513f8b..6b8e3655f67c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - [#9601](https://github.com/blockscout/blockscout/pull/9601) - Fix token instance transform for some unconventional tokens - [#9597](https://github.com/blockscout/blockscout/pull/9597) - Update token transfers block_consensus by block_number - [#9596](https://github.com/blockscout/blockscout/pull/9596) - Fix logging +- [#9585](https://github.com/blockscout/blockscout/pull/9585) - Fix Geth block internal transactions fetching - [#9572](https://github.com/blockscout/blockscout/pull/9572) - Fix Shibarium L1 fetcher - [#9563](https://github.com/blockscout/blockscout/pull/9563) - Fix timestamp handler for unfinalized zkEVM batches - [#9560](https://github.com/blockscout/blockscout/pull/9560) - Fix fetch pending transaction for hyperledger besu client diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex index dc9b556892e3..fbd067d0d446 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex @@ -104,24 +104,29 @@ defmodule EthereumJSONRPC.Geth do end defp to_transactions_params(blocks_responses, id_to_params) do - Enum.reduce(blocks_responses, [], fn %{id: id, result: tx_result}, blocks_acc -> - extract_transactions_params(Map.fetch!(id_to_params, id), tx_result) ++ blocks_acc + blocks_responses + |> Enum.reduce({[], 0}, fn %{id: id, result: tx_result}, {blocks_acc, counter} -> + {transactions_params, _, new_counter} = + extract_transactions_params(Map.fetch!(id_to_params, id), tx_result, counter) + + {transactions_params ++ blocks_acc, new_counter} end) + |> elem(0) end - defp extract_transactions_params(block_number, tx_result) do - tx_result - |> Enum.reduce({[], 0}, fn %{"txHash" => tx_hash, "result" => calls_result}, {tx_acc, counter} -> + defp extract_transactions_params(block_number, tx_result, counter) do + Enum.reduce(tx_result, {[], 0, counter}, fn %{"txHash" => tx_hash, "result" => calls_result}, + {tx_acc, inner_counter, counter} -> { [ - {%{block_number: block_number, hash_data: tx_hash, transaction_index: counter, id: counter}, + {%{block_number: block_number, hash_data: tx_hash, transaction_index: inner_counter, id: counter}, %{id: counter, result: calls_result}} | tx_acc ], + inner_counter + 1, counter + 1 } end) - |> elem(0) end @doc """ diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs index cc22f8baf483..ed1202cf3b12 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs @@ -642,6 +642,160 @@ defmodule EthereumJSONRPC.GethTest do ]} = Geth.fetch_block_internal_transactions([block_number], json_rpc_named_arguments) end + test "works for multiple blocks request", %{json_rpc_named_arguments: json_rpc_named_arguments} do + block_number_1 = 3_287_375 + block_number_2 = 3_287_376 + block_quantity_1 = EthereumJSONRPC.integer_to_quantity(block_number_1) + block_quantity_2 = EthereumJSONRPC.integer_to_quantity(block_number_2) + transaction_hash_1 = "0x32b17f27ddb546eab3c4c33f31eb22c1cb992d4ccc50dae26922805b717efe5c" + transaction_hash_2 = "0x32b17f27ddb546eab3c4c33f31eb22c1cb992d4ccc50dae26922805b717efe5b" + + expect(EthereumJSONRPC.Mox, :json_rpc, fn + [ + %{id: id_1, params: [^block_quantity_1, %{"tracer" => "callTracer"}]}, + %{id: id_2, params: [^block_quantity_2, %{"tracer" => "callTracer"}]} + ], + _ -> + {:ok, + [ + %{ + id: id_1, + result: [ + %{ + "result" => %{ + "calls" => [ + %{ + "from" => "0x4200000000000000000000000000000000000015", + "gas" => "0xe9a3c", + "gasUsed" => "0x4a28", + "input" => + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + "to" => "0x6df83a19647a398d48e77a6835f4a28eb7e2f7c0", + "type" => "DELEGATECALL", + "value" => "0x0" + } + ], + "from" => "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001", + "gas" => "0xf4240", + "gasUsed" => "0xb6f9", + "input" => + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + "to" => "0x4200000000000000000000000000000000000015", + "type" => "CALL", + "value" => "0x0" + }, + "txHash" => transaction_hash_1 + } + ] + }, + %{ + id: id_2, + result: [ + %{ + "result" => %{ + "calls" => [ + %{ + "from" => "0x4200000000000000000000000000000000000015", + "gas" => "0xe9a3c", + "gasUsed" => "0x4a28", + "input" => + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + "to" => "0x6df83a19647a398d48e77a6835f4a28eb7e2f7c0", + "type" => "DELEGATECALL", + "value" => "0x0" + } + ], + "from" => "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001", + "gas" => "0xf4240", + "gasUsed" => "0xb6f9", + "input" => + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + "to" => "0x4200000000000000000000000000000000000015", + "type" => "CALL", + "value" => "0x0" + }, + "txHash" => transaction_hash_2 + } + ] + } + ]} + end) + + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + + assert {:ok, + [ + %{ + block_number: ^block_number_1, + call_type: "call", + from_address_hash: "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001", + gas: 1_000_000, + gas_used: 46841, + index: 0, + input: + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + output: "0x", + to_address_hash: "0x4200000000000000000000000000000000000015", + trace_address: [], + transaction_hash: ^transaction_hash_1, + transaction_index: 0, + type: "call", + value: 0 + }, + %{ + block_number: ^block_number_1, + call_type: "delegatecall", + from_address_hash: "0x4200000000000000000000000000000000000015", + gas: 956_988, + gas_used: 18984, + index: 1, + input: + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + output: "0x", + to_address_hash: "0x6df83a19647a398d48e77a6835f4a28eb7e2f7c0", + trace_address: [0], + transaction_hash: ^transaction_hash_1, + transaction_index: 0, + type: "call", + value: 0 + }, + %{ + block_number: ^block_number_2, + call_type: "call", + from_address_hash: "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001", + gas: 1_000_000, + gas_used: 46841, + index: 0, + input: + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + output: "0x", + to_address_hash: "0x4200000000000000000000000000000000000015", + trace_address: [], + transaction_hash: ^transaction_hash_2, + transaction_index: 0, + type: "call", + value: 0 + }, + %{ + block_number: ^block_number_2, + call_type: "delegatecall", + from_address_hash: "0x4200000000000000000000000000000000000015", + gas: 956_988, + gas_used: 18984, + index: 1, + input: + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + output: "0x", + to_address_hash: "0x6df83a19647a398d48e77a6835f4a28eb7e2f7c0", + trace_address: [0], + transaction_hash: ^transaction_hash_2, + transaction_index: 0, + type: "call", + value: 0 + } + ]} = Geth.fetch_block_internal_transactions([block_number_1, block_number_2], json_rpc_named_arguments) + end + test "result is the same as fetch_internal_transactions/2", %{json_rpc_named_arguments: json_rpc_named_arguments} do block_number = 3_287_375 block_quantity = EthereumJSONRPC.integer_to_quantity(block_number) From ed064c3ec2ff0fbd8a9fcb5cb3492fe63e20a3f0 Mon Sep 17 00:00:00 2001 From: Fedor Ivanov Date: Tue, 12 Mar 2024 21:44:54 +0300 Subject: [PATCH 580/607] Fix skipped read methods (#9621) * Bump ex_abi from 0.7.0 to 0.7.1 * Add regression test for function ABI missing `outputs` field --- .../explorer/smart_contract/reader_test.exs | 29 +++++++++++++++++++ mix.lock | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/apps/explorer/test/explorer/smart_contract/reader_test.exs b/apps/explorer/test/explorer/smart_contract/reader_test.exs index 8eb6e1a2ec6c..4b8a632c5d19 100644 --- a/apps/explorer/test/explorer/smart_contract/reader_test.exs +++ b/apps/explorer/test/explorer/smart_contract/reader_test.exs @@ -102,6 +102,35 @@ defmodule Explorer.SmartContract.ReaderTest do assert %{"6d4ce63c" => {:error, "no function clause matches"}} = response end + + test "with function ABI that is missing the outputs field" do + smart_contract = + build(:smart_contract, + abi: [ + %{ + "type" => "function", + "stateMutability" => "view", + "name" => "assumeLastTokenIdMatches", + "inputs" => [ + %{ + "type" => "uint256", + "name" => "lastTokenId", + "internalType" => "uint256" + } + ] + } + ] + ) + + contract_address_hash = Hash.to_string(smart_contract.address_hash) + abi = smart_contract.abi + + blockchain_get_function_mock() + + response = Reader.query_contract(contract_address_hash, abi, %{"e72878b4" => [123]}, false) + + assert response == %{"e72878b4" => {:ok, []}} + end end describe "query_verified_contract/3" do diff --git a/mix.lock b/mix.lock index f1f409f55968..7248f7832a11 100644 --- a/mix.lock +++ b/mix.lock @@ -41,7 +41,7 @@ "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_abi": {:hex, :ex_abi, "0.7.0", "4a2f83c47d98357c75862ca77fcbaa05d9199ba2888248fdb20d4f3befc3e151", [:mix], [{:ex_keccak, "~> 0.7.3", [hex: :ex_keccak, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d35c4ef7d860fd94bcf952ab866b7639df9054c009c098ddc94cf8916d2208d6"}, + "ex_abi": {:hex, :ex_abi, "0.7.1", "8136d8363653418442848009ed5d31312161781326d867e77467608b2d5ff2c9", [:mix], [{:ex_keccak, "~> 0.7.3", [hex: :ex_keccak, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41ad2de3f62b6e79f7e865c7ab03f58e0d44435325ff15e345a986e0387a2a04"}, "ex_cldr": {:hex, :ex_cldr, "2.37.5", "9da6d97334035b961d2c2de167dc6af8cd3e09859301a5b8f49f90bd8b034593", [:mix], [{:cldr_utils, "~> 2.21", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "74ad5ddff791112ce4156382e171a5f5d3766af9d5c4675e0571f081fe136479"}, "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.15.1", "e92ba17c41e7405b7784e0e65f406b5f17cfe313e0e70de9befd653e12854822", [:mix], [{:ex_cldr, "~> 2.34", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "31df8bd37688340f8819bdd770eb17d659652078d34db632b85d4a32864d6a25"}, "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.10.2", "c8dbb3324ca35cea3679a96f4c774cdf4bdd425786a44c4f52aacb57a0cee446", [:mix], [{:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "0cc2124eccffa5438045c2504dd3365490b64065131f58ecc27f344db1edb4b8"}, From 2f49bf0f61ea5b4108a20fc42bf33dac33c9166f Mon Sep 17 00:00:00 2001 From: one230six <723682061@qq.com> Date: Wed, 13 Mar 2024 15:40:26 +0800 Subject: [PATCH 581/607] chore: fix some comments Signed-off-by: one230six <723682061@qq.com> --- CHANGELOG.md | 2 +- .../explorer/lib/explorer/chain/block/second_degree_relation.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b8e3655f67c..28222921130f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3036,7 +3036,7 @@ fixed menu hovers in dark mode desktop view - [#2617](https://github.com/blockscout/blockscout/pull/2617) - skip cache update if there are no blocks inserted - [#2611](https://github.com/blockscout/blockscout/pull/2611) - fix js dependency vulnerabilities - [#2594](https://github.com/blockscout/blockscout/pull/2594) - do not start genesis data fetching periodically -- [#2590](https://github.com/blockscout/blockscout/pull/2590) - restore backward compatablity with old releases +- [#2590](https://github.com/blockscout/blockscout/pull/2590) - restore backward compatibility with old releases - [#2577](https://github.com/blockscout/blockscout/pull/2577) - Need recompile column in the env vars table - [#2574](https://github.com/blockscout/blockscout/pull/2574) - limit request body in json rpc error - [#2566](https://github.com/blockscout/blockscout/pull/2566) - upgrade absinthe phoenix diff --git a/apps/explorer/lib/explorer/chain/block/second_degree_relation.ex b/apps/explorer/lib/explorer/chain/block/second_degree_relation.ex index 2f6af19fbb72..941dc51480e7 100644 --- a/apps/explorer/lib/explorer/chain/block/second_degree_relation.ex +++ b/apps/explorer/lib/explorer/chain/block/second_degree_relation.ex @@ -8,7 +8,7 @@ defmodule Explorer.Chain.Block.SecondDegreeRelation do Uncles occur when a Proof-of-Work proof is completed slightly late, but before the next block is completes, so the network knows about the late proof and can credit as an uncle in the next block. - This schema is the join schema between the `nephew` and the `uncle` it is is including the `uncle`. The actual + This schema is the join schema between the `nephew` and the `uncle` it is including the `uncle`. The actual `uncle` block is still a normal `t:Explorer.Chain.Block.t/0`. """ From f5674ff38e4d28b7981f7329eea8d7d10abea134 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Wed, 13 Mar 2024 12:55:00 +0300 Subject: [PATCH 582/607] Improve gasprice oracle time estimation fallback (#9582) * Improve gasprice oracle time estimation fallback * Fix review comments --- .../explorer/chain/cache/gas_price_oracle.ex | 230 ++++++------------ .../chain/cache/gas_price_oracle_test.exs | 49 ++-- config/config_helper.exs | 11 + config/runtime.exs | 5 +- docker-compose/envs/common-blockscout.env | 3 + 5 files changed, 129 insertions(+), 169 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex index 163f24eb5763..ad2fdb00f941 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex @@ -10,12 +10,7 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do from: 2 ] - alias Explorer.Chain.{ - Block, - DenormalizationHelper, - Transaction, - Wei - } + alias Explorer.Chain.{Block, Wei} alias Explorer.Counters.AverageBlockTime alias Explorer.{Market, Repo} @@ -99,150 +94,79 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do end fee_query = - if DenormalizationHelper.transactions_denormalization_finished?() do - from( - transaction in Transaction, - where: transaction.block_consensus == true, - where: transaction.status == ^1, - where: is_nil(transaction.gas_price) or 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: is_nil(transaction.gas_price) or 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 + from( + block in Block, + left_join: transaction in assoc(block, :transactions), + where: block.consensus == true, + where: is_nil(transaction.gas_price) or 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 least(?, ?) )", + ^safelow_percentile_fraction, + transaction.max_priority_fee_per_gas, + transaction.max_fee_per_gas - block.base_fee_per_gas + ), + average_priority_fee_per_gas: + fragment( + "percentile_disc(? :: real) within group ( order by least(?, ?) )", + ^average_percentile_fraction, + transaction.max_priority_fee_per_gas, + transaction.max_fee_per_gas - block.base_fee_per_gas + ), + fast_priority_fee_per_gas: + fragment( + "percentile_disc(? :: real) within group ( order by least(?, ?) )", + ^fast_percentile_fraction, + transaction.max_priority_fee_per_gas, + transaction.max_fee_per_gas - block.base_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_block_time * safelow_time_coefficient()) + ), + 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 && average_block_time * average_time_coefficient()) + ), + 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 && average_block_time * fast_time_coefficient()) + ) + }, + limit: ^num_of_blocks + ) new_acc = fee_query |> Repo.all(timeout: :infinity) |> merge_gas_prices(acc, num_of_blocks) @@ -372,6 +296,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do defp fast, do: Application.get_env(:explorer, __MODULE__)[:fast_percentile] + defp safelow_time_coefficient, do: Application.get_env(:explorer, __MODULE__)[:safelow_time_coefficient] + + defp average_time_coefficient, do: Application.get_env(:explorer, __MODULE__)[:average_time_coefficient] + + defp fast_time_coefficient, do: Application.get_env(:explorer, __MODULE__)[:fast_time_coefficient] + defp handle_fallback(:gas_prices) do # This will get the task PID if one exists and launch a new task if not # See next `handle_fallback` definition diff --git a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs index 1fdccd0a1bc6..5fa0fc97a454 100644 --- a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs +++ b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs @@ -28,7 +28,7 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do }}, []} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) end - test "returns nil percentile values for blocks with failed txs in the DB" do + test "returns gas prices for blocks with failed txs in the DB" do block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") :transaction @@ -44,12 +44,14 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269" ) - assert {{:ok, - %{ - slow: nil, - average: nil, - fast: nil - }}, []} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + assert {{ + :ok, + %{ + average: %{price: 0.01}, + fast: %{price: 0.01}, + slow: %{price: 0.01} + } + }, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) end test "returns nil percentile values for transactions with 0 gas price aka 'whitelisted transactions' in the DB" do @@ -276,8 +278,8 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do assert {{:ok, %{ # including base fee - slow: %{price: 1.5}, - average: %{price: 2.5} + slow: %{price: 1.25}, + average: %{price: 2.25} }}, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) end @@ -350,18 +352,31 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do assert {{ :ok, %{ - average: %{price: 2.5, time: 15000.0}, - fast: %{price: 2.5, time: 15000.0}, - slow: %{price: 1.5, time: 17500.0} + average: %{price: 2.25, time: 15000.0}, + fast: %{price: 2.25, time: 15000.0}, + slow: %{price: 1.25, time: 17500.0} } }, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) end test "return gas prices with average block time if earliest_processing_start is not available" do - old_env = Application.get_env(:explorer, AverageBlockTime) + average_block_time_old_env = Application.get_env(:explorer, AverageBlockTime) + gas_price_oracle_old_env = Application.get_env(:explorer, GasPriceOracle) + + Application.put_env(:explorer, GasPriceOracle, + safelow_time_coefficient: 2.5, + average_time_coefficient: 1.5, + fast_time_coefficient: 1 + ) + Application.put_env(:explorer, AverageBlockTime, enabled: true, cache_period: 1_800_000) start_supervised!(AverageBlockTime) + on_exit(fn -> + Application.put_env(:explorer, AverageBlockTime, average_block_time_old_env) + Application.put_env(:explorer, GasPriceOracle, gas_price_oracle_old_env) + end) + block_number = 99_999_999 first_timestamp = ~U[2023-12-12 12:12:30.000000Z] @@ -439,13 +454,11 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do assert {{ :ok, %{ - average: %{price: 2.5, time: 1000.0}, - fast: %{price: 2.5, time: 1000.0}, - slow: %{price: 1.5, time: 1000.0} + average: %{price: 2.25, time: 1500.0}, + fast: %{price: 2.25, time: 1000.0}, + slow: %{price: 1.25, time: 2500.0} } }, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) - - Application.put_env(:explorer, AverageBlockTime, old_env) end end end diff --git a/config/config_helper.exs b/config/config_helper.exs index 5c2eab9dd5e6..6891ae75910c 100644 --- a/config/config_helper.exs +++ b/config/config_helper.exs @@ -61,6 +61,17 @@ defmodule ConfigHelper do end end + @spec parse_float_env_var(String.t(), float()) :: float() + def parse_float_env_var(env_var, default_value) do + env_var + |> safe_get_env(to_string(default_value)) + |> Float.parse() + |> case do + {float, _} -> float + _ -> 0 + end + end + @spec parse_integer_or_nil_env_var(String.t()) :: non_neg_integer() | nil def parse_integer_or_nil_env_var(env_var) do env_var diff --git a/config/runtime.exs b/config/runtime.exs index 9760b0300549..5ba06802677f 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -253,7 +253,10 @@ config :explorer, Explorer.Chain.Cache.GasPriceOracle, num_of_blocks: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_NUM_OF_BLOCKS", 200), safelow_percentile: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_SAFELOW_PERCENTILE", 35), average_percentile: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_AVERAGE_PERCENTILE", 60), - fast_percentile: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_FAST_PERCENTILE", 90) + fast_percentile: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_FAST_PERCENTILE", 90), + safelow_time_coefficient: ConfigHelper.parse_float_env_var("GAS_PRICE_ORACLE_SAFELOW_TIME_COEFFICIENT", 5), + average_time_coefficient: ConfigHelper.parse_float_env_var("GAS_PRICE_ORACLE_AVERAGE_TIME_COEFFICIENT", 3), + fast_time_coefficient: ConfigHelper.parse_float_env_var("GAS_PRICE_ORACLE_FAST_TIME_COEFFICIENT", 1) config :explorer, Explorer.Chain.Cache.RootstockLockedBTC, enabled: System.get_env("ETHEREUM_JSONRPC_VARIANT") == "rsk", diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 959db856b6f4..6268f16202af 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -249,6 +249,9 @@ EXTERNAL_APPS=[] # GAS_PRICE_ORACLE_SAFELOW_PERCENTILE= # GAS_PRICE_ORACLE_AVERAGE_PERCENTILE= # GAS_PRICE_ORACLE_FAST_PERCENTILE= +# GAS_PRICE_ORACLE_SAFELOW_TIME_COEFFICIENT= +# GAS_PRICE_ORACLE_AVERAGE_TIME_COEFFICIENT= +# GAS_PRICE_ORACLE_FAST_TIME_COEFFICIENT= # RESTRICTED_LIST= # RESTRICTED_LIST_KEY= SHOW_MAINTENANCE_ALERT=false From 610f6223abffb1032cc1ec10919c5072d04b4ff5 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Wed, 13 Mar 2024 13:11:01 +0300 Subject: [PATCH 583/607] API v1 bridgedtokenlist endpoint (#9506) * bridgedtokenlist API endpoint * Change spec for bridged_token_usd_cap function * Fix review comments * Return bridged_token_usd_cap as string --- CHANGELOG.md | 1 + .../controllers/api/rpc/token_controller.ex | 33 + .../lib/block_scout_web/etherscan.ex | 126 +- .../views/api/rpc/token_view.ex | 23 + .../views/bridged_tokens_view.ex | 24 + apps/explorer/lib/explorer/chain.ex | 21 - .../contracts_abi/posdao/BlockRewardAuRa.json | 636 ---------- .../priv/contracts_abi/posdao/README.md | 1 - .../contracts_abi/posdao/StakingAuRa.json | 1127 ----------------- .../priv/contracts_abi/posdao/Token.json | 968 -------------- .../posdao/ValidatorSetAuRa.json | 734 ----------- cspell.json | 3 + 12 files changed, 206 insertions(+), 3491 deletions(-) create mode 100644 apps/block_scout_web/lib/block_scout_web/views/bridged_tokens_view.ex delete mode 100644 apps/explorer/priv/contracts_abi/posdao/BlockRewardAuRa.json delete mode 100644 apps/explorer/priv/contracts_abi/posdao/README.md delete mode 100644 apps/explorer/priv/contracts_abi/posdao/StakingAuRa.json delete mode 100644 apps/explorer/priv/contracts_abi/posdao/Token.json delete mode 100644 apps/explorer/priv/contracts_abi/posdao/ValidatorSetAuRa.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b8e3655f67c..b0543ed13edf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ - [#9571](https://github.com/blockscout/blockscout/pull/9571) - Support Optimism Ecotone upgrade by Indexer.Fetcher.Optimism.TxnBatch module - [#9562](https://github.com/blockscout/blockscout/pull/9562) - Add cancun evm version +- [#9506](https://github.com/blockscout/blockscout/pull/9506) - API v1 bridgedtokenlist endpoint - [#9260](https://github.com/blockscout/blockscout/pull/9260) - Optimism Delta upgrade support by Indexer.Fetcher.OptimismTxnBatch module - [#8740](https://github.com/blockscout/blockscout/pull/8740) - Add delay to Indexer.Fetcher.OptimismTxnBatch module initialization diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/token_controller.ex index 4f6ccd5a448d..51e1aff18221 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/token_controller.ex @@ -3,6 +3,7 @@ defmodule BlockScoutWeb.API.RPC.TokenController do alias BlockScoutWeb.API.RPC.Helper alias Explorer.{Chain, PagingOptions} + alias Explorer.Chain.BridgedToken def gettoken(conn, params) do with {:contractaddress_param, {:ok, contractaddress_param}} <- fetch_contractaddress(params), @@ -50,6 +51,38 @@ defmodule BlockScoutWeb.API.RPC.TokenController do end end + if Application.compile_env(:explorer, BridgedToken)[:enabled] do + @api_true [api?: true] + def bridgedtokenlist(conn, params) do + import BlockScoutWeb.PagingHelper, + only: [ + chain_ids_filter_options: 1, + tokens_sorting: 1 + ] + + import BlockScoutWeb.Chain, + only: [ + paging_options: 1 + ] + + bridged_tokens = + if BridgedToken.enabled?() do + options = + params + |> paging_options() + |> Keyword.merge(chain_ids_filter_options(params)) + |> Keyword.merge(tokens_sorting(params)) + |> Keyword.merge(@api_true) + + "" |> BridgedToken.list_top_bridged_tokens(options) + else + [] + end + + render(conn, "bridgedtokenlist.json", %{bridged_tokens: bridged_tokens}) + end + end + defp fetch_contractaddress(params) do {:contractaddress_param, Map.fetch(params, "contractaddress")} end diff --git a/apps/block_scout_web/lib/block_scout_web/etherscan.ex b/apps/block_scout_web/lib/block_scout_web/etherscan.ex index e477eb9dca5c..b6be61809eb5 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -2,6 +2,7 @@ defmodule BlockScoutWeb.Etherscan do @moduledoc """ Documentation data for Etherscan-compatible API. """ + alias Explorer.Chain.BridgedToken @account_balance_example_value %{ "status" => "1", @@ -2009,6 +2010,117 @@ defmodule BlockScoutWeb.Etherscan do ] } + if Application.compile_env(:explorer, BridgedToken)[:enabled] do + @success_status_type %{ + type: "status", + enum: ~s(["1"]), + enum_interpretation: %{"1" => "ok"} + } + + @bridged_token_details %{ + name: "Bridged Token Detail", + fields: %{ + foreignChainId: %{ + type: "value", + definition: "Chain ID of the chain where original token exists.", + example: ~s("1") + }, + foreignTokenContractAddressHash: @address_hash_type, + homeContractAddressHash: @address_hash_type, + homeDecimals: @token_decimal_type, + homeHolderCount: %{ + type: "value", + definition: "Token holders count.", + example: ~s("393") + }, + homeName: @token_name_type, + homeSymbol: @token_symbol_type, + homeTotalSupply: %{ + type: "value", + definition: "Total supply of the token on the home side (where token was bridged).", + example: ~s("1484374.775044204093387391") + }, + homeUsdValue: %{ + type: "value", + definition: "Total supply of the token on the home side (where token was bridged) in USD.", + example: ~s("6638727.472651464170990256943") + } + } + } + + @token_bridgedtokenlist_example_value %{ + "status" => "1", + "message" => "OK", + "result" => [ + %{ + "foreignChainId" => "1", + "foreignTokenContractAddressHash" => "0x0ae055097c6d159879521c384f1d2123d1f195e6", + "homeContractAddressHash" => "0xb7d311e2eb55f2f68a9440da38e7989210b9a05e", + "homeDecimals" => "18", + "homeHolderCount" => 393, + "homeName" => "STAKE on xDai", + "homeSymbol" => "STAKE", + "homeTotalSupply" => "1484374.775044204093387391", + "homeUsdValue" => "18807028.39981006586321824397" + }, + %{ + "foreignChainId" => "1", + "foreignTokenContractAddressHash" => "0xf5581dfefd8fb0e4aec526be659cfab1f8c781da", + "homeContractAddressHash" => "0xd057604a14982fe8d88c5fc25aac3267ea142a08", + "homeDecimals" => "18", + "homeHolderCount" => 73, + "homeName" => "HOPR Token on xDai", + "homeSymbol" => "HOPR", + "homeTotalSupply" => "26600449.86076749062791602", + "homeUsdValue" => "6638727.472651464170990256943" + } + ] + } + + @token_bridgedtokenlist_action %{ + name: "bridgedTokenList", + description: "Get bridged tokens list.", + required_params: [], + optional_params: [ + %{ + key: "chainid", + type: "integer", + description: "A nonnegative integer that represents the chain id, where original token exists." + }, + %{ + key: "page", + type: "integer", + description: + "A nonnegative integer that represents the page number to be used for pagination. 'offset' must be provided in conjunction." + }, + %{ + key: "offset", + type: "integer", + description: + "A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction." + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@token_bridgedtokenlist_example_value), + model: %{ + name: "Result", + fields: %{ + status: @success_status_type, + message: @message_type, + result: %{ + type: "array", + array_type: @bridged_token_details + } + } + } + } + ] + } + end + @stats_tokensupply_action %{ name: "tokensupply", description: @@ -2947,12 +3059,18 @@ defmodule BlockScoutWeb.Etherscan do actions: [@logs_getlogs_action] } + @base_token_actions [ + @token_gettoken_action, + @token_gettokenholders_action + ] + + @token_actions if Application.compile_env(:explorer, BridgedToken)[:enabled], + do: [@token_bridgedtokenlist_action, @base_token_actions], + else: @base_token_actions + @token_module %{ name: "token", - actions: [ - @token_gettoken_action, - @token_gettokenholders_action - ] + actions: @token_actions } @stats_module %{ diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/token_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/token_view.ex index 9ccab7c9d163..693e5c3c72ab 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/token_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/token_view.ex @@ -2,6 +2,7 @@ defmodule BlockScoutWeb.API.RPC.TokenView do use BlockScoutWeb, :view alias BlockScoutWeb.API.RPC.RPCView + alias BlockScoutWeb.{BridgedTokensView, CurrencyHelper} def render("gettoken.json", %{token: token}) do RPCView.render("show.json", data: prepare_token(token)) @@ -12,6 +13,11 @@ defmodule BlockScoutWeb.API.RPC.TokenView do RPCView.render("show.json", data: data) end + def render("bridgedtokenlist.json", %{bridged_tokens: bridged_tokens}) do + data = Enum.map(bridged_tokens, &prepare_bridged_token/1) + RPCView.render("show.json", data: data) + end + def render("error.json", assigns) do RPCView.render("error.json", assigns) end @@ -34,4 +40,21 @@ defmodule BlockScoutWeb.API.RPC.TokenView do "value" => token_holder.value } end + + defp prepare_bridged_token({token, bridged_token}) do + total_supply = CurrencyHelper.divide_decimals(token.total_supply, token.decimals) + usd_value = BridgedTokensView.bridged_token_usd_cap(bridged_token, token) + + %{ + "foreignChainId" => bridged_token.foreign_chain_id, + "foreignTokenContractAddressHash" => bridged_token.foreign_token_contract_address_hash, + "homeContractAddressHash" => token.contract_address_hash, + "homeDecimals" => token.decimals, + "homeHolderCount" => if(token.holder_count, do: to_string(token.holder_count), else: "0"), + "homeName" => token.name, + "homeSymbol" => token.symbol, + "homeTotalSupply" => total_supply, + "homeUsdValue" => usd_value + } + end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/bridged_tokens_view.ex b/apps/block_scout_web/lib/block_scout_web/views/bridged_tokens_view.ex new file mode 100644 index 000000000000..3cf7e32512c7 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/bridged_tokens_view.ex @@ -0,0 +1,24 @@ +defmodule BlockScoutWeb.BridgedTokensView do + use BlockScoutWeb, :view + + alias Explorer.Chain.{BridgedToken, CurrencyHelper, Token} + + @doc """ + Calculates capitalization of the bridged token in USD. + """ + @spec bridged_token_usd_cap(BridgedToken.t(), Token.t()) :: String.t() + def bridged_token_usd_cap(bridged_token, token) do + usd_cap = + if bridged_token.custom_cap do + bridged_token.custom_cap + else + if bridged_token.exchange_rate && token.total_supply do + Decimal.mult(bridged_token.exchange_rate, CurrencyHelper.divide_decimals(token.total_supply, token.decimals)) + else + Decimal.new(0) + end + end + + usd_cap |> Decimal.to_float() |> :erlang.float_to_binary([:compact, decimals: 20]) + end +end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 2f8fa0dff1ff..70633182812b 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -3545,27 +3545,6 @@ defmodule Explorer.Chain do |> Repo.stream_reduce(initial, reducer) end - def decode_contract_address_hash_response(resp) do - case resp do - "0x000000000000000000000000" <> address -> - "0x" <> address - - _ -> - nil - end - end - - def decode_contract_integer_response(resp) do - case resp do - "0x" <> integer_encoded -> - {integer_value, _} = Integer.parse(integer_encoded, 16) - integer_value - - _ -> - nil - end - end - @doc """ Fetches a `t:Token.t/0` by an address hash. diff --git a/apps/explorer/priv/contracts_abi/posdao/BlockRewardAuRa.json b/apps/explorer/priv/contracts_abi/posdao/BlockRewardAuRa.json deleted file mode 100644 index 49737b7a3220..000000000000 --- a/apps/explorer/priv/contracts_abi/posdao/BlockRewardAuRa.json +++ /dev/null @@ -1,636 +0,0 @@ -[ - { - "constant": true, - "inputs": [ - { - "name": "_poolStakingAddress", - "type": "address" - }, - { - "name": "_staker", - "type": "address" - } - ], - "name": "epochsToClaimRewardFrom", - "outputs": [ - { - "name": "epochsToClaimFrom", - "type": "uint256[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "bridgeTokenReward", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - }, - { - "name": "", - "type": "uint256" - } - ], - "name": "mintedForAccountInBlock", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - }, - { - "name": "", - "type": "uint256" - } - ], - "name": "epochPoolNativeReward", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "mintedForAccount", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "mintedInBlock", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "mintedTotally", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "tokenRewardUndistributed", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "nativeRewardUndistributed", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "mintedTotallyByBridge", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - }, - { - "name": "", - "type": "uint256" - } - ], - "name": "epochPoolTokenReward", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "validatorMinRewardPercent", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "validatorSetContract", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "bridgeNativeReward", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - }, - { - "name": "", - "type": "uint256" - } - ], - "name": "blocksCreated", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "amount", - "type": "uint256" - }, - { - "indexed": true, - "name": "receiver", - "type": "address" - }, - { - "indexed": true, - "name": "bridge", - "type": "address" - } - ], - "name": "AddedReceiver", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "receivers", - "type": "address[]" - }, - { - "indexed": false, - "name": "rewards", - "type": "uint256[]" - } - ], - "name": "MintedNative", - "type": "event" - }, - { - "constant": false, - "inputs": [ - { - "name": "_amount", - "type": "uint256" - } - ], - "name": "addBridgeNativeRewardReceivers", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_amount", - "type": "uint256" - } - ], - "name": "addBridgeTokenRewardReceivers", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_amount", - "type": "uint256" - }, - { - "name": "_receiver", - "type": "address" - } - ], - "name": "addExtraReceiver", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_validatorSet", - "type": "address" - }, - { - "name": "_prevBlockReward", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_bridge", - "type": "address" - }, - { - "name": "_prevBlockRewardContract", - "type": "address" - } - ], - "name": "migrateMintingStatistics", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "benefactors", - "type": "address[]" - }, - { - "name": "kind", - "type": "uint16[]" - } - ], - "name": "reward", - "outputs": [ - { - "name": "receiversNative", - "type": "address[]" - }, - { - "name": "rewardsNative", - "type": "uint256[]" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_bridgesAllowed", - "type": "address[]" - } - ], - "name": "setErcToNativeBridgesAllowed", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_bridgesAllowed", - "type": "address[]" - } - ], - "name": "setNativeToErcBridgesAllowed", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_bridgesAllowed", - "type": "address[]" - } - ], - "name": "setErcToErcBridgesAllowed", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "blockRewardContractId", - "outputs": [ - { - "name": "", - "type": "bytes4" - } - ], - "payable": false, - "stateMutability": "pure", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "ercToErcBridgesAllowed", - "outputs": [ - { - "name": "", - "type": "address[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "ercToNativeBridgesAllowed", - "outputs": [ - { - "name": "", - "type": "address[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "extraReceiversQueueSize", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "isInitialized", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "nativeToErcBridgesAllowed", - "outputs": [ - { - "name": "", - "type": "address[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_poolId", - "type": "uint256" - } - ], - "name": "validatorRewardPercent", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_stakingEpoch", - "type": "uint256" - }, - { - "name": "_validatorStaked", - "type": "uint256" - }, - { - "name": "_totalStaked", - "type": "uint256" - }, - { - "name": "_poolReward", - "type": "uint256" - } - ], - "name": "validatorShare", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_stakingEpoch", - "type": "uint256" - }, - { - "name": "_delegatorStaked", - "type": "uint256" - }, - { - "name": "_validatorStaked", - "type": "uint256" - }, - { - "name": "_totalStaked", - "type": "uint256" - }, - { - "name": "_poolReward", - "type": "uint256" - } - ], - "name": "delegatorShare", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - } -] diff --git a/apps/explorer/priv/contracts_abi/posdao/README.md b/apps/explorer/priv/contracts_abi/posdao/README.md deleted file mode 100644 index 98a1dd21090c..000000000000 --- a/apps/explorer/priv/contracts_abi/posdao/README.md +++ /dev/null @@ -1 +0,0 @@ -ABIs are taken from compiled contract JSONs in the `build/` directory of https://github.com/poanetwork/posdao-contracts. diff --git a/apps/explorer/priv/contracts_abi/posdao/StakingAuRa.json b/apps/explorer/priv/contracts_abi/posdao/StakingAuRa.json deleted file mode 100644 index aebd900c42e9..000000000000 --- a/apps/explorer/priv/contracts_abi/posdao/StakingAuRa.json +++ /dev/null @@ -1,1127 +0,0 @@ -[ - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - }, - { - "name": "", - "type": "address" - } - ], - "name": "poolDelegatorIndex", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "candidateMinStake", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_delegator", - "type": "address" - }, - { - "name": "_offset", - "type": "uint256" - }, - { - "name": "_length", - "type": "uint256" - } - ], - "name": "getDelegatorPools", - "outputs": [ - { - "name": "result", - "type": "uint256[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_delegator", - "type": "address" - } - ], - "name": "getDelegatorPoolsLength", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "poolInactiveIndex", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "stakingEpochStartBlock", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "stakingEpoch", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "erc677TokenContract", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - }, - { - "name": "", - "type": "address" - } - ], - "name": "poolDelegatorInactiveIndex", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "stakeWithdrawDisallowPeriod", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - }, - { - "name": "", - "type": "address" - } - ], - "name": "orderWithdrawEpoch", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "poolIndex", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "stakingEpochDuration", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "delegatorMinStake", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "orderedWithdrawAmountTotal", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "validatorSetContract", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - }, - { - "name": "", - "type": "address" - } - ], - "name": "orderedWithdrawAmount", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "poolToBeRemovedIndex", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "MAX_CANDIDATES", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "poolToBeElectedIndex", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "fromPoolStakingAddress", - "type": "address" - }, - { - "indexed": true, - "name": "staker", - "type": "address" - }, - { - "indexed": true, - "name": "stakingEpoch", - "type": "uint256" - }, - { - "indexed": false, - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "name": "fromPoolId", - "type": "uint256" - } - ], - "name": "ClaimedOrderedWithdrawal", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "toPoolStakingAddress", - "type": "address" - }, - { - "indexed": true, - "name": "staker", - "type": "address" - }, - { - "indexed": true, - "name": "stakingEpoch", - "type": "uint256" - }, - { - "indexed": false, - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "name": "toPoolId", - "type": "uint256" - } - ], - "name": "PlacedStake", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "fromPoolStakingAddress", - "type": "address" - }, - { - "indexed": true, - "name": "toPoolStakingAddress", - "type": "address" - }, - { - "indexed": true, - "name": "staker", - "type": "address" - }, - { - "indexed": true, - "name": "stakingEpoch", - "type": "uint256" - }, - { - "indexed": false, - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "name": "fromPoolId", - "type": "uint256" - }, - { - "indexed": false, - "name": "toPoolId", - "type": "uint256" - } - ], - "name": "MovedStake", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "fromPoolStakingAddress", - "type": "address" - }, - { - "indexed": true, - "name": "staker", - "type": "address" - }, - { - "indexed": true, - "name": "stakingEpoch", - "type": "uint256" - }, - { - "indexed": false, - "name": "amount", - "type": "int256" - }, - { - "indexed": false, - "name": "fromPoolId", - "type": "uint256" - } - ], - "name": "OrderedWithdrawal", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "fromPoolStakingAddress", - "type": "address" - }, - { - "indexed": true, - "name": "staker", - "type": "address" - }, - { - "indexed": true, - "name": "stakingEpoch", - "type": "uint256" - }, - { - "indexed": false, - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "name": "fromPoolId", - "type": "uint256" - } - ], - "name": "WithdrewStake", - "type": "event" - }, - { - "constant": false, - "inputs": [ - { - "name": "_amount", - "type": "uint256" - }, - { - "name": "_miningAddress", - "type": "address" - } - ], - "name": "addPool", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_stakingEpochs", - "type": "uint256[]" - }, - { - "name": "_poolStakingAddress", - "type": "address" - } - ], - "name": "claimReward", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_unremovablePoolId", - "type": "uint256" - } - ], - "name": "clearUnremovableValidator", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "incrementStakingEpoch", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_validatorSetContract", - "type": "address" - }, - { - "name": "_initialIds", - "type": "uint256[]" - }, - { - "name": "_delegatorMinStake", - "type": "uint256" - }, - { - "name": "_candidateMinStake", - "type": "uint256" - }, - { - "name": "_stakingEpochDuration", - "type": "uint256" - }, - { - "name": "_stakingEpochStartBlock", - "type": "uint256" - }, - { - "name": "_stakeWithdrawDisallowPeriod", - "type": "uint256" - } - ], - "name": "initialize", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_poolId", - "type": "uint256" - } - ], - "name": "removePool", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "removeMyPool", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_blockNumber", - "type": "uint256" - } - ], - "name": "setStakingEpochStartBlock", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_fromPoolStakingAddress", - "type": "address" - }, - { - "name": "_toPoolStakingAddress", - "type": "address" - }, - { - "name": "_amount", - "type": "uint256" - } - ], - "name": "moveStake", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_toPoolStakingAddress", - "type": "address" - }, - { - "name": "_amount", - "type": "uint256" - } - ], - "name": "stake", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_fromPoolStakingAddress", - "type": "address" - }, - { - "name": "_amount", - "type": "uint256" - } - ], - "name": "withdraw", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_poolStakingAddress", - "type": "address" - }, - { - "name": "_amount", - "type": "int256" - } - ], - "name": "orderWithdraw", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_poolStakingAddress", - "type": "address" - } - ], - "name": "claimOrderedWithdraw", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_erc677TokenContract", - "type": "address" - } - ], - "name": "setErc677TokenContract", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_minStake", - "type": "uint256" - } - ], - "name": "setCandidateMinStake", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_minStake", - "type": "uint256" - } - ], - "name": "setDelegatorMinStake", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getPools", - "outputs": [ - { - "name": "", - "type": "uint256[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getPoolsInactive", - "outputs": [ - { - "name": "", - "type": "uint256[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getPoolsLikelihood", - "outputs": [ - { - "name": "likelihoods", - "type": "uint256[]" - }, - { - "name": "sum", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getPoolsToBeElected", - "outputs": [ - { - "name": "", - "type": "uint256[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getPoolsToBeRemoved", - "outputs": [ - { - "name": "", - "type": "uint256[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "areStakeAndWithdrawAllowed", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "isInitialized", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_poolId", - "type": "uint256" - } - ], - "name": "isPoolActive", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_poolStakingAddress", - "type": "address" - }, - { - "name": "_staker", - "type": "address" - } - ], - "name": "maxWithdrawAllowed", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_poolStakingAddress", - "type": "address" - }, - { - "name": "_staker", - "type": "address" - } - ], - "name": "maxWithdrawOrderAllowed", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "", - "type": "address" - }, - { - "name": "", - "type": "uint256" - }, - { - "name": "", - "type": "bytes" - } - ], - "name": "onTokenTransfer", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_poolId", - "type": "uint256" - } - ], - "name": "poolDelegators", - "outputs": [ - { - "name": "", - "type": "address[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_poolId", - "type": "uint256" - } - ], - "name": "poolDelegatorsInactive", - "outputs": [ - { - "name": "", - "type": "address[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_poolId", - "type": "uint256" - }, - { - "name": "_delegatorOrZero", - "type": "address" - } - ], - "name": "stakeAmountByCurrentEpoch", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_poolId", - "type": "uint256" - }, - { - "name": "_delegatorOrZero", - "type": "address" - } - ], - "name": "stakeAmount", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_poolId", - "type": "uint256" - } - ], - "name": "stakeAmountTotal", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "stakingEpochEndBlock", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "lastChangeBlock", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - } -] diff --git a/apps/explorer/priv/contracts_abi/posdao/Token.json b/apps/explorer/priv/contracts_abi/posdao/Token.json deleted file mode 100644 index a10faa963858..000000000000 --- a/apps/explorer/priv/contracts_abi/posdao/Token.json +++ /dev/null @@ -1,968 +0,0 @@ -[ - { - "constant": false, - "inputs": [ - { - "name": "_bridge", - "type": "address" - } - ], - "name": "removeBridge", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "PERMIT_TYPEHASH", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [ - { - "name": "", - "type": "uint8" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "DOMAIN_SEPARATOR", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "spender", - "type": "address" - }, - { - "name": "addedValue", - "type": "uint256" - } - ], - "name": "increaseAllowance", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - }, - { - "name": "_data", - "type": "bytes" - } - ], - "name": "transferAndCall", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_amount", - "type": "uint256" - } - ], - "name": "mint", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_value", - "type": "uint256" - } - ], - "name": "burn", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "bridgePointers", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "version", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "blockRewardContract", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_subtractedValue", - "type": "uint256" - } - ], - "name": "decreaseApproval", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_to", - "type": "address" - } - ], - "name": "claimTokens", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_address", - "type": "address" - } - ], - "name": "isBridge", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "nonces", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getTokenInterfacesVersion", - "outputs": [ - { - "name": "major", - "type": "uint64" - }, - { - "name": "minor", - "type": "uint64" - }, - { - "name": "patch", - "type": "uint64" - } - ], - "payable": false, - "stateMutability": "pure", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "owner", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_holder", - "type": "address" - }, - { - "name": "_spender", - "type": "address" - }, - { - "name": "_nonce", - "type": "uint256" - }, - { - "name": "_expiry", - "type": "uint256" - }, - { - "name": "_allowed", - "type": "bool" - }, - { - "name": "_v", - "type": "uint8" - }, - { - "name": "_r", - "type": "bytes32" - }, - { - "name": "_s", - "type": "bytes32" - } - ], - "name": "permit", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_bridge", - "type": "address" - } - ], - "name": "addBridge", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "bridgeList", - "outputs": [ - { - "name": "", - "type": "address[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "spender", - "type": "address" - }, - { - "name": "subtractedValue", - "type": "uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_amount", - "type": "uint256" - } - ], - "name": "push", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_from", - "type": "address" - }, - { - "name": "_to", - "type": "address" - }, - { - "name": "_amount", - "type": "uint256" - } - ], - "name": "move", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "F_ADDR", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_addedValue", - "type": "uint256" - } - ], - "name": "increaseApproval", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - }, - { - "name": "_spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "stakingContract", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_from", - "type": "address" - }, - { - "name": "_amount", - "type": "uint256" - } - ], - "name": "pull", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "bridgeCount", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - }, - { - "name": "", - "type": "address" - } - ], - "name": "expirations", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "name": "_name", - "type": "string" - }, - { - "name": "_symbol", - "type": "string" - }, - { - "name": "_decimals", - "type": "uint8" - }, - { - "name": "_chainId", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "bridge", - "type": "address" - } - ], - "name": "BridgeAdded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "bridge", - "type": "address" - } - ], - "name": "BridgeRemoved", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "from", - "type": "address" - }, - { - "indexed": false, - "name": "to", - "type": "address" - }, - { - "indexed": false, - "name": "value", - "type": "uint256" - } - ], - "name": "ContractFallbackCallFailed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "to", - "type": "address" - }, - { - "indexed": false, - "name": "amount", - "type": "uint256" - } - ], - "name": "Mint", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "burner", - "type": "address" - }, - { - "indexed": false, - "name": "value", - "type": "uint256" - } - ], - "name": "Burn", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "from", - "type": "address" - }, - { - "indexed": true, - "name": "to", - "type": "address" - }, - { - "indexed": false, - "name": "value", - "type": "uint256" - }, - { - "indexed": false, - "name": "data", - "type": "bytes" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "from", - "type": "address" - }, - { - "indexed": true, - "name": "to", - "type": "address" - }, - { - "indexed": false, - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "constant": false, - "inputs": [ - { - "name": "_blockRewardContract", - "type": "address" - } - ], - "name": "setBlockRewardContract", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_stakingContract", - "type": "address" - } - ], - "name": "setStakingContract", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_amount", - "type": "uint256" - } - ], - "name": "mintReward", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_staker", - "type": "address" - }, - { - "name": "_amount", - "type": "uint256" - } - ], - "name": "stake", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_from", - "type": "address" - }, - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/apps/explorer/priv/contracts_abi/posdao/ValidatorSetAuRa.json b/apps/explorer/priv/contracts_abi/posdao/ValidatorSetAuRa.json deleted file mode 100644 index ca4c9e787f32..000000000000 --- a/apps/explorer/priv/contracts_abi/posdao/ValidatorSetAuRa.json +++ /dev/null @@ -1,734 +0,0 @@ -[ - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "miningByStakingAddress", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "initiateChangeAllowed", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "banCounter", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "stakingByMiningAddress", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "blockRewardContract", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "banReason", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "bannedUntil", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "bannedDelegatorsUntil", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "unremovableValidator", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "MAX_VALIDATORS", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "validatorCounter", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "validatorSetApplyBlock", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "randomContract", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "changeRequestCount", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "stakingContract", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "isValidator", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "parentHash", - "type": "bytes32" - }, - { - "indexed": false, - "name": "newSet", - "type": "address[]" - } - ], - "name": "InitiateChange", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "reportingValidator", - "type": "address" - }, - { - "indexed": false, - "name": "maliciousValidator", - "type": "address" - }, - { - "indexed": false, - "name": "blockNumber", - "type": "uint256" - } - ], - "name": "ReportedMalicious", - "type": "event" - }, - { - "constant": false, - "inputs": [], - "name": "clearUnremovableValidator", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "emitInitiateChange", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "finalizeChange", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_blockRewardContract", - "type": "address" - }, - { - "name": "_randomContract", - "type": "address" - }, - { - "name": "_stakingContract", - "type": "address" - }, - { - "name": "_initialMiningAddresses", - "type": "address[]" - }, - { - "name": "_initialStakingAddresses", - "type": "address[]" - }, - { - "name": "_firstValidatorIsUnremovable", - "type": "bool" - } - ], - "name": "initialize", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "newValidatorSet", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_miningAddresses", - "type": "address[]" - } - ], - "name": "removeMaliciousValidators", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_maliciousMiningAddress", - "type": "address" - }, - { - "name": "_blockNumber", - "type": "uint256" - }, - { - "name": "", - "type": "bytes" - } - ], - "name": "reportMalicious", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "emitInitiateChangeCallable", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getPendingValidators", - "outputs": [ - { - "name": "", - "type": "address[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "validatorsToBeFinalized", - "outputs": [ - { - "name": "miningAddresses", - "type": "address[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getValidators", - "outputs": [ - { - "name": "", - "type": "address[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "isInitialized", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_miningAddress", - "type": "address" - } - ], - "name": "isReportValidatorValid", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_miningAddress", - "type": "address" - } - ], - "name": "isValidatorBanned", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_miningAddress", - "type": "address" - } - ], - "name": "areDelegatorsBanned", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_reportingMiningAddress", - "type": "address" - }, - { - "name": "_maliciousMiningAddress", - "type": "address" - }, - { - "name": "_blockNumber", - "type": "uint256" - } - ], - "name": "reportMaliciousCallable", - "outputs": [ - { - "name": "callable", - "type": "bool" - }, - { - "name": "removeReportingValidator", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "lastChangeBlock", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "hasEverBeenMiningAddress", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "idByMiningAddress", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "miningAddressById", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "stakingAddressById", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "idByStakingAddress", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "poolName", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "poolDescription", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_name", - "type": "string" - }, - { - "name": "_description", - "type": "string" - } - ], - "name": "changeMetadata", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/cspell.json b/cspell.json index d4a8e9fd81d8..a1f1f7f12751 100644 --- a/cspell.json +++ b/cspell.json @@ -54,6 +54,7 @@ "blockscout", "blockscoutuser", "bools", + "bridgedtokenlist", "browserconfig", "bsdr", "buildcache", @@ -73,6 +74,7 @@ "cellspacing", "certifi", "cfasync", + "chainid", "chainlink", "chakra", "chartjs", @@ -210,6 +212,7 @@ "histoday", "hljs", "Hodl", + "HOPR", "httpoison", "hyperledger", "ifdef", From 914c2b259fd0c1535cb41e0698686c77eeff2a30 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 13 Mar 2024 14:18:09 +0300 Subject: [PATCH 584/607] Fix typos in CHANGELOG --- CHANGELOG.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d9fc109ef07..30773a091036 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -164,6 +164,7 @@ - [#9424](https://github.com/blockscout/blockscout/pull/9424) - Bump webpack from 5.90.1 to 5.90.3 in /apps/block_scout_web/assets - [#9425](https://github.com/blockscout/blockscout/pull/9425) - Bump sass-loader from 14.1.0 to 14.1.1 in /apps/block_scout_web/assets - [#9421](https://github.com/blockscout/blockscout/pull/9421) - Bump sass from 1.70.0 to 1.71.0 in /apps/block_scout_web/assets +
## 6.1.0 @@ -857,6 +858,7 @@ - [#7958](https://github.com/blockscout/blockscout/pull/7958) - Bump ex_doc from 0.30.2 to 0.30.3 - [#7965](https://github.com/blockscout/blockscout/pull/7965) - Bump webpack from 5.88.1 to 5.88.2 in /apps/block_scout_web/assets - [#7972](https://github.com/blockscout/blockscout/pull/7972) - Bump word-wrap from 1.2.3 to 1.2.4 in /apps/block_scout_web/assets + ## 5.2.0-beta @@ -2624,8 +2626,8 @@ - [#3249](https://github.com/blockscout/blockscout/pull/3249) - Fix incorrect ABI decoding of address in tuple output - [#3237](https://github.com/blockscout/blockscout/pull/3237) - Refine contract method signature detection for read/write feature -- [#3235](https://github.com/blockscout/blockscout/pull/3235) - Fix coin supply api edpoint -- [#3233](https://github.com/blockscout/blockscout/pull/3233) - Fix for the contract verifiaction for solc 0.5 family with experimental features enabled +- [#3235](https://github.com/blockscout/blockscout/pull/3235) - Fix coin supply api endpoint +- [#3233](https://github.com/blockscout/blockscout/pull/3233) - Fix for the contract verification for solc 0.5 family with experimental features enabled - [#3231](https://github.com/blockscout/blockscout/pull/3231) - Improve search: unlimited number of searching results - [#3231](https://github.com/blockscout/blockscout/pull/3231) - Improve search: allow search with space - [#3231](https://github.com/blockscout/blockscout/pull/3231) - Improve search: order by token holders in descending order and token/contract name is ascending order @@ -2636,7 +2638,7 @@ - [#3326](https://github.com/blockscout/blockscout/pull/3326) - Chart smooth lines - [#3250](https://github.com/blockscout/blockscout/pull/3250) - Eliminate occurrences of obsolete env variable ETHEREUM_JSONRPC_JSON_RPC_TRANSPORT -- [#3240](https://github.com/blockscout/blockscout/pull/3240), [#3251](https://github.com/blockscout/blockscout/pull/3251) - various CSS imroving +- [#3240](https://github.com/blockscout/blockscout/pull/3240), [#3251](https://github.com/blockscout/blockscout/pull/3251) - various CSS improving - [f3a720](https://github.com/blockscout/blockscout/commit/2dd909c10a79b0bf4b7541a486be114152f3a720) - Make wobserver optional ## 3.3.1-beta @@ -3001,11 +3003,11 @@ fixed menu hovers in dark mode desktop view - [#2596](https://github.com/blockscout/blockscout/pull/2596) - support AuRa's empty step reward type - [#2588](https://github.com/blockscout/blockscout/pull/2588) - add verification submission comment - [#2505](https://github.com/blockscout/blockscout/pull/2505) - support POA Network emission rewards -- [#2581](https://github.com/blockscout/blockscout/pull/2581) - Add generic Map-like Cache behaviour and implementation +- [#2581](https://github.com/blockscout/blockscout/pull/2581) - Add generic Map-like Cache behavior and implementation - [#2561](https://github.com/blockscout/blockscout/pull/2561) - Add token's type to the response of tokenlist method - [#2555](https://github.com/blockscout/blockscout/pull/2555) - find and show decoding candidates for logs - [#2499](https://github.com/blockscout/blockscout/pull/2499) - import emission reward ranges -- [#2497](https://github.com/blockscout/blockscout/pull/2497) - Add generic Ordered Cache behaviour and implementation +- [#2497](https://github.com/blockscout/blockscout/pull/2497) - Add generic Ordered Cache behavior and implementation ### Fixes @@ -3061,7 +3063,7 @@ fixed menu hovers in dark mode desktop view - [#2559](https://github.com/blockscout/blockscout/pull/2559) - fix rsk total supply for empty exchange rate - [#2553](https://github.com/blockscout/blockscout/pull/2553) - Dark theme import to the end of sass - [#2550](https://github.com/blockscout/blockscout/pull/2550) - correctly encode decimal values for frontend -- [#2549](https://github.com/blockscout/blockscout/pull/2549) - Fix wrong colour of tooltip +- [#2549](https://github.com/blockscout/blockscout/pull/2549) - Fix wrong color of tooltip - [#2548](https://github.com/blockscout/blockscout/pull/2548) - CSS preload support in Firefox - [#2547](https://github.com/blockscout/blockscout/pull/2547) - do not show eth value if it's zero on the transaction overview page - [#2543](https://github.com/blockscout/blockscout/pull/2543) - do not hide search input during logs search @@ -3250,7 +3252,7 @@ fixed menu hovers in dark mode desktop view ### Chore -- [#2127](https://github.com/blockscout/blockscout/pull/2127) - use previouse chromedriver version +- [#2127](https://github.com/blockscout/blockscout/pull/2127) - use previous chromedriver version - [#2118](https://github.com/blockscout/blockscout/pull/2118) - show only the last decompiled contract - [#2255](https://github.com/blockscout/blockscout/pull/2255) - upgrade elixir version to 1.9.0 - [#2256](https://github.com/blockscout/blockscout/pull/2256) - use the latest version of chromedriver @@ -3485,7 +3487,7 @@ Reverting of synchronous block counter, implemented in #1848 ### Fixes -- [#1630](https://github.com/blockscout/blockscout/pull/1630) - (Fix) colour for release link in the footer +- [#1630](https://github.com/blockscout/blockscout/pull/1630) - (Fix) color for release link in the footer - [#1621](https://github.com/blockscout/blockscout/pull/1621) - Modify query to fetch failed contract creations - [#1614](https://github.com/blockscout/blockscout/pull/1614) - Do not fetch burn address token balance - [#1639](https://github.com/blockscout/blockscout/pull/1614) - Optimize token holder count updates when importing address current balances From 79bd263363992236a412b47ea27ea1ed48b271f1 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 13 Mar 2024 14:26:57 +0300 Subject: [PATCH 585/607] Find a single creation tx for smart-contract --- CHANGELOG.md | 2 +- apps/explorer/lib/explorer/chain.ex | 4 +++- apps/explorer/lib/explorer/chain/smart_contract.ex | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30773a091036..fe5dff669dd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ - [#9563](https://github.com/blockscout/blockscout/pull/9563) - Fix timestamp handler for unfinalized zkEVM batches - [#9560](https://github.com/blockscout/blockscout/pull/9560) - Fix fetch pending transaction for hyperledger besu client - [#9555](https://github.com/blockscout/blockscout/pull/9555) - Fix EIP-1967 beacon proxy pattern detection -- [#9518](https://github.com/blockscout/blockscout/pull/9518) - Fix MultipleResultsError in `smart_contract_creation_tx_bytecode/1` +- [#9518](https://github.com/blockscout/blockscout/pull/9518), [#9628](https://github.com/blockscout/blockscout/pull/9628) - Fix MultipleResultsError in `smart_contract_creation_tx_bytecode/1` - [#9514](https://github.com/blockscout/blockscout/pull/9514) - Fix missing `0x` prefix for `blockNumber`, `logIndex`, `transactionIndex` and remove `transactionLogIndex` in `eth_getLogs` response. - [#9512](https://github.com/blockscout/blockscout/pull/9512) - Docker-compose 2.24.6 compatibility - [#9262](https://github.com/blockscout/blockscout/pull/9262) - Fix withdrawal status diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 70633182812b..ba442277c834 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -3030,7 +3030,9 @@ defmodule Explorer.Chain do on: tx.created_contract_address_hash == a.hash, where: tx.created_contract_address_hash == ^address_hash, where: tx.status == ^1, - select: %{init: tx.input, created_contract_code: a.contract_code} + select: %{init: tx.input, created_contract_code: a.contract_code}, + order_by: [desc: tx.block_number], + limit: ^1 ) tx_input = diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index cb9ac24ea5a6..2c97ae84cb52 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -674,7 +674,9 @@ defmodule Explorer.Chain.SmartContract do from( tx in Transaction, where: tx.created_contract_address_hash == ^address_hash, - where: tx.status == ^1 + where: tx.status == ^1, + order_by: [desc: tx.block_number], + limit: ^1 ) tx = From 646c8492b8424c9c27f67ed3f4f51ccfdef01809 Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Wed, 13 Mar 2024 16:55:39 +0300 Subject: [PATCH 586/607] Convert outputs to string in smart_contract_view.ex (#9529) * Convert integers and bools to string in smart_contract_view.ex * Changelog * Fix tests * Fix test to support new numbers format --------- Co-authored-by: Fedor Ivanov --- CHANGELOG.md | 1 + .../views/api/v2/smart_contract_view.ex | 2 +- .../api/v2/smart_contract_controller_test.exs | 26 +++++++++---------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe5dff669dd7..ce4a044ba7c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - [#9563](https://github.com/blockscout/blockscout/pull/9563) - Fix timestamp handler for unfinalized zkEVM batches - [#9560](https://github.com/blockscout/blockscout/pull/9560) - Fix fetch pending transaction for hyperledger besu client - [#9555](https://github.com/blockscout/blockscout/pull/9555) - Fix EIP-1967 beacon proxy pattern detection +- [#9529](https://github.com/blockscout/blockscout/pull/9529) - Fix `MAX_SAFE_INTEGER` frontend bug - [#9518](https://github.com/blockscout/blockscout/pull/9518), [#9628](https://github.com/blockscout/blockscout/pull/9628) - Fix MultipleResultsError in `smart_contract_creation_tx_bytecode/1` - [#9514](https://github.com/blockscout/blockscout/pull/9514) - Fix missing `0x` prefix for `blockNumber`, `logIndex`, `transactionIndex` and remove `transactionLogIndex` in `eth_getLogs` response. - [#9512](https://github.com/blockscout/blockscout/pull/9512) - Docker-compose 2.24.6 compatibility diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex index a0e7196b5843..2ae2f7514f32 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex @@ -355,6 +355,6 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do end def render_json(value, _type) do - value + to_string(value) end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index eed9c4b6b742..c1ed3c3eae65 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -1126,13 +1126,13 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "tuple[bytes32,uint256,bytes32,uint256,address,address,uint256,bool,tuple[address,bytes32[],bytes][]]", "value" => [ "0xfe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab2", - 1000, + "1000", "0xfe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab2", - 10, + "10", "0xbb36c792b9b45aaf8b848a1392b0d6559202729e", "0xbb36c792b9b45aaf8b848a1392b0d6559202729e", - 123_123, - true, + "123123", + "true", [ [ "0xbb36c792b9b45aaf8b848a1392b0d6559202729e", @@ -1220,7 +1220,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "outputs" => [ %{ "type" => "uint256", - "value" => 2_900_102_562_052_921_000_000 + "value" => "2900102562052921000000" } ], "name" => "test", @@ -1535,7 +1535,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do assert %{ "is_error" => false, - "result" => %{"names" => ["bool"], "output" => [%{"type" => "bool", "value" => true}]} + "result" => %{"names" => ["bool"], "output" => [%{"type" => "bool", "value" => "true"}]} } == response end @@ -1636,13 +1636,13 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "tuple[bytes32,uint256,bytes32,uint256,address,address,uint256,bool,tuple[address,bytes32[],bytes][]]", "value" => [ "0xfe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab2", - 1000, + "1000", "0xfe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab2", - 10, + "10", "0xbb36c792b9b45aaf8b848a1392b0d6559202729e", "0xbb36c792b9b45aaf8b848a1392b0d6559202729e", - 123_123, - true, + "123123", + "true", [ [ "0xbb36c792b9b45aaf8b848a1392b0d6559202729e", @@ -2145,7 +2145,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do assert %{ "is_error" => false, - "result" => %{"names" => ["bool"], "output" => [%{"type" => "bool", "value" => true}]} + "result" => %{"names" => ["bool"], "output" => [%{"type" => "bool", "value" => "true"}]} } == response end @@ -2225,7 +2225,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "result" => %{ "names" => ["amounts"], "output" => [ - %{"type" => "uint256[]", "value" => [1_000_000_000_000_000_000_000, 15_520_773_838_563_941]} + %{"type" => "uint256[]", "value" => ["1000000000000000000000", "15520773838563941"]} ] } } == response @@ -2423,7 +2423,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do assert %{ "is_error" => false, - "result" => %{"names" => ["bool"], "output" => [%{"type" => "bool", "value" => true}]} + "result" => %{"names" => ["bool"], "output" => [%{"type" => "bool", "value" => "true"}]} } == response end From ac5625df905780a23a2ac4d01cda97cbb92dfec2 Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Wed, 13 Mar 2024 17:29:00 +0300 Subject: [PATCH 587/607] Add user_op interpretation (#9473) * Add /api/v2/proxy/account-abstraction/operations/{operation_hash_param}/summary endpoint * Add changelog, fix test * Process review comments --- CHANGELOG.md | 1 + .../lib/block_scout_web/api_router.ex | 1 + .../controllers/api/v2/fallback_controller.ex | 16 +- .../proxy/account_abstraction_controller.ex | 45 +++++- .../transaction_interpretation.ex | 143 ++++++++++++++++-- apps/explorer/lib/explorer/chain.ex | 2 +- apps/explorer/lib/explorer/chain/log.ex | 17 +++ .../lib/explorer/chain/token_transfer.ex | 53 +++++++ apps/explorer/lib/explorer/helper.ex | 8 +- .../sanitize_incorrect_nft_token_transfers.ex | 22 +-- .../migrator/token_transfer_token_type.ex | 22 +-- .../lib/explorer/utility/microservice.ex | 26 +++- apps/explorer/test/explorer/chain_test.exs | 4 +- 13 files changed, 289 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce4a044ba7c8..26547a5f5aae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - [#9490](https://github.com/blockscout/blockscout/pull/9490) - Add blob transaction counter and filter in block view +- [#9473](https://github.com/blockscout/blockscout/pull/9473) - Add user_op interpretation - [#9461](https://github.com/blockscout/blockscout/pull/9461) - Fetch blocks without internal transactions backwards - [#9460](https://github.com/blockscout/blockscout/pull/9460) - Optimism chain type - [#9409](https://github.com/blockscout/blockscout/pull/9409) - ETH JSON RPC extension diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 39a9475bba19..e3ee53cab444 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -351,6 +351,7 @@ defmodule BlockScoutWeb.ApiRouter do scope "/account-abstraction" do get("/operations/:operation_hash_param", V2.Proxy.AccountAbstractionController, :operation) + get("/operations/:operation_hash_param/summary", V2.Proxy.AccountAbstractionController, :summary) get("/bundlers/:address_hash_param", V2.Proxy.AccountAbstractionController, :bundler) get("/bundlers", V2.Proxy.AccountAbstractionController, :bundlers) get("/factories/:address_hash_param", V2.Proxy.AccountAbstractionController, :factory) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex index 4948bc20eea4..88522f872f6d 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex @@ -30,8 +30,9 @@ defmodule BlockScoutWeb.API.V2.FallbackController do @vyper_smart_contract_is_not_supported "Vyper smart-contracts are not supported by SolidityScan" @unverified_smart_contract "Smart-contract is unverified" @empty_response "Empty response" - @tx_interpreter_service_disabled "Transaction Interpretation Service is not enabled" + @tx_interpreter_service_disabled "Transaction Interpretation Service is disabled" @disabled "API endpoint is disabled" + @service_disabled "Service is disabled" def call(conn, {:format, _params}) do Logger.error(fn -> @@ -297,4 +298,17 @@ defmodule BlockScoutWeb.API.V2.FallbackController do |> put_view(ApiView) |> render(:message, %{message: @disabled}) end + + def call(conn, {:error, :disabled}) do + conn + |> put_status(501) + |> put_view(ApiView) + |> render(:message, %{message: @service_disabled}) + end + + def call(conn, {code, response}) when is_integer(code) do + conn + |> put_status(code) + |> json(response) + end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex index 69bd9b9d4420..f3e996de3d20 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex @@ -2,7 +2,7 @@ defmodule BlockScoutWeb.API.V2.Proxy.AccountAbstractionController do use BlockScoutWeb, :controller alias BlockScoutWeb.API.V2.Helper - + alias BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation, as: TransactionInterpretationService alias Explorer.Chain alias Explorer.MicroserviceInterfaces.AccountAbstraction @@ -20,6 +20,36 @@ defmodule BlockScoutWeb.API.V2.Proxy.AccountAbstractionController do |> process_response(conn) end + @doc """ + Function to handle GET requests to `/api/v2/proxy/account-abstraction/operations/:user_operation_hash_param/summary` endpoint. + """ + @spec summary(Plug.Conn.t(), map()) :: + {:error | :format | :tx_interpreter_enabled | non_neg_integer(), any()} | Plug.Conn.t() + def summary(conn, %{"operation_hash_param" => operation_hash_string, "just_request_body" => "true"}) do + with {:format, {:ok, _operation_hash}} <- {:format, Chain.string_to_transaction_hash(operation_hash_string)}, + {200, %{"hash" => _} = user_op} <- AccountAbstraction.get_user_ops_by_hash(operation_hash_string) do + conn + |> json(TransactionInterpretationService.get_user_op_request_body(user_op)) + end + end + + def summary(conn, %{"operation_hash_param" => operation_hash_string}) do + with {:format, {:ok, _operation_hash}} <- {:format, Chain.string_to_transaction_hash(operation_hash_string)}, + {:tx_interpreter_enabled, true} <- {:tx_interpreter_enabled, TransactionInterpretationService.enabled?()}, + {200, %{"hash" => _} = user_op} <- AccountAbstraction.get_user_ops_by_hash(operation_hash_string) do + {response, code} = + case TransactionInterpretationService.interpret_user_operation(user_op) do + {:ok, response} -> {response, 200} + {:error, %Jason.DecodeError{}} -> {%{error: "Error while tx interpreter response decoding"}, 500} + {{:error, error}, code} -> {%{error: error}, code} + end + + conn + |> put_status(code) + |> json(response) + end + end + @doc """ Function to handle GET requests to `/api/v2/proxy/account-abstraction/bundlers/:address_hash_param` endpoint. """ @@ -188,12 +218,21 @@ defmodule BlockScoutWeb.API.V2.Proxy.AccountAbstractionController do {:error, :disabled} -> conn |> put_status(501) - |> json(extended_info(%{message: "Service is disabled"})) + |> json(%{message: "Service is disabled"}) {status_code, response} -> + final_json = response |> extended_info() |> try_to_decode_call_data() + conn |> put_status(status_code) - |> json(extended_info(response)) + |> json(final_json) end end + + defp try_to_decode_call_data(%{"call_data" => _call_data} = user_op) do + {_mock_tx, _decoded_input, decoded_input_json} = TransactionInterpretationService.decode_user_op_calldata(user_op) + Map.put(user_op, "decoded_call_data", decoded_input_json) + end + + defp try_to_decode_call_data(response), do: response end diff --git a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex index d5e388cefa1c..5bb328ad8f6a 100644 --- a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex +++ b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex @@ -4,11 +4,12 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do """ alias BlockScoutWeb.API.V2.{Helper, TokenView, TransactionView} + alias Ecto.Association.NotLoaded alias Explorer.Chain - alias Explorer.Chain.Transaction + alias Explorer.Chain.{Data, Log, TokenTransfer, Transaction} alias HTTPoison.Response - import Explorer.Utility.Microservice, only: [base_url: 2] + import Explorer.Utility.Microservice, only: [base_url: 2, check_enabled: 2] require Logger @@ -17,15 +18,18 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do @api_true api?: true @items_limit 50 - @spec interpret(Transaction.t()) :: + @doc """ + Interpret transaction or user operation + """ + @spec interpret(Transaction.t() | map(), (Transaction.t() -> any()) | (map() -> any())) :: {{:error, :disabled | binary()}, integer()} | {:error, Jason.DecodeError.t()} - | {:ok, any} - def interpret(transaction) do + | {:ok, any()} + def interpret(transaction_or_map, request_builder \\ &prepare_request_body/1) do if enabled?() do url = interpret_url() - body = prepare_request_body(transaction) + body = request_builder.(transaction_or_map) http_post_request(url, body) else @@ -33,10 +37,33 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do end end + @doc """ + Interpret user operation + """ + @spec interpret_user_operation(map()) :: + {{:error, :disabled | binary()}, integer()} + | {:error, Jason.DecodeError.t()} + | {:ok, any()} + def interpret_user_operation(user_operation) do + interpret(user_operation, &prepare_request_body_from_user_op/1) + end + + @doc """ + Build the request body as for the tx interpreter POST request. + """ + @spec get_request_body(Transaction.t()) :: map() def get_request_body(transaction) do prepare_request_body(transaction) end + @doc """ + Build the request body as for the tx interpreter POST request. + """ + @spec get_user_op_request_body(map()) :: map() + def get_user_op_request_body(user_op) do + prepare_request_body_from_user_op(user_op) + end + defp http_post_request(url, body) do headers = [{"Content-Type", "application/json"}] @@ -63,11 +90,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do defp http_response_code({:ok, %Response{status_code: status_code}}), do: status_code defp http_response_code(_), do: 500 - defp config do - Application.get_env(:block_scout_web, __MODULE__) - end - - def enabled?, do: config()[:enabled] + def enabled?, do: check_enabled(:block_scout_web, __MODULE__) == :ok defp interpret_url do base_url(:block_scout_web, __MODULE__) <> "/transactions/summary" @@ -100,7 +123,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do hash: transaction.hash, type: transaction.type, value: transaction.value, - method: TransactionView.method_name(transaction, decoded_input), + method: TransactionView.method_name(transaction, TransactionView.format_decoded_input(decoded_input)), status: transaction.status, actions: TransactionView.transaction_actions(transaction.transaction_actions), tx_types: TransactionView.tx_types(transaction), @@ -131,6 +154,51 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do |> Enum.map(&TransactionView.prepare_token_transfer(&1, nil, decoded_input)) end + defp user_op_to_logs_and_token_transfers(user_op, decoded_input) do + log_options = + [ + necessity_by_association: %{ + [address: :names] => :optional, + [address: :smart_contract] => :optional, + address: :optional + }, + limit: @items_limit + ] + |> Keyword.merge(@api_true) + + logs = Log.user_op_to_logs(user_op, log_options) + + decoded_logs = TransactionView.decode_logs(logs, false) + + prepared_logs = + logs + |> Enum.zip(decoded_logs) + |> Enum.map(fn {log, decoded_log} -> + TransactionView.prepare_log(log, user_op["transaction_hash"], decoded_log, true) + end) + + token_transfer_options = + [ + necessity_by_association: %{ + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + :token => :optional + } + ] + |> Keyword.merge(@api_true) + + prepared_token_transfers = + logs + |> TokenTransfer.logs_to_token_transfers(token_transfer_options) + |> Chain.flat_1155_batch_token_transfers() + |> Enum.take(@items_limit) + |> Enum.map(&TransactionView.prepare_token_transfer(&1, nil, decoded_input)) + + {prepared_logs, prepared_token_transfers} + end + defp prepare_logs(transaction) do full_options = [ @@ -210,4 +278,55 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do address.hash, true ) + + defp prepare_request_body_from_user_op(user_op) do + {mock_tx, decoded_input, decoded_input_json} = decode_user_op_calldata(user_op) + + {prepared_logs, prepared_token_transfers} = user_op_to_logs_and_token_transfers(user_op, decoded_input) + + {:ok, from_address_hash} = Chain.string_to_address_hash(user_op["sender"]) + + from_address = Chain.hash_to_address(from_address_hash, []) + + %{ + data: %{ + to: nil, + from: Helper.address_with_info(nil, from_address, from_address_hash, true), + hash: user_op["hash"], + type: 0, + value: "0", + method: TransactionView.method_name(mock_tx, TransactionView.format_decoded_input(decoded_input), true), + status: user_op["status"], + actions: [], + tx_types: [], + raw_input: user_op["call_data"], + decoded_input: decoded_input_json, + token_transfers: prepared_token_transfers + }, + logs_data: %{items: prepared_logs} + } + end + + @doc """ + Decodes user_op["call_data"] and return {mock_tx, decoded_input, decoded_input_json} + """ + @spec decode_user_op_calldata(map()) :: {Transaction.t(), tuple(), map()} + def decode_user_op_calldata(user_op) do + {:ok, input} = Data.cast(user_op["call_data"]) + + {:ok, op_hash} = Chain.string_to_transaction_hash(user_op["hash"]) + + mock_tx = %Transaction{ + to_address: %NotLoaded{}, + input: input, + hash: op_hash + } + + skip_sig_provider? = false + + {decoded_input, _abi_acc, _methods_acc} = Transaction.decoded_input_data(mock_tx, skip_sig_provider?, @api_true) + + decoded_input_json = decoded_input |> TransactionView.format_decoded_input() |> TransactionView.decoded_input() + {mock_tx, decoded_input, decoded_input_json} + end end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index ba442277c834..8657c4b5f039 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -960,7 +960,7 @@ defmodule Explorer.Chain do :contracts_creation_transaction => :optional } ], - query_decompiled_code_flag \\ true + query_decompiled_code_flag \\ false ) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 2e1cde30ced6..183c93230e17 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -314,4 +314,21 @@ defmodule Explorer.Chain.Log do |> limit(1) |> Chain.select_repo(options).one() end + + @doc """ + Fetches logs by user operation. + """ + @spec user_op_to_logs(map(), Keyword.t()) :: [t()] + def user_op_to_logs(user_op, options) do + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + limit = Keyword.get(options, :limit, 50) + + __MODULE__ + |> where([log], log.block_hash == ^user_op["block_hash"] and log.transaction_hash == ^user_op["transaction_hash"]) + |> where([log], log.index >= ^user_op["user_logs_start_index"]) + |> order_by([log], asc: log.index) + |> limit(^min(user_op["user_logs_count"], limit)) + |> Chain.join_associations(necessity_by_association) + |> Chain.select_repo(options).all() + end end diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index 294b0e4b9b93..758c5707178d 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -400,4 +400,57 @@ defmodule Explorer.Chain.TokenTransfer do |> where([tt, token: token], token.type == "ERC-721") |> preload([tt, token: token], [{:token, token}]) end + + @doc """ + To be used in migrators + """ + @spec encode_token_transfer_ids([{Hash.t(), Hash.t(), non_neg_integer()}]) :: binary() + def encode_token_transfer_ids(ids) do + encoded_values = + ids + |> Enum.reduce("", fn {t_hash, b_hash, log_index}, acc -> + acc <> "('#{hash_to_query_string(t_hash)}', '#{hash_to_query_string(b_hash)}', #{log_index})," + end) + |> String.trim_trailing(",") + + "(#{encoded_values})" + end + + defp hash_to_query_string(hash) do + s_hash = + hash + |> to_string() + |> String.trim_leading("0") + + "\\#{s_hash}" + end + + @doc """ + Fetches token transfers from logs. + """ + @spec logs_to_token_transfers([Log.t()], Keyword.t()) :: [TokenTransfer.t()] + def logs_to_token_transfers(logs, options) do + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + + logs + |> logs_to_token_transfers_query() + |> limit(^Enum.count(logs)) + |> Chain.join_associations(necessity_by_association) + |> Chain.select_repo(options).all() + end + + defp logs_to_token_transfers_query(query \\ __MODULE__, logs) + + defp logs_to_token_transfers_query(query, [log | tail]) do + query + |> or_where( + [tt], + tt.transaction_hash == ^log.transaction_hash and tt.block_hash == ^log.block_hash and tt.log_index == ^log.index + ) + |> logs_to_token_transfers_query(tail) + end + + defp logs_to_token_transfers_query(query, []) do + query + end end diff --git a/apps/explorer/lib/explorer/helper.ex b/apps/explorer/lib/explorer/helper.ex index a8791eb365d5..0b913c6556a6 100644 --- a/apps/explorer/lib/explorer/helper.ex +++ b/apps/explorer/lib/explorer/helper.ex @@ -136,10 +136,12 @@ defmodule Explorer.Helper do @doc """ Validate url """ - @spec valid_url?(String.t()) :: boolean - def valid_url?(string) do + @spec valid_url?(String.t()) :: boolean() + def valid_url?(string) when is_binary(string) do uri = URI.parse(string) - uri.scheme != nil && uri.host =~ "." + !is_nil(uri.scheme) && !is_nil(uri.host) end + + def valid_url?(_), do: false end diff --git a/apps/explorer/lib/explorer/migrator/sanitize_incorrect_nft_token_transfers.ex b/apps/explorer/lib/explorer/migrator/sanitize_incorrect_nft_token_transfers.ex index 5e4fd49a2627..42f852b2e13d 100644 --- a/apps/explorer/lib/explorer/migrator/sanitize_incorrect_nft_token_transfers.ex +++ b/apps/explorer/lib/explorer/migrator/sanitize_incorrect_nft_token_transfers.ex @@ -130,27 +130,7 @@ defmodule Explorer.Migrator.SanitizeIncorrectNFTTokenTransfers do """ DELETE FROM token_transfers tt - WHERE (tt.transaction_hash, tt.block_hash, tt.log_index) IN #{encode_token_transfer_ids(token_transfer_ids)} + WHERE (tt.transaction_hash, tt.block_hash, tt.log_index) IN #{TokenTransfer.encode_token_transfer_ids(token_transfer_ids)} """ end - - defp encode_token_transfer_ids(ids) do - encoded_values = - ids - |> Enum.reduce("", fn {t_hash, b_hash, log_index}, acc -> - acc <> "('#{hash_to_query_string(t_hash)}', '#{hash_to_query_string(b_hash)}', #{log_index})," - end) - |> String.trim_trailing(",") - - "(#{encoded_values})" - end - - defp hash_to_query_string(hash) do - s_hash = - hash - |> to_string() - |> String.trim_leading("0") - - "\\#{s_hash}" - end end diff --git a/apps/explorer/lib/explorer/migrator/token_transfer_token_type.ex b/apps/explorer/lib/explorer/migrator/token_transfer_token_type.ex index 07c4edc80714..bb6ee477ab96 100644 --- a/apps/explorer/lib/explorer/migrator/token_transfer_token_type.ex +++ b/apps/explorer/lib/explorer/migrator/token_transfer_token_type.ex @@ -54,27 +54,7 @@ defmodule Explorer.Migrator.TokenTransferTokenType do FROM tokens t, blocks b WHERE tt.block_hash = b.hash AND tt.token_contract_address_hash = t.contract_address_hash - AND (tt.transaction_hash, tt.block_hash, tt.log_index) IN #{encode_token_transfer_ids(token_transfer_ids)}; + AND (tt.transaction_hash, tt.block_hash, tt.log_index) IN #{TokenTransfer.encode_token_transfer_ids(token_transfer_ids)}; """ end - - defp encode_token_transfer_ids(ids) do - encoded_values = - ids - |> Enum.reduce("", fn {t_hash, b_hash, log_index}, acc -> - acc <> "('#{hash_to_query_string(t_hash)}', '#{hash_to_query_string(b_hash)}', #{log_index})," - end) - |> String.trim_trailing(",") - - "(#{encoded_values})" - end - - defp hash_to_query_string(hash) do - s_hash = - hash - |> to_string() - |> String.trim_leading("0") - - "\\#{s_hash}" - end end diff --git a/apps/explorer/lib/explorer/utility/microservice.ex b/apps/explorer/lib/explorer/utility/microservice.ex index fe1af3df46b4..ecdec3134e4b 100644 --- a/apps/explorer/lib/explorer/utility/microservice.ex +++ b/apps/explorer/lib/explorer/utility/microservice.ex @@ -2,14 +2,26 @@ defmodule Explorer.Utility.Microservice do @moduledoc """ Module is responsible for common utils related to microservices. """ + + alias Explorer.Helper + + @doc """ + Returns base url of the microservice or nil if it is invalid or not set + """ + @spec base_url(atom(), atom()) :: false | nil | binary() def base_url(application \\ :explorer, module) do url = Application.get_env(application, module)[:service_url] - if String.ends_with?(url, "/") do - url - |> String.slice(0..(String.length(url) - 2)) - else - url + cond do + not Helper.valid_url?(url) -> + nil + + String.ends_with?(url, "/") -> + url + |> String.slice(0..(String.length(url) - 2)) + + true -> + url end end @@ -17,8 +29,8 @@ defmodule Explorer.Utility.Microservice do Returns :ok if Application.get_env(:explorer, module)[:enabled] is true (module is enabled) """ @spec check_enabled(atom) :: :ok | {:error, :disabled} - def check_enabled(module) do - if Application.get_env(:explorer, module)[:enabled] do + def check_enabled(application \\ :explorer, module) do + if Application.get_env(application, module)[:enabled] && base_url(application, module) do :ok else {:error, :disabled} diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 64010074965e..59e3adbd7e4a 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -896,7 +896,7 @@ defmodule Explorer.ChainTest do address = insert(:address) insert(:decompiled_smart_contract, address_hash: address.hash) - {:ok, found_address} = Chain.hash_to_address(address.hash) + {:ok, found_address} = Chain.hash_to_address(address.hash, [], true) assert found_address.has_decompiled_code? end @@ -1384,7 +1384,7 @@ defmodule Explorer.ChainTest do } } - test "with valid data", %{json_rpc_named_arguments: json_rpc_named_arguments} do + test "with valid data", %{json_rpc_named_arguments: _json_rpc_named_arguments} 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) From a706cbd697c86ecea74557afccdd18ca2d94010f Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 13 Mar 2024 19:16:30 +0300 Subject: [PATCH 588/607] Format .eex files --- .../lib/block_scout_web/templates/address/_link.html.eex | 2 +- .../block_scout_web/templates/address/overview.html.eex | 8 ++++---- .../templates/address_token/overview.html.eex | 8 ++++---- .../advertisement/banners_ad/_banner_728.html.eex | 2 +- .../templates/advertisement/text_ad/index.html.eex | 2 +- .../templates/block_transaction/404.html.eex | 2 +- .../templates/common_components/_btn_copy.html.eex | 2 +- .../lib/block_scout_web/templates/form/_tag.html.eex | 2 +- .../block_scout_web/templates/tokens/_token_icon.html.eex | 2 +- .../templates/tokens/overview/_details.html.eex | 8 ++++---- .../templates/transaction/_total_transfers.html.eex | 2 +- .../transaction/_total_transfers_from_to.html.eex | 2 +- 12 files changed, 21 insertions(+), 21 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_link.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_link.html.eex index 275a8f058dc1..d28be9224205 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_link.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_link.html.eex @@ -1,7 +1,7 @@ <%= if @address do %> <%= if assigns[:show_full_hash] do %> <%= if name = if assigns[:ignore_implementation_name], do: primary_name(@address), else: implementation_name(@address) || primary_name(@address) do %> - <%= name %> | + <%= name %> | <% end %> <%= link to: address_path(BlockScoutWeb.Endpoint, :show, @address), "data-test": "address_hash_link", class: assigns[:class] do %> <%= @address %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex index 2b8ba6e6aa23..005508956e65 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex @@ -62,7 +62,7 @@ @address.hash), "data-test": "token_hash_link" - ) + ) %> @@ -137,7 +137,7 @@ (if name, do: name <> " | " <> implementation_address, else: implementation_address), to: address_path(@conn, :show, implementation_address), class: "contract-address" - ) + ) %> @@ -160,7 +160,7 @@ data-placement="top" data-toggle="tooltip" data-html="true" - title='<%= "@ " <> usd_value <> "/" <> Explorer.coin_name() %>' + title='<%= "@ " <> usd_value <> "/" <> Explorer.coin_name() %>' > ) @@ -288,4 +288,4 @@ <%= render BlockScoutWeb.CommonComponentsView, "_modal_qr_code.html", qr_code: qr_code(@address), title: @address %> -<%= render BlockScoutWeb.Advertisement.BannersAdView, "_banner_728.html", conn: @conn %> +<%= render BlockScoutWeb.Advertisement.BannersAdView, "_banner_728.html", conn: @conn %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token/overview.html.eex index 6f922375b973..3afb3d97a3f4 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_token/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/overview.html.eex @@ -14,14 +14,14 @@ _ -> Decimal.new(0) end %> -<% data_usd_exchange_rate = +<% data_usd_exchange_rate = unless AddressView.empty_exchange_rate?(@exchange_rate) do "data-usd-exchange-rate='#{@exchange_rate.usd_value}' data-raw-usd-value='#{raw_usd_value}'" end %> <% native_coin_balance_token = AddressView.balance(@address) %> -<% native_coin_balance_usd = +<% native_coin_balance_usd = if AddressView.empty_exchange_rate?(@exchange_rate) do nil else @@ -32,7 +32,7 @@ " end %> -<% native_coin_balance = +<% native_coin_balance = if native_coin_balance_usd do native_coin_balance_usd <> " | " <> native_coin_balance_token else @@ -70,4 +70,4 @@ classes: ["fs-14"], container_classes: ["d-none"] %> - \ No newline at end of file + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/advertisement/banners_ad/_banner_728.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/advertisement/banners_ad/_banner_728.html.eex index 20c47d29fe4b..550a85799498 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/advertisement/banners_ad/_banner_728.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/advertisement/banners_ad/_banner_728.html.eex @@ -3,4 +3,4 @@ - \ No newline at end of file + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/advertisement/text_ad/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/advertisement/text_ad/index.html.eex index c8dc85c5621d..66eedb9a02f9 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/advertisement/text_ad/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/advertisement/text_ad/index.html.eex @@ -1,4 +1,4 @@ \ No newline at end of file + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/404.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/404.html.eex index 5f87d8debbf5..84fe3df3aa39 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/404.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/404.html.eex @@ -10,4 +10,4 @@ - \ No newline at end of file + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_copy.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_copy.html.eex index d36e97feccb7..135fe488e534 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_copy.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_btn_copy.html.eex @@ -11,4 +11,4 @@ - \ No newline at end of file + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/form/_tag.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/form/_tag.html.eex index 54f032df05f8..962284ff61fe 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/form/_tag.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/form/_tag.html.eex @@ -1,3 +1,3 @@
"> <%= @text %> -
\ No newline at end of file + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/_token_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/_token_icon.html.eex index 5ccb004cd27a..9a4de756d7d3 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/_token_icon.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/_token_icon.html.eex @@ -1,2 +1,2 @@ <% token_icon_url = Explorer.Chain.get_token_icon_url_by(@chain_id, @address) %> -" alt="" onerror="if (this.src != '/images/icons/token_icon_default.svg') this.src = '/images/icons/token_icon_default.svg';"/> \ No newline at end of file +" alt="" onerror="if (this.src != '/images/icons/token_icon_default.svg') this.src = '/images/icons/token_icon_default.svg';"/> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex index e3b1d90529ca..80a6ba2077d5 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex @@ -54,10 +54,10 @@
<%= link( @token.contract_address_hash, - to: AccessHelper.get_path(@conn, :address_path, :show, + to: AccessHelper.get_path(@conn, :address_path, :show, Address.checksum(@token.contract_address_hash)), "data-test": "token_contract_address" - ) + ) %>
@@ -99,7 +99,7 @@ - <% end %> + <% end %> <% end %>
@@ -134,7 +134,7 @@ <%= @token.decimals %>
- <% end %> + <% end %>
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers.html.eex index 4896e6e85312..9f3022941f31 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers.html.eex @@ -20,4 +20,4 @@ <% {:ok, value} -> %> <%= value %> <%= " " %><%= render BlockScoutWeb.TransactionView, "_link_to_token_symbol.html", transfer: @transfer %> -<% end %> \ No newline at end of file +<% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex index d26557b9c32a..91de91125ad1 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex @@ -40,7 +40,7 @@ style: "position: relative;"%> For -<% end %> +<% end %> <%= render BlockScoutWeb.TransactionView, "_total_transfers.html", transfer: @transfer %> From a8cd7085b84078ae9eb4c16f2385747a6634aea9 Mon Sep 17 00:00:00 2001 From: Fedor Ivanov Date: Wed, 13 Mar 2024 19:59:19 +0300 Subject: [PATCH 589/607] Setup alternative hex.pm mirrors (#9622) * Setup alternative hex.pm mirrors * Update `CHANGELOG.md` --- .github/workflows/config.yml | 39 ++++++++++++++++++++++++++++++++++++ CHANGELOG.md | 1 + 2 files changed, 40 insertions(+) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 1e74caf41a56..0f2ad51ee036 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -59,6 +59,9 @@ jobs: with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex - name: "ELIXIR_VERSION.lock" run: echo "${ELIXIR_VERSION}" > ELIXIR_VERSION.lock @@ -123,6 +126,9 @@ jobs: with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex - name: Restore Mix Deps Cache uses: actions/cache/restore@v4 @@ -147,6 +153,9 @@ jobs: with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex - name: Restore Mix Deps Cache uses: actions/cache/restore@v4 @@ -176,6 +185,9 @@ jobs: with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex - name: Restore Mix Deps Cache uses: actions/cache/restore@v4 @@ -220,6 +232,9 @@ jobs: with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex - name: Restore Mix Deps Cache uses: actions/cache/restore@v4 @@ -246,6 +261,9 @@ jobs: with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex - name: Mix Deps Cache uses: actions/cache/restore@v4 @@ -275,6 +293,9 @@ jobs: with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex - name: Mix Deps Cache uses: actions/cache/restore@v4 @@ -323,6 +344,9 @@ jobs: with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex - name: Mix Deps Cache uses: actions/cache/restore@v4 @@ -369,6 +393,9 @@ jobs: with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex - name: Mix Deps Cache uses: actions/cache/restore@v4 @@ -431,6 +458,9 @@ jobs: with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex - name: Mix Deps Cache uses: actions/cache/restore@v4 @@ -491,6 +521,9 @@ jobs: with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex - name: Mix Deps Cache uses: actions/cache/restore@v4 @@ -562,6 +595,9 @@ jobs: with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex - name: Mix Deps Cache uses: actions/cache/restore@v4 @@ -630,6 +666,9 @@ jobs: with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex - name: Mix Deps Cache uses: actions/cache/restore@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 26547a5f5aae..aaf64e9d4412 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ ### Chore +- [#9622](https://github.com/blockscout/blockscout/pull/9622) - Add alternative `hex.pm` mirrors - [#9571](https://github.com/blockscout/blockscout/pull/9571) - Support Optimism Ecotone upgrade by Indexer.Fetcher.Optimism.TxnBatch module - [#9562](https://github.com/blockscout/blockscout/pull/9562) - Add cancun evm version - [#9506](https://github.com/blockscout/blockscout/pull/9506) - API v1 bridgedtokenlist endpoint From de905162a0a0816997db6f3824ecc0c2d96e6a0f Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 13 Mar 2024 20:05:33 +0300 Subject: [PATCH 590/607] Release workflow for Gnosis chain --- .../publish-docker-image-for-gnosis-chain.yml | 6 +-- .github/workflows/release-gnosis.yml | 46 +++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/release-gnosis.yml diff --git a/.github/workflows/publish-docker-image-for-gnosis-chain.yml b/.github/workflows/publish-docker-image-for-gnosis-chain.yml index 31aa884f38f7..86f59dfd0643 100644 --- a/.github/workflows/publish-docker-image-for-gnosis-chain.yml +++ b/.github/workflows/publish-docker-image-for-gnosis-chain.yml @@ -36,8 +36,6 @@ jobs: CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= ADMIN_PANEL_ENABLED=false CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= - DISABLE_BRIDGE_MARKET_CAP_UPDATER=false - CACHE_BRIDGE_MARKET_CAP_UPDATE_INTERVAL= - SENTRY_DSN_CLIENT_GNOSIS=${{ secrets.SENTRY_DSN_CLIENT_GNOSIS }} BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} - RELEASE_VERSION=${{ env.RELEASE_VERSION }} \ No newline at end of file + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=ethereum \ No newline at end of file diff --git a/.github/workflows/release-gnosis.yml b/.github/workflows/release-gnosis.yml new file mode 100644 index 000000000000..bdabb752b213 --- /dev/null +++ b/.github/workflows/release-gnosis.yml @@ -0,0 +1,46 @@ +name: Release for Gnosis Chain + +on: + release: + types: [published] + +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Docker image for Gnosis chain + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-xdai:latest, blockscout/blockscout-xdai:${{ env.RELEASE_VERSION }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BRIDGED_TOKENS_ENABLED=true + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=ethereum \ No newline at end of file From 400b45b14528d23dbc617ace4197b55a4d1acbf3 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop <105209995+Qwerty5Uiop@users.noreply.github.com> Date: Wed, 13 Mar 2024 23:59:19 +0400 Subject: [PATCH 591/607] Massive blocks fetcher (#9486) * Massive blocks fetcher * Improve massive blocks fetcher log * Update apps/explorer/lib/explorer/utility/massive_block.ex Co-authored-by: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> * Add low priority queue for MassiveBlocksFetcher --------- Co-authored-by: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> --- CHANGELOG.md | 1 + .../lib/explorer/utility/massive_block.ex | 42 +++++++++ .../20240226074456_create_massive_blocks.exs | 11 +++ .../lib/indexer/block/catchup/fetcher.ex | 20 ++++- .../block/catchup/massive_blocks_fetcher.ex | 88 +++++++++++++++++++ .../lib/indexer/block/catchup/supervisor.ex | 3 +- apps/indexer/lib/indexer/block/fetcher.ex | 7 +- 7 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 apps/explorer/lib/explorer/utility/massive_block.ex create mode 100644 apps/explorer/priv/repo/migrations/20240226074456_create_massive_blocks.exs create mode 100644 apps/indexer/lib/indexer/block/catchup/massive_blocks_fetcher.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index aaf64e9d4412..c6aaf217923d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - [#9490](https://github.com/blockscout/blockscout/pull/9490) - Add blob transaction counter and filter in block view +- [#9486](https://github.com/blockscout/blockscout/pull/9486) - Massive blocks fetcher - [#9473](https://github.com/blockscout/blockscout/pull/9473) - Add user_op interpretation - [#9461](https://github.com/blockscout/blockscout/pull/9461) - Fetch blocks without internal transactions backwards - [#9460](https://github.com/blockscout/blockscout/pull/9460) - Optimism chain type diff --git a/apps/explorer/lib/explorer/utility/massive_block.ex b/apps/explorer/lib/explorer/utility/massive_block.ex new file mode 100644 index 000000000000..9aa710480396 --- /dev/null +++ b/apps/explorer/lib/explorer/utility/massive_block.ex @@ -0,0 +1,42 @@ +defmodule Explorer.Utility.MassiveBlock do + @moduledoc """ + Module is responsible for keeping the block numbers that are too large for regular import + and need more time to complete. + """ + + use Explorer.Schema + + alias Explorer.Repo + + @primary_key false + typed_schema "massive_blocks" do + field(:number, :integer, primary_key: true) + + timestamps() + end + + @doc false + def changeset(massive_block \\ %__MODULE__{}, params) do + cast(massive_block, params, [:number]) + end + + def get_last_block_number(except_numbers) do + __MODULE__ + |> where([mb], mb.number not in ^except_numbers) + |> select([mb], max(mb.number)) + |> Repo.one() + end + + def insert_block_numbers(numbers) do + now = DateTime.utc_now() + params = Enum.map(numbers, &%{number: &1, inserted_at: now, updated_at: now}) + + Repo.insert_all(__MODULE__, params, on_conflict: {:replace, [:updated_at]}, conflict_target: :number) + end + + def delete_block_number(number) do + __MODULE__ + |> where([mb], mb.number == ^number) + |> Repo.delete_all() + end +end diff --git a/apps/explorer/priv/repo/migrations/20240226074456_create_massive_blocks.exs b/apps/explorer/priv/repo/migrations/20240226074456_create_massive_blocks.exs new file mode 100644 index 000000000000..824a4f8f271c --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240226074456_create_massive_blocks.exs @@ -0,0 +1,11 @@ +defmodule Explorer.Repo.Migrations.CreateMassiveBlocks do + use Ecto.Migration + + def change do + create table(:massive_blocks, primary_key: false) do + add(:number, :bigint, primary_key: true) + + timestamps() + end + end +end diff --git a/apps/indexer/lib/indexer/block/catchup/fetcher.ex b/apps/indexer/lib/indexer/block/catchup/fetcher.ex index 8326e2945127..de26da59c552 100644 --- a/apps/indexer/lib/indexer/block/catchup/fetcher.ex +++ b/apps/indexer/lib/indexer/block/catchup/fetcher.ex @@ -25,7 +25,7 @@ defmodule Indexer.Block.Catchup.Fetcher do alias Ecto.Changeset alias Explorer.Chain alias Explorer.Chain.NullRoundHeight - alias Explorer.Utility.MissingRangesManipulator + alias Explorer.Utility.{MassiveBlock, MissingRangesManipulator} alias Indexer.{Block, Tracer} alias Indexer.Block.Catchup.{Sequence, TaskSupervisor} alias Indexer.Memory.Shrinkable @@ -219,6 +219,7 @@ defmodule Indexer.Block.Catchup.Fetcher do {:error, {:import = step, reason}} = error -> Prometheus.Instrumenter.import_errors() Logger.error(fn -> [inspect(reason), ". Retrying."] end, step: step) + if reason == :timeout, do: add_range_to_massive_blocks(range) push_back(sequence, range) @@ -250,6 +251,7 @@ defmodule Indexer.Block.Catchup.Fetcher do end rescue exception -> + if timeout_exception?(exception), do: add_range_to_massive_blocks(range) Logger.error(fn -> [Exception.format(:error, exception, __STACKTRACE__), ?\n, ?\n, "Retrying."] end) {:error, exception} end @@ -268,6 +270,20 @@ defmodule Indexer.Block.Catchup.Fetcher do other_errors end + defp timeout_exception?(%{message: message}) when is_binary(message) do + String.match?(message, ~r/due to a timeout/) + end + + defp timeout_exception?(_exception), do: false + + defp add_range_to_massive_blocks(range) do + clear_missing_ranges(range) + + range + |> Enum.to_list() + |> MassiveBlock.insert_block_numbers() + end + defp cap_seq(seq, errors) do {not_founds, other_errors} = Enum.split_with(errors, fn @@ -301,7 +317,7 @@ defmodule Indexer.Block.Catchup.Fetcher do |> Enum.map(&push_back(sequence, &1)) end - defp clear_missing_ranges(initial_range, errors) do + defp clear_missing_ranges(initial_range, errors \\ []) do success_numbers = Enum.to_list(initial_range) -- Enum.map(errors, &block_error_to_number/1) success_numbers diff --git a/apps/indexer/lib/indexer/block/catchup/massive_blocks_fetcher.ex b/apps/indexer/lib/indexer/block/catchup/massive_blocks_fetcher.ex new file mode 100644 index 000000000000..fa1c91ed5ad7 --- /dev/null +++ b/apps/indexer/lib/indexer/block/catchup/massive_blocks_fetcher.ex @@ -0,0 +1,88 @@ +defmodule Indexer.Block.Catchup.MassiveBlocksFetcher do + @moduledoc """ + Fetches and indexes blocks by numbers from massive_blocks table. + """ + + use GenServer + + require Logger + + alias Explorer.Utility.MassiveBlock + alias Indexer.Block.Fetcher + + @increased_interval 10000 + + @spec start_link(term()) :: GenServer.on_start() + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + @impl true + def init(_) do + send_new_task() + + {:ok, %{block_fetcher: generate_block_fetcher(), low_priority_blocks: []}} + end + + @impl true + def handle_info(:task, %{low_priority_blocks: low_priority_blocks} = state) do + {result, new_low_priority_blocks} = + case MassiveBlock.get_last_block_number(low_priority_blocks) do + nil -> + case low_priority_blocks do + [number | rest] -> + failed_blocks = process_block(state.block_fetcher, number) + {:processed, rest ++ failed_blocks} + + [] -> + {:empty, []} + end + + number -> + failed_blocks = process_block(state.block_fetcher, number) + {:processed, low_priority_blocks ++ failed_blocks} + end + + case result do + :processed -> send_new_task() + :empty -> send_new_task(@increased_interval) + end + + {:noreply, %{state | low_priority_blocks: new_low_priority_blocks}} + end + + def handle_info(_, state) do + {:noreply, state} + end + + defp process_block(block_fetcher, number) do + case Fetcher.fetch_and_import_range(block_fetcher, number..number, %{timeout: :infinity}) do + {:ok, _result} -> + Logger.info("MassiveBlockFetcher successfully proceed block #{inspect(number)}") + MassiveBlock.delete_block_number(number) + [] + + {:error, error} -> + Logger.error("MassiveBlockFetcher failed: #{inspect(error)}") + [number] + end + end + + defp generate_block_fetcher do + receipts_batch_size = Application.get_env(:indexer, :receipts_batch_size) + receipts_concurrency = Application.get_env(:indexer, :receipts_concurrency) + json_rpc_named_arguments = Application.get_env(:indexer, :json_rpc_named_arguments) + + %Fetcher{ + broadcast: :catchup, + callback_module: Indexer.Block.Catchup.Fetcher, + json_rpc_named_arguments: json_rpc_named_arguments, + receipts_batch_size: receipts_batch_size, + receipts_concurrency: receipts_concurrency + } + end + + defp send_new_task(interval \\ 0) do + Process.send_after(self(), :task, interval) + end +end diff --git a/apps/indexer/lib/indexer/block/catchup/supervisor.ex b/apps/indexer/lib/indexer/block/catchup/supervisor.ex index c3388d103751..024c93c91a91 100644 --- a/apps/indexer/lib/indexer/block/catchup/supervisor.ex +++ b/apps/indexer/lib/indexer/block/catchup/supervisor.ex @@ -5,7 +5,7 @@ defmodule Indexer.Block.Catchup.Supervisor do use Supervisor - alias Indexer.Block.Catchup.{BoundIntervalSupervisor, MissingRangesCollector} + alias Indexer.Block.Catchup.{BoundIntervalSupervisor, MassiveBlocksFetcher, MissingRangesCollector} def child_spec([init_arguments]) do child_spec([init_arguments, []]) @@ -31,6 +31,7 @@ defmodule Indexer.Block.Catchup.Supervisor do [ {MissingRangesCollector, []}, {Task.Supervisor, name: Indexer.Block.Catchup.TaskSupervisor}, + {MassiveBlocksFetcher, []}, {BoundIntervalSupervisor, [bound_interval_supervisor_arguments, [name: BoundIntervalSupervisor]]} ], strategy: :one_for_one diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index e17c48c3e597..2676ab02cf78 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -118,7 +118,7 @@ defmodule Indexer.Block.Fetcher do end @decorate span(tracer: Tracer) - @spec fetch_and_import_range(t, Range.t()) :: + @spec fetch_and_import_range(t, Range.t(), map) :: {:ok, %{inserted: %{}, errors: [EthereumJSONRPC.Transport.error()]}} | {:error, {step :: atom(), reason :: [Ecto.Changeset.t()] | term()} @@ -129,7 +129,8 @@ defmodule Indexer.Block.Fetcher do callback_module: callback_module, json_rpc_named_arguments: json_rpc_named_arguments } = state, - _.._ = range + _.._ = range, + additional_options \\ %{} ) when callback_module != nil do {fetch_time, fetched_blocks} = @@ -228,7 +229,7 @@ defmodule Indexer.Block.Fetcher do {:ok, inserted} <- __MODULE__.import( state, - import_options(basic_import_options, chain_type_import_options) + basic_import_options |> Map.merge(additional_options) |> import_options(chain_type_import_options) ), {:tx_actions, {:ok, inserted_tx_actions}} <- {:tx_actions, From 51d82f1dbf3b12b6c1ca2751862b8c125aaa816c Mon Sep 17 00:00:00 2001 From: Alexander Kolotov Date: Thu, 14 Mar 2024 10:36:30 +0300 Subject: [PATCH 592/607] zksync chain type support (#9631) * zkSync customizations * Insert placeholders instead of deriving current token balances * ZkSync Batches status tracking (#9080) * initial version of batch tracking * missed file added * attempt to add DB migration * Finalized L1 txs tracking * keep batches in DB * Batches statuses tracker introduction * rpc endponts to get batches data * extended views for blocks and transactions * Refactoring of fetchers * Fetch historical blocks * handle_info calls simplified * Ability to recover missed blocks * zksync info in a separate sub-map * added doc comments, part 1 * finalized doc comments * actual bathes count instead of the last imported batch * fix formatting * credo fixes * Address dialyzer warnings * Fix spelling * remaining issues with spelling and dialyzer * Attempt to address BlockScout Web Tests issue * review comments addressed, part 1 * review comments addressed, part 2 * collection all_options for import module reworked to get rid of dialyzer findings * removed unnecessary functionality * proper import * Credo fixes * Add CHAIN_TYPE=zksync to image generation workflow * Proper handling of empty transactions list in etc_getBlockByNumber * Merge master * Address merge issues * Fix format * Refactoring of chain type specific code for block and transaction views * Consistent name for functions * add exceptions for Credo.Check.Design.AliasUsage * Fix rebasing conflicts * Fix rebase conflicts * fix issue with stability fees in tx view * make Stability related tests dependent on chain type in compile time * move zksync related migration * Changelog updated * removal of duplicated migration * List r,s,v as optional attributes for transaction --------- Co-authored-by: Viktor Baranov Co-authored-by: Qwerty5Uiop --- .../publish-docker-image-for-zksync.yml | 3 +- CHANGELOG.md | 1 + .../lib/block_scout_web/api_router.ex | 17 + .../controllers/api/v2/block_controller.ex | 23 + .../api/v2/transaction_controller.ex | 25 ++ .../controllers/api/v2/zksync_controller.ex | 120 +++++ .../views/api/v2/block_view.ex | 30 +- .../views/api/v2/ethereum_view.ex | 41 ++ .../views/api/v2/optimism_view.ex | 31 ++ .../views/api/v2/polygon_edge_view.ex | 47 ++ .../views/api/v2/polygon_zkevm_view.ex | 28 ++ .../views/api/v2/rootstock_view.ex | 19 + .../views/api/v2/stability_view.ex | 126 ++++++ .../views/api/v2/suave_view.ex | 130 ++++++ .../views/api/v2/transaction_view.ex | 339 +++++--------- .../views/api/v2/zksync_view.ex | 235 ++++++++++ apps/block_scout_web/mix.exs | 3 +- .../api/v2/transaction_controller_test.exs | 240 +++++----- .../lib/ethereum_jsonrpc/log.ex | 5 + .../lib/ethereum_jsonrpc/receipt.ex | 6 + .../lib/ethereum_jsonrpc/transaction.ex | 63 ++- apps/explorer/config/dev.exs | 3 + apps/explorer/config/prod.exs | 4 + apps/explorer/config/test.exs | 1 + apps/explorer/lib/explorer/application.ex | 1 + apps/explorer/lib/explorer/chain/block.ex | 13 + apps/explorer/lib/explorer/chain/import.ex | 2 +- .../explorer/chain/import/runner/blocks.ex | 86 +--- .../import/runner/zksync/batch_blocks.ex | 79 ++++ .../runner/zksync/batch_transactions.ex | 79 ++++ .../runner/zksync/lifecycle_transactions.ex | 103 +++++ .../runner/zksync/transaction_batches.ex | 122 ++++++ .../chain/import/stage/address_referencing.ex | 30 ++ .../explorer/chain/import/stage/addresses.ex | 26 ++ .../chain/import/stage/block_referencing.ex | 13 +- .../lib/explorer/chain/transaction.ex | 169 ++----- .../lib/explorer/chain/zksync/batch_block.ex | 37 ++ .../chain/zksync/batch_transaction.ex | 37 ++ .../chain/zksync/lifecycle_transaction.ex | 38 ++ .../lib/explorer/chain/zksync/reader.ex | 339 ++++++++++++++ .../chain/zksync/transaction_batch.ex | 83 ++++ apps/explorer/lib/explorer/repo.ex | 24 + ...2082101_make_tranaction_r_s_v_optional.exs | 17 + .../20231213171043_create_zksync_tables.exs | 82 ++++ .../chain/import/runner/blocks_test.exs | 96 +--- .../test/explorer/chain/import_test.exs | 184 +------- .../polygon_zkevm/transaction_batch.ex | 10 + .../fetcher/zksync/batches_status_tracker.ex | 242 ++++++++++ .../fetcher/zksync/discovery/batches_data.ex | 413 ++++++++++++++++++ .../fetcher/zksync/discovery/workers.ex | 163 +++++++ .../zksync/status_tracking/committed.ex | 78 ++++ .../fetcher/zksync/status_tracking/common.ex | 173 ++++++++ .../zksync/status_tracking/executed.ex | 78 ++++ .../fetcher/zksync/status_tracking/proven.ex | 137 ++++++ .../fetcher/zksync/transaction_batch.ex | 149 +++++++ .../lib/indexer/fetcher/zksync/utils/db.ex | 204 +++++++++ .../indexer/fetcher/zksync/utils/logging.ex | 143 ++++++ .../lib/indexer/fetcher/zksync/utils/rpc.ex | 403 +++++++++++++++++ apps/indexer/lib/indexer/supervisor.ex | 9 + config/config_helper.exs | 1 + config/runtime.exs | 15 + config/runtime/dev.exs | 11 +- config/runtime/prod.exs | 10 +- cspell.json | 193 ++++---- docker-compose/envs/common-blockscout.env | 6 + 65 files changed, 4652 insertions(+), 986 deletions(-) create mode 100644 apps/block_scout_web/lib/block_scout_web/controllers/api/v2/zksync_controller.ex create mode 100644 apps/block_scout_web/lib/block_scout_web/views/api/v2/ethereum_view.ex create mode 100644 apps/block_scout_web/lib/block_scout_web/views/api/v2/rootstock_view.ex create mode 100644 apps/block_scout_web/lib/block_scout_web/views/api/v2/stability_view.ex create mode 100644 apps/block_scout_web/lib/block_scout_web/views/api/v2/suave_view.ex create mode 100644 apps/block_scout_web/lib/block_scout_web/views/api/v2/zksync_view.ex create mode 100644 apps/explorer/lib/explorer/chain/import/runner/zksync/batch_blocks.ex create mode 100644 apps/explorer/lib/explorer/chain/import/runner/zksync/batch_transactions.ex create mode 100644 apps/explorer/lib/explorer/chain/import/runner/zksync/lifecycle_transactions.ex create mode 100644 apps/explorer/lib/explorer/chain/import/runner/zksync/transaction_batches.ex create mode 100644 apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex create mode 100644 apps/explorer/lib/explorer/chain/import/stage/addresses.ex create mode 100644 apps/explorer/lib/explorer/chain/zksync/batch_block.ex create mode 100644 apps/explorer/lib/explorer/chain/zksync/batch_transaction.ex create mode 100644 apps/explorer/lib/explorer/chain/zksync/lifecycle_transaction.ex create mode 100644 apps/explorer/lib/explorer/chain/zksync/reader.ex create mode 100644 apps/explorer/lib/explorer/chain/zksync/transaction_batch.ex create mode 100644 apps/explorer/priv/zk_sync/migrations/20211202082101_make_tranaction_r_s_v_optional.exs create mode 100644 apps/explorer/priv/zk_sync/migrations/20231213171043_create_zksync_tables.exs create mode 100644 apps/indexer/lib/indexer/fetcher/zksync/batches_status_tracker.ex create mode 100644 apps/indexer/lib/indexer/fetcher/zksync/discovery/batches_data.ex create mode 100644 apps/indexer/lib/indexer/fetcher/zksync/discovery/workers.ex create mode 100644 apps/indexer/lib/indexer/fetcher/zksync/status_tracking/committed.ex create mode 100644 apps/indexer/lib/indexer/fetcher/zksync/status_tracking/common.ex create mode 100644 apps/indexer/lib/indexer/fetcher/zksync/status_tracking/executed.ex create mode 100644 apps/indexer/lib/indexer/fetcher/zksync/status_tracking/proven.ex create mode 100644 apps/indexer/lib/indexer/fetcher/zksync/transaction_batch.ex create mode 100644 apps/indexer/lib/indexer/fetcher/zksync/utils/db.ex create mode 100644 apps/indexer/lib/indexer/fetcher/zksync/utils/logging.ex create mode 100644 apps/indexer/lib/indexer/fetcher/zksync/utils/rpc.ex diff --git a/.github/workflows/publish-docker-image-for-zksync.yml b/.github/workflows/publish-docker-image-for-zksync.yml index 3cd9c2ad750d..1b746bf26983 100644 --- a/.github/workflows/publish-docker-image-for-zksync.yml +++ b/.github/workflows/publish-docker-image-for-zksync.yml @@ -36,4 +36,5 @@ jobs: ADMIN_PANEL_ENABLED=false CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} - RELEASE_VERSION=${{ env.RELEASE_VERSION }} \ No newline at end of file + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zksync \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c6aaf217923d..e261aa327bf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#9631](https://github.com/blockscout/blockscout/pull/9631) - Initial support of zksync chain type - [#9490](https://github.com/blockscout/blockscout/pull/9490) - Add blob transaction counter and filter in block view - [#9486](https://github.com/blockscout/blockscout/pull/9486) - Massive blocks fetcher - [#9473](https://github.com/blockscout/blockscout/pull/9473) - Add user_op interpretation diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index e3ee53cab444..b377a60fd9a9 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -207,6 +207,10 @@ defmodule BlockScoutWeb.ApiRouter do get("/zkevm-batch/:batch_number", V2.TransactionController, :polygon_zkevm_batch) end + if Application.compile_env(:explorer, :chain_type) == "zksync" do + get("/zksync-batch/:batch_number", V2.TransactionController, :zksync_batch) + end + if Application.compile_env(:explorer, :chain_type) == "suave" do get("/execution-node/:execution_node_hash_param", V2.TransactionController, :execution_node) end @@ -281,6 +285,11 @@ defmodule BlockScoutWeb.ApiRouter do get("/zkevm/batches/confirmed", V2.PolygonZkevmController, :batches_confirmed) get("/zkevm/batches/latest-number", V2.PolygonZkevmController, :batch_latest_number) end + + if Application.compile_env(:explorer, :chain_type) == "zksync" do + get("/zksync/batches/confirmed", V2.ZkSyncController, :batches_confirmed) + get("/zksync/batches/latest-number", V2.ZkSyncController, :batch_latest_number) + end end scope "/stats" do @@ -379,6 +388,14 @@ defmodule BlockScoutWeb.ApiRouter do end end end + + scope "/zksync" do + if Application.compile_env(:explorer, :chain_type) == "zksync" do + get("/batches", V2.ZkSyncController, :batches) + get("/batches/count", V2.ZkSyncController, :batches_count) + get("/batches/:batch_number", V2.ZkSyncController, :batch) + end + end end scope "/v1", as: :api_v1 do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex index a7dbae108e5a..6f4c51082d94 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex @@ -27,6 +27,15 @@ defmodule BlockScoutWeb.API.V2.BlockController do [transactions: :beacon_blob_transaction] => :optional } + "zksync" -> + @chain_type_transaction_necessity_by_association %{} + @chain_type_block_necessity_by_association %{ + :zksync_batch => :optional, + :zksync_commit_transaction => :optional, + :zksync_prove_transaction => :optional, + :zksync_execute_transaction => :optional + } + _ -> @chain_type_transaction_necessity_by_association %{} @chain_type_block_necessity_by_association %{} @@ -62,6 +71,20 @@ defmodule BlockScoutWeb.API.V2.BlockController do api?: true ] + @block_params [ + necessity_by_association: + %{ + [miner: :names] => :optional, + :uncles => :optional, + :nephews => :optional, + :rewards => :optional, + :transactions => :optional, + :withdrawals => :optional + } + |> Map.merge(@chain_type_block_necessity_by_association), + api?: true + ] + action_fallback(BlockScoutWeb.API.V2.FallbackController) def block(conn, %{"block_hash_or_number" => block_hash_or_number}) do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex index a850ed2c421e..be5c58beddb4 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -32,6 +32,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do alias Explorer.Chain.Beacon.Reader, as: BeaconReader alias Explorer.Chain.{Hash, Transaction} alias Explorer.Chain.PolygonZkevm.Reader + alias Explorer.Chain.ZkSync.Reader alias Indexer.Fetcher.FirstTraceOnDemand action_fallback(BlockScoutWeb.API.V2.FallbackController) @@ -101,6 +102,13 @@ defmodule BlockScoutWeb.API.V2.TransactionController do |> Map.put(:zkevm_sequence_transaction, :optional) |> Map.put(:zkevm_verify_transaction, :optional) + "zksync" -> + necessity_by_association_with_actions + |> Map.put(:zksync_batch, :optional) + |> Map.put(:zksync_commit_transaction, :optional) + |> Map.put(:zksync_prove_transaction, :optional) + |> Map.put(:zksync_execute_transaction, :optional) + "suave" -> necessity_by_association_with_actions |> Map.put(:logs, :optional) @@ -168,6 +176,23 @@ defmodule BlockScoutWeb.API.V2.TransactionController do |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), items: true}) end + @doc """ + Function to handle GET requests to `/api/v2/transactions/zksync-batch/:batch_number` endpoint. + It renders the list of L2 transactions bound to the specified batch. + """ + @spec zksync_batch(Plug.Conn.t(), map()) :: Plug.Conn.t() + def zksync_batch(conn, %{"batch_number" => batch_number} = _params) do + transactions = + batch_number + |> Reader.batch_transactions(api?: true) + |> Enum.map(fn tx -> tx.hash end) + |> Chain.hashes_to_transactions(api?: true, necessity_by_association: @transaction_necessity_by_association) + + conn + |> put_status(200) + |> render(:transactions, %{transactions: transactions, items: true}) + end + def execution_node(conn, %{"execution_node_hash_param" => execution_node_hash_string} = params) do with {:format, {:ok, execution_node_hash}} <- {:format, Chain.string_to_address_hash(execution_node_hash_string)} do full_options = diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/zksync_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/zksync_controller.ex new file mode 100644 index 000000000000..c9bfa544285a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/zksync_controller.ex @@ -0,0 +1,120 @@ +defmodule BlockScoutWeb.API.V2.ZkSyncController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Chain, + only: [ + next_page_params: 4, + paging_options: 1, + split_list_by_page: 1 + ] + + alias Explorer.Chain.ZkSync.{Reader, TransactionBatch} + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + @batch_necessity_by_association %{ + :commit_transaction => :optional, + :prove_transaction => :optional, + :execute_transaction => :optional, + :l2_transactions => :optional + } + + @batches_necessity_by_association %{ + :commit_transaction => :optional, + :prove_transaction => :optional, + :execute_transaction => :optional + } + + @doc """ + Function to handle GET requests to `/api/v2/zksync/batches/:batch_number` endpoint. + """ + @spec batch(Plug.Conn.t(), map()) :: Plug.Conn.t() + def batch(conn, %{"batch_number" => batch_number} = _params) do + case Reader.batch( + batch_number, + necessity_by_association: @batch_necessity_by_association, + api?: true + ) do + {:ok, batch} -> + conn + |> put_status(200) + |> render(:zksync_batch, %{batch: batch}) + + {:error, :not_found} = res -> + res + end + end + + @doc """ + Function to handle GET requests to `/api/v2/zksync/batches` endpoint. + """ + @spec batches(Plug.Conn.t(), map()) :: Plug.Conn.t() + def batches(conn, params) do + {batches, next_page} = + params + |> paging_options() + |> Keyword.put(:necessity_by_association, @batches_necessity_by_association) + |> Keyword.put(:api?, true) + |> Reader.batches() + |> split_list_by_page() + + next_page_params = + next_page_params( + next_page, + batches, + params, + fn %TransactionBatch{number: number} -> %{"number" => number} end + ) + + conn + |> put_status(200) + |> render(:zksync_batches, %{ + batches: batches, + next_page_params: next_page_params + }) + end + + @doc """ + Function to handle GET requests to `/api/v2/zksync/batches/count` endpoint. + """ + @spec batches_count(Plug.Conn.t(), map()) :: Plug.Conn.t() + def batches_count(conn, _params) do + conn + |> put_status(200) + |> render(:zksync_batches_count, %{count: Reader.batches_count(api?: true)}) + end + + @doc """ + Function to handle GET requests to `/api/v2/main-page/zksync/batches/confirmed` endpoint. + """ + @spec batches_confirmed(Plug.Conn.t(), map()) :: Plug.Conn.t() + def batches_confirmed(conn, _params) do + batches = + [] + |> Keyword.put(:necessity_by_association, @batches_necessity_by_association) + |> Keyword.put(:api?, true) + |> Keyword.put(:confirmed?, true) + |> Reader.batches() + + conn + |> put_status(200) + |> render(:zksync_batches, %{batches: batches}) + end + + @doc """ + Function to handle GET requests to `/api/v2/main-page/zksync/batches/latest-number` endpoint. + """ + @spec batch_latest_number(Plug.Conn.t(), map()) :: Plug.Conn.t() + def batch_latest_number(conn, _params) do + conn + |> put_status(200) + |> render(:zksync_batch_latest_number, %{number: batch_latest_number()}) + end + + defp batch_latest_number do + case Reader.batch(:latest, api?: true) do + {:ok, batch} -> batch.number + {:error, :not_found} -> 0 + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex index 5a661af3f709..35081b8ab27f 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex @@ -109,37 +109,29 @@ defmodule BlockScoutWeb.API.V2.BlockView do "rsk" -> defp chain_type_fields(result, block, single_block?) do if single_block? do - result - |> Map.put("minimum_gas_price", block.minimum_gas_price) - |> Map.put("bitcoin_merged_mining_header", block.bitcoin_merged_mining_header) - |> Map.put("bitcoin_merged_mining_coinbase_transaction", block.bitcoin_merged_mining_coinbase_transaction) - |> Map.put("bitcoin_merged_mining_merkle_proof", block.bitcoin_merged_mining_merkle_proof) - |> Map.put("hash_for_merged_mining", block.hash_for_merged_mining) + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.RootstockView.extend_block_json_response(result, block) else result end end - "ethereum" -> + "zksync" -> defp chain_type_fields(result, block, single_block?) do if single_block? do - blob_gas_price = Block.transaction_blob_gas_price(block.transactions) - burnt_blob_transaction_fees = Decimal.mult(block.blob_gas_used || 0, blob_gas_price || 0) - - result - |> Map.put("blob_tx_count", count_blob_transactions(block)) - |> Map.put("blob_gas_used", block.blob_gas_used) - |> Map.put("excess_blob_gas", block.excess_blob_gas) - |> Map.put("blob_gas_price", blob_gas_price) - |> Map.put("burnt_blob_fees", burnt_blob_transaction_fees) + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.ZkSyncView.extend_block_json_response(result, block) else result - |> Map.put("blob_tx_count", count_blob_transactions(block)) - |> Map.put("blob_gas_used", block.blob_gas_used) - |> Map.put("excess_blob_gas", block.excess_blob_gas) end end + "ethereum" -> + defp chain_type_fields(result, block, single_block?) do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.EthereumView.extend_block_json_response(result, block, single_block?) + end + _ -> defp chain_type_fields(result, _block, _single_block?) do result diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/ethereum_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/ethereum_view.ex new file mode 100644 index 000000000000..7fce94ef6297 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/ethereum_view.ex @@ -0,0 +1,41 @@ +defmodule BlockScoutWeb.API.V2.EthereumView do + alias Explorer.Chain.{Block, Transaction} + + def extend_transaction_json_response(out_json, %Transaction{} = transaction) do + case Map.get(transaction, :beacon_blob_transaction) do + nil -> + out_json + + %Ecto.Association.NotLoaded{} -> + out_json + + item -> + out_json + |> Map.put("max_fee_per_blob_gas", item.max_fee_per_blob_gas) + |> Map.put("blob_versioned_hashes", item.blob_versioned_hashes) + |> Map.put("blob_gas_used", item.blob_gas_used) + |> Map.put("blob_gas_price", item.blob_gas_price) + |> Map.put("burnt_blob_fee", Decimal.mult(item.blob_gas_used, item.blob_gas_price)) + end + end + + def extend_block_json_response(out_json, %Block{} = block, single_block?) do + blob_gas_used = Map.get(block, :blob_gas_used) + excess_blob_gas = Map.get(block, :excess_blob_gas) + + if single_block? do + blob_gas_price = Block.transaction_blob_gas_price(block.transactions) + burnt_blob_transaction_fees = Decimal.mult(blob_gas_used || 0, blob_gas_price || 0) + + out_json + |> Map.put("blob_gas_used", blob_gas_used) + |> Map.put("excess_blob_gas", excess_blob_gas) + |> Map.put("blob_gas_price", blob_gas_price) + |> Map.put("burnt_blob_fees", burnt_blob_transaction_fees) + else + out_json + |> Map.put("blob_gas_used", blob_gas_used) + |> Map.put("excess_blob_gas", excess_blob_gas) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/optimism_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/optimism_view.ex index b1d003a1f109..353d09a4e9bd 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/optimism_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/optimism_view.ex @@ -146,4 +146,35 @@ defmodule BlockScoutWeb.API.V2.OptimismView do def render("optimism_items_count.json", %{count: count}) do count end + + def extend_transaction_json_response(out_json, %Transaction{} = transaction) do + out_json + |> add_optional_transaction_field(transaction, :l1_fee) + |> add_optional_transaction_field(transaction, :l1_fee_scalar) + |> add_optional_transaction_field(transaction, :l1_gas_price) + |> add_optional_transaction_field(transaction, :l1_gas_used) + |> add_optimism_fields(transaction.hash) + end + + defp add_optional_transaction_field(out_json, transaction, field) do + case Map.get(transaction, field) do + nil -> out_json + value -> Map.put(out_json, Atom.to_string(field), value) + end + end + + defp add_optimism_fields(out_json, transaction_hash) do + withdrawals = + transaction_hash + |> Withdrawal.transaction_statuses() + |> Enum.map(fn {nonce, status, l1_transaction_hash} -> + %{ + "nonce" => nonce, + "status" => status, + "l1_transaction_hash" => l1_transaction_hash + } + end) + + Map.put(out_json, "op_withdrawals", withdrawals) + end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_edge_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_edge_view.ex index 3a813a6a1960..3e92f7893ff3 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_edge_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_edge_view.ex @@ -1,6 +1,10 @@ defmodule BlockScoutWeb.API.V2.PolygonEdgeView do use BlockScoutWeb, :view + alias BlockScoutWeb.API.V2.Helper + alias Explorer.Chain + alias Explorer.Chain.PolygonEdge.Reader + @spec render(String.t(), map()) :: map() def render("polygon_edge_deposits.json", %{ deposits: deposits, @@ -47,4 +51,47 @@ defmodule BlockScoutWeb.API.V2.PolygonEdgeView do def render("polygon_edge_items_count.json", %{count: count}) do count end + + def extend_transaction_json_response(out_json, tx_hash, connection) do + out_json + |> Map.put("polygon_edge_deposit", polygon_edge_deposit(tx_hash, connection)) + |> Map.put("polygon_edge_withdrawal", polygon_edge_withdrawal(tx_hash, connection)) + end + + defp polygon_edge_deposit(transaction_hash, conn) do + transaction_hash + |> Reader.deposit_by_transaction_hash() + |> polygon_edge_deposit_or_withdrawal(conn) + end + + defp polygon_edge_withdrawal(transaction_hash, conn) do + transaction_hash + |> Reader.withdrawal_by_transaction_hash() + |> polygon_edge_deposit_or_withdrawal(conn) + end + + defp polygon_edge_deposit_or_withdrawal(item, conn) do + if not is_nil(item) do + {from_address, from_address_hash} = hash_to_address_and_hash(item.from) + {to_address, to_address_hash} = hash_to_address_and_hash(item.to) + + item + |> Map.put(:from, Helper.address_with_info(conn, from_address, from_address_hash, item.from)) + |> Map.put(:to, Helper.address_with_info(conn, to_address, to_address_hash, item.to)) + end + end + + defp hash_to_address_and_hash(hash) do + with false <- is_nil(hash), + {:ok, address} <- + Chain.hash_to_address( + hash, + [necessity_by_association: %{:names => :optional, :smart_contract => :optional}, api?: true], + false + ) do + {address, address.hash} + else + _ -> {nil, nil} + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_zkevm_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_zkevm_view.ex index 1f99d6e5749e..051851bf0e5d 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_zkevm_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_zkevm_view.ex @@ -1,6 +1,8 @@ defmodule BlockScoutWeb.API.V2.PolygonZkevmView do use BlockScoutWeb, :view + alias Explorer.Chain.Transaction + @doc """ Function to render GET requests to `/api/v2/zkevm/batches/:batch_number` endpoint. """ @@ -158,4 +160,30 @@ defmodule BlockScoutWeb.API.V2.PolygonZkevmView do } end) end + + def extend_transaction_json_response(out_json, %Transaction{} = transaction) do + extended_result = + out_json + |> add_optional_transaction_field(transaction, "zkevm_batch_number", :zkevm_batch, :number) + |> add_optional_transaction_field(transaction, "zkevm_sequence_hash", :zkevm_sequence_transaction, :hash) + |> add_optional_transaction_field(transaction, "zkevm_verify_hash", :zkevm_verify_transaction, :hash) + + Map.put(extended_result, "zkevm_status", zkevm_status(extended_result)) + end + + defp zkevm_status(result_map) do + if is_nil(Map.get(result_map, "zkevm_sequence_hash")) do + "Confirmed by Sequencer" + else + "L1 Confirmed" + end + end + + defp add_optional_transaction_field(out_json, transaction, out_field, association, association_field) do + case Map.get(transaction, association) do + nil -> out_json + %Ecto.Association.NotLoaded{} -> out_json + item -> Map.put(out_json, out_field, Map.get(item, association_field)) + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/rootstock_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/rootstock_view.ex new file mode 100644 index 000000000000..06d4d8e68f21 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/rootstock_view.ex @@ -0,0 +1,19 @@ +defmodule BlockScoutWeb.API.V2.RootstockView do + alias Explorer.Chain.Block + + def extend_block_json_response(out_json, %Block{} = block) do + out_json + |> add_optional_transaction_field(block, :minimum_gas_price) + |> add_optional_transaction_field(block, :bitcoin_merged_mining_header) + |> add_optional_transaction_field(block, :bitcoin_merged_mining_coinbase_transaction) + |> add_optional_transaction_field(block, :bitcoin_merged_mining_merkle_proof) + |> add_optional_transaction_field(block, :hash_for_merged_mining) + end + + defp add_optional_transaction_field(out_json, block, field) do + case Map.get(block, field) do + nil -> out_json + value -> Map.put(out_json, Atom.to_string(field), value) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/stability_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/stability_view.ex new file mode 100644 index 000000000000..f713428a3d08 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/stability_view.ex @@ -0,0 +1,126 @@ +defmodule BlockScoutWeb.API.V2.StabilityView do + alias BlockScoutWeb.API.V2.{Helper, TokenView} + alias Explorer.Chain.{Hash, Log, Token, Transaction} + + @api_true [api?: true] + @transaction_fee_event_signature "0x99e7b0ba56da2819c37c047f0511fd2bf6c9b4e27b4a979a19d6da0f74be8155" + @transaction_fee_event_abi [ + %{ + "anonymous" => false, + "inputs" => [ + %{ + "indexed" => false, + "internalType" => "address", + "name" => "token", + "type" => "address" + }, + %{ + "indexed" => false, + "internalType" => "uint256", + "name" => "totalFee", + "type" => "uint256" + }, + %{ + "indexed" => false, + "internalType" => "address", + "name" => "validator", + "type" => "address" + }, + %{ + "indexed" => false, + "internalType" => "uint256", + "name" => "validatorFee", + "type" => "uint256" + }, + %{ + "indexed" => false, + "internalType" => "address", + "name" => "dapp", + "type" => "address" + }, + %{ + "indexed" => false, + "internalType" => "uint256", + "name" => "dappFee", + "type" => "uint256" + } + ], + "name" => "TransactionFee", + "type" => "event" + } + ] + + def extend_transaction_json_response(out_json, %Transaction{} = transaction) do + case transaction.transaction_fee_log do + [ + {"token", "address", false, token_address_hash}, + {"totalFee", "uint256", false, total_fee}, + {"validator", "address", false, validator_address_hash}, + {"validatorFee", "uint256", false, validator_fee}, + {"dapp", "address", false, dapp_address_hash}, + {"dappFee", "uint256", false, dapp_fee} + ] -> + stability_fee = %{ + "token" => + TokenView.render("token.json", %{ + token: transaction.transaction_fee_token, + contract_address_hash: bytes_to_address_hash(token_address_hash) + }), + "validator_address" => + Helper.address_with_info(nil, nil, bytes_to_address_hash(validator_address_hash), false), + "dapp_address" => Helper.address_with_info(nil, nil, bytes_to_address_hash(dapp_address_hash), false), + "total_fee" => to_string(total_fee), + "dapp_fee" => to_string(dapp_fee), + "validator_fee" => to_string(validator_fee) + } + + out_json + |> Map.put("stability_fee", stability_fee) + + _ -> + out_json + end + end + + def transform_transactions(transactions) do + do_extend_with_stability_fees_info(transactions) + end + + defp do_extend_with_stability_fees_info(transactions) when is_list(transactions) do + {transactions, _tokens_acc} = + Enum.map_reduce(transactions, %{}, fn transaction, tokens_acc -> + case Log.fetch_log_by_tx_hash_and_first_topic(transaction.hash, @transaction_fee_event_signature, @api_true) do + fee_log when not is_nil(fee_log) -> + {:ok, _selector, mapping} = Log.find_and_decode(@transaction_fee_event_abi, fee_log, transaction.hash) + + [{"token", "address", false, token_address_hash}, _, _, _, _, _] = mapping + + {token, new_tokens_acc} = check_tokens_acc(bytes_to_address_hash(token_address_hash), tokens_acc) + + {%Transaction{transaction | transaction_fee_log: mapping, transaction_fee_token: token}, new_tokens_acc} + + _ -> + {transaction, tokens_acc} + end + end) + + transactions + end + + defp do_extend_with_stability_fees_info(transaction) do + [transaction] = do_extend_with_stability_fees_info([transaction]) + transaction + end + + defp check_tokens_acc(token_address_hash, tokens_acc) do + if Map.has_key?(tokens_acc, token_address_hash) do + {tokens_acc[token_address_hash], tokens_acc} + else + token = Token.get_by_contract_address_hash(token_address_hash, @api_true) + + {token, Map.put(tokens_acc, token_address_hash, token)} + end + end + + defp bytes_to_address_hash(bytes), do: %Hash{byte_count: 20, bytes: bytes} +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/suave_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/suave_view.ex new file mode 100644 index 000000000000..8bbee60b6f1c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/suave_view.ex @@ -0,0 +1,130 @@ +defmodule BlockScoutWeb.API.V2.SuaveView do + alias BlockScoutWeb.API.V2.Helper, as: APIHelper + alias BlockScoutWeb.API.V2.TransactionView + + alias Explorer.Helper, as: ExplorerHelper + + alias Ecto.Association.NotLoaded + alias Explorer.Chain.{Hash, Transaction} + + @suave_bid_event "0x83481d5b04dea534715acad673a8177a46fc93882760f36bdc16ccac439d504e" + + def extend_transaction_json_response(%Transaction{} = transaction, out_json, single_tx?, conn, watchlist_names) do + if is_nil(Map.get(transaction, :execution_node_hash)) do + out_json + else + wrapped_to_address = Map.get(transaction, :wrapped_to_address) + wrapped_to_address_hash = Map.get(transaction, :wrapped_to_address_hash) + wrapped_input = Map.get(transaction, :wrapped_input) + wrapped_hash = Map.get(transaction, :wrapped_hash) + execution_node = Map.get(transaction, :execution_node) + execution_node_hash = Map.get(transaction, :execution_node_hash) + wrapped_type = Map.get(transaction, :wrapped_type) + wrapped_nonce = Map.get(transaction, :wrapped_nonce) + wrapped_gas = Map.get(transaction, :wrapped_gas) + wrapped_gas_price = Map.get(transaction, :wrapped_gas_price) + wrapped_max_priority_fee_per_gas = Map.get(transaction, :wrapped_max_priority_fee_per_gas) + wrapped_max_fee_per_gas = Map.get(transaction, :wrapped_max_fee_per_gas) + wrapped_value = Map.get(transaction, :wrapped_value) + + {[wrapped_decoded_input], _, _} = + TransactionView.decode_transactions( + [ + %Transaction{ + to_address: wrapped_to_address, + input: wrapped_input, + hash: wrapped_hash + } + ], + false + ) + + out_json + |> Map.put("allowed_peekers", suave_parse_allowed_peekers(transaction.logs)) + |> Map.put( + "execution_node", + APIHelper.address_with_info( + conn, + execution_node, + execution_node_hash, + single_tx?, + watchlist_names + ) + ) + |> Map.put("wrapped", %{ + "type" => wrapped_type, + "nonce" => wrapped_nonce, + "to" => + APIHelper.address_with_info( + conn, + wrapped_to_address, + wrapped_to_address_hash, + single_tx?, + watchlist_names + ), + "gas_limit" => wrapped_gas, + "gas_price" => wrapped_gas_price, + "fee" => + TransactionView.format_fee( + Transaction.fee( + %Transaction{gas: wrapped_gas, gas_price: wrapped_gas_price, gas_used: nil}, + :wei + ) + ), + "max_priority_fee_per_gas" => wrapped_max_priority_fee_per_gas, + "max_fee_per_gas" => wrapped_max_fee_per_gas, + "value" => wrapped_value, + "hash" => wrapped_hash, + "method" => + TransactionView.method_name( + %Transaction{to_address: wrapped_to_address, input: wrapped_input}, + wrapped_decoded_input + ), + "decoded_input" => TransactionView.decoded_input(wrapped_decoded_input), + "raw_input" => wrapped_input + }) + end + end + + # @spec suave_parse_allowed_peekers(Ecto.Schema.has_many(Log.t())) :: [String.t()] + defp suave_parse_allowed_peekers(%NotLoaded{}), do: [] + + defp suave_parse_allowed_peekers(logs) do + suave_bid_contracts = + Application.get_all_env(:explorer)[Transaction][:suave_bid_contracts] + |> String.split(",") + |> Enum.map(fn sbc -> String.downcase(String.trim(sbc)) end) + + bid_event = + Enum.find(logs, fn log -> + sanitize_log_first_topic(log.first_topic) == @suave_bid_event && + Enum.member?(suave_bid_contracts, String.downcase(Hash.to_string(log.address_hash))) + end) + + if is_nil(bid_event) do + [] + else + [_bid_id, _decryption_condition, allowed_peekers] = + ExplorerHelper.decode_data(bid_event.data, [{:bytes, 16}, {:uint, 64}, {:array, :address}]) + + Enum.map(allowed_peekers, fn peeker -> + "0x" <> Base.encode16(peeker, case: :lower) + end) + end + end + + defp sanitize_log_first_topic(first_topic) do + if is_nil(first_topic) do + "" + else + sanitized = + if is_binary(first_topic) do + first_topic + else + Hash.to_string(first_topic) + end + + String.downcase(sanitized) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index a1d8c5008266..cd8d57e3c30f 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -2,6 +2,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do use BlockScoutWeb, :view alias BlockScoutWeb.API.V2.{ApiView, Helper, TokenView} + alias BlockScoutWeb.{ABIEncodedValueView, TransactionView} alias BlockScoutWeb.Models.GetTransactionTags alias BlockScoutWeb.Tokens.Helper, as: TokensHelper @@ -10,14 +11,11 @@ defmodule BlockScoutWeb.API.V2.TransactionView do alias Explorer.{Chain, Market} alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Token, Transaction, Wei} alias Explorer.Chain.Block.Reward - alias Explorer.Chain.Optimism.Withdrawal, as: OptimismWithdrawal - alias Explorer.Chain.PolygonEdge.Reader alias Explorer.Chain.Transaction.StateChange alias Explorer.Counters.AverageBlockTime alias Timex.Duration import BlockScoutWeb.Account.AuthController, only: [current_user: 1] - import Explorer.Chain.Transaction, only: [maybe_prepare_stability_fees: 1, bytes_to_address_hash: 1] @api_true [api?: true] @@ -37,7 +35,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do %{ "items" => transactions - |> maybe_prepare_stability_fees() + |> chain_type_transformations() |> Enum.zip(decoded_transactions) |> Enum.map(fn {tx, decoded_input} -> prepare_transaction(tx, conn, false, block_height, watchlist_names, decoded_input) @@ -55,7 +53,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do {decoded_transactions, _, _} = decode_transactions(transactions, true) transactions - |> maybe_prepare_stability_fees() + |> chain_type_transformations() |> Enum.zip(decoded_transactions) |> Enum.map(fn {tx, decoded_input} -> prepare_transaction(tx, conn, false, block_height, watchlist_names, decoded_input) @@ -69,7 +67,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do %{ "items" => transactions - |> maybe_prepare_stability_fees() + |> chain_type_transformations() |> Enum.zip(decoded_transactions) |> Enum.map(fn {tx, decoded_input} -> prepare_transaction(tx, conn, false, block_height, decoded_input) end), "next_page_params" => next_page_params @@ -87,7 +85,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do {decoded_transactions, _, _} = decode_transactions(transactions, true) transactions - |> maybe_prepare_stability_fees() + |> chain_type_transformations() |> Enum.zip(decoded_transactions) |> Enum.map(fn {tx, decoded_input} -> prepare_transaction(tx, conn, false, block_height, decoded_input) end) end @@ -95,7 +93,10 @@ defmodule BlockScoutWeb.API.V2.TransactionView do def render("transaction.json", %{transaction: transaction, conn: conn}) do block_height = Chain.block_height(@api_true) {[decoded_input], _, _} = decode_transactions([transaction], false) - prepare_transaction(transaction |> maybe_prepare_stability_fees(), conn, true, block_height, decoded_input) + + transaction + |> chain_type_transformations() + |> prepare_transaction(conn, true, block_height, decoded_input) end def render("raw_trace.json", %{internal_transactions: internal_transactions}) do @@ -438,166 +439,6 @@ defmodule BlockScoutWeb.API.V2.TransactionView do result |> chain_type_fields(transaction, single_tx?, conn, watchlist_names) - |> maybe_put_stability_fee(transaction) - end - - defp add_optional_transaction_field(result, transaction, field) do - case Map.get(transaction, field) do - nil -> result - value -> Map.put(result, Atom.to_string(field), value) - end - end - - # credo:disable-for-next-line - defp chain_type_fields(result, transaction, single_tx?, conn, watchlist_names) do - case {single_tx?, Application.get_env(:explorer, :chain_type)} do - {true, "polygon_edge"} -> - result - |> Map.put("polygon_edge_deposit", polygon_edge_deposit(transaction.hash, conn)) - |> Map.put("polygon_edge_withdrawal", polygon_edge_withdrawal(transaction.hash, conn)) - - {true, "polygon_zkevm"} -> - extended_result = - result - |> add_optional_transaction_field(transaction, "zkevm_batch_number", :zkevm_batch, :number) - |> add_optional_transaction_field(transaction, "zkevm_sequence_hash", :zkevm_sequence_transaction, :hash) - |> add_optional_transaction_field(transaction, "zkevm_verify_hash", :zkevm_verify_transaction, :hash) - - Map.put(extended_result, "zkevm_status", zkevm_status(extended_result)) - - {true, "optimism"} -> - result - |> add_optional_transaction_field(transaction, :l1_fee) - |> add_optional_transaction_field(transaction, :l1_fee_scalar) - |> add_optional_transaction_field(transaction, :l1_gas_price) - |> add_optional_transaction_field(transaction, :l1_gas_used) - |> add_optimism_fields(transaction.hash, single_tx?) - - {true, "suave"} -> - suave_fields(transaction, result, single_tx?, conn, watchlist_names) - - {_, "ethereum"} -> - case Map.get(transaction, :beacon_blob_transaction) do - nil -> - result - - %Ecto.Association.NotLoaded{} -> - result - - item -> - result - |> Map.put("max_fee_per_blob_gas", item.max_fee_per_blob_gas) - |> Map.put("blob_versioned_hashes", item.blob_versioned_hashes) - |> Map.put("blob_gas_used", item.blob_gas_used) - |> Map.put("blob_gas_price", item.blob_gas_price) - |> Map.put("burnt_blob_fee", Decimal.mult(item.blob_gas_used, item.blob_gas_price)) - end - - _ -> - result - end - end - - defp add_optional_transaction_field(result, transaction, field_name, assoc_name, assoc_field) do - case Map.get(transaction, assoc_name) do - nil -> result - %Ecto.Association.NotLoaded{} -> result - item -> Map.put(result, field_name, Map.get(item, assoc_field)) - end - end - - defp zkevm_status(result_map) do - if is_nil(Map.get(result_map, "zkevm_sequence_hash")) do - "Confirmed by Sequencer" - else - "L1 Confirmed" - end - end - - if Application.compile_env(:explorer, :chain_type) != "suave" do - defp suave_fields(_transaction, result, _single_tx?, _conn, _watchlist_names), do: result - else - defp suave_fields(transaction, result, single_tx?, conn, watchlist_names) do - if is_nil(transaction.execution_node_hash) do - result - else - {[wrapped_decoded_input], _, _} = - decode_transactions( - [ - %Transaction{ - to_address: transaction.wrapped_to_address, - input: transaction.wrapped_input, - hash: transaction.wrapped_hash - } - ], - false - ) - - result - |> Map.put("allowed_peekers", Transaction.suave_parse_allowed_peekers(transaction.logs)) - |> Map.put( - "execution_node", - Helper.address_with_info( - conn, - transaction.execution_node, - transaction.execution_node_hash, - single_tx?, - watchlist_names - ) - ) - |> Map.put("wrapped", %{ - "type" => transaction.wrapped_type, - "nonce" => transaction.wrapped_nonce, - "to" => - Helper.address_with_info( - conn, - transaction.wrapped_to_address, - transaction.wrapped_to_address_hash, - single_tx?, - watchlist_names - ), - "gas_limit" => transaction.wrapped_gas, - "gas_price" => transaction.wrapped_gas_price, - "fee" => - format_fee( - Transaction.fee( - %Transaction{gas: transaction.wrapped_gas, gas_price: transaction.wrapped_gas_price, gas_used: nil}, - :wei - ) - ), - "max_priority_fee_per_gas" => transaction.wrapped_max_priority_fee_per_gas, - "max_fee_per_gas" => transaction.wrapped_max_fee_per_gas, - "value" => transaction.wrapped_value, - "hash" => transaction.wrapped_hash, - "method" => - method_name( - %Transaction{to_address: transaction.wrapped_to_address, input: transaction.wrapped_input}, - wrapped_decoded_input - ), - "decoded_input" => decoded_input(wrapped_decoded_input), - "raw_input" => transaction.wrapped_input - }) - end - end - end - - defp add_optimism_fields(result, transaction_hash, single_tx?) do - if Application.get_env(:explorer, :chain_type) == "optimism" && single_tx? do - withdrawals = - transaction_hash - |> OptimismWithdrawal.transaction_statuses() - |> Enum.map(fn {nonce, status, l1_transaction_hash} -> - %{ - "nonce" => nonce, - "status" => status, - "l1_transaction_hash" => l1_transaction_hash - } - end) - - Map.put(result, "op_withdrawals", withdrawals) - else - result - end end def token_transfers(_, _conn, false), do: nil @@ -928,71 +769,111 @@ defmodule BlockScoutWeb.API.V2.TransactionView do Map.merge(map, %{"change" => change}) end - defp polygon_edge_deposit(transaction_hash, conn) do - transaction_hash - |> Reader.deposit_by_transaction_hash() - |> polygon_edge_deposit_or_withdrawal(conn) - end + case Application.compile_env(:explorer, :chain_type) do + "polygon_edge" -> + defp chain_type_transformations(transactions) do + transactions + end - defp polygon_edge_withdrawal(transaction_hash, conn) do - transaction_hash - |> Reader.withdrawal_by_transaction_hash() - |> polygon_edge_deposit_or_withdrawal(conn) - end + defp chain_type_fields(result, transaction, single_tx?, conn, _watchlist_names) do + if single_tx? do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.PolygonEdgeView.extend_transaction_json_response(result, transaction.hash, conn) + else + result + end + end + + "polygon_zkevm" -> + defp chain_type_transformations(transactions) do + transactions + end - defp polygon_edge_deposit_or_withdrawal(item, conn) do - if not is_nil(item) do - {from_address, from_address_hash} = hash_to_address_and_hash(item.from) - {to_address, to_address_hash} = hash_to_address_and_hash(item.to) + defp chain_type_fields(result, transaction, single_tx?, _conn, _watchlist_names) do + if single_tx? do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.PolygonZkevmView.extend_transaction_json_response(result, transaction) + else + result + end + end - item - |> Map.put(:from, Helper.address_with_info(conn, from_address, from_address_hash, item.from)) - |> Map.put(:to, Helper.address_with_info(conn, to_address, to_address_hash, item.to)) - end - end + "zksync" -> + defp chain_type_transformations(transactions) do + transactions + end - defp hash_to_address_and_hash(hash) do - with false <- is_nil(hash), - {:ok, address} <- - Chain.hash_to_address( - hash, - [necessity_by_association: %{:names => :optional, :smart_contract => :optional}, api?: true], - false - ) do - {address, address.hash} - else - _ -> {nil, nil} - end - end + defp chain_type_fields(result, transaction, single_tx?, _conn, _watchlist_names) do + if single_tx? do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.ZkSyncView.extend_transaction_json_response(result, transaction) + else + result + end + end - defp maybe_put_stability_fee(body, transaction) do - with "stability" <- Application.get_env(:explorer, :chain_type), - [ - {"token", "address", false, token_address_hash}, - {"totalFee", "uint256", false, total_fee}, - {"validator", "address", false, validator_address_hash}, - {"validatorFee", "uint256", false, validator_fee}, - {"dapp", "address", false, dapp_address_hash}, - {"dappFee", "uint256", false, dapp_fee} - ] <- transaction.transaction_fee_log do - stability_fee = %{ - "token" => - TokenView.render("token.json", %{ - token: transaction.transaction_fee_token, - contract_address_hash: bytes_to_address_hash(token_address_hash) - }), - "validator_address" => Helper.address_with_info(nil, nil, bytes_to_address_hash(validator_address_hash), false), - "dapp_address" => Helper.address_with_info(nil, nil, bytes_to_address_hash(dapp_address_hash), false), - "total_fee" => to_string(total_fee), - "dapp_fee" => to_string(dapp_fee), - "validator_fee" => to_string(validator_fee) - } - - body - |> Map.put("stability_fee", stability_fee) - else - _ -> - body - end + "optimism" -> + defp chain_type_transformations(transactions) do + transactions + end + + defp chain_type_fields(result, transaction, single_tx?, _conn, _watchlist_names) do + if single_tx? do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.OptimismView.extend_transaction_json_response(result, transaction) + else + result + end + end + + "suave" -> + defp chain_type_transformations(transactions) do + transactions + end + + defp chain_type_fields(result, transaction, single_tx?, conn, watchlist_names) do + if single_tx? do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.SuaveView.extend_transaction_json_response( + transaction, + result, + single_tx?, + conn, + watchlist_names + ) + else + result + end + end + + "stability" -> + defp chain_type_transformations(transactions) do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.StabilityView.transform_transactions(transactions) + end + + defp chain_type_fields(result, transaction, _single_tx?, _conn, _watchlist_names) do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.StabilityView.extend_transaction_json_response(result, transaction) + end + + "ethereum" -> + defp chain_type_transformations(transactions) do + transactions + end + + defp chain_type_fields(result, transaction, _single_tx?, _conn, _watchlist_names) do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.EthereumView.extend_transaction_json_response(result, transaction) + end + + _ -> + defp chain_type_transformations(transactions) do + transactions + end + + defp chain_type_fields(result, _transaction, _single_tx?, _conn, _watchlist_names) do + result + end end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/zksync_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/zksync_view.ex new file mode 100644 index 000000000000..7f6bf7a26138 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/zksync_view.ex @@ -0,0 +1,235 @@ +defmodule BlockScoutWeb.API.V2.ZkSyncView do + use BlockScoutWeb, :view + + alias Explorer.Chain.{Block, Transaction} + alias Explorer.Chain.ZkSync.TransactionBatch + + @doc """ + Function to render GET requests to `/api/v2/zksync/batches/:batch_number` endpoint. + """ + @spec render(binary(), map()) :: map() | non_neg_integer() + def render("zksync_batch.json", %{batch: batch}) do + l2_transactions = + if Map.has_key?(batch, :l2_transactions) do + Enum.map(batch.l2_transactions, fn tx -> tx.hash end) + end + + %{ + "number" => batch.number, + "timestamp" => batch.timestamp, + "root_hash" => batch.root_hash, + "l1_tx_count" => batch.l1_tx_count, + "l2_tx_count" => batch.l2_tx_count, + "l1_gas_price" => batch.l1_gas_price, + "l2_fair_gas_price" => batch.l2_fair_gas_price, + "start_block" => batch.start_block, + "end_block" => batch.end_block, + "transactions" => l2_transactions + } + |> add_l1_txs_info_and_status(batch) + end + + @doc """ + Function to render GET requests to `/api/v2/zksync/batches` endpoint. + """ + def render("zksync_batches.json", %{ + batches: batches, + next_page_params: next_page_params + }) do + %{ + items: render_zksync_batches(batches), + next_page_params: next_page_params + } + end + + @doc """ + Function to render GET requests to `/api/v2/main-page/zksync/batches/confirmed` endpoint. + """ + def render("zksync_batches.json", %{batches: batches}) do + %{items: render_zksync_batches(batches)} + end + + @doc """ + Function to render GET requests to `/api/v2/zksync/batches/count` endpoint. + """ + def render("zksync_batches_count.json", %{count: count}) do + count + end + + @doc """ + Function to render GET requests to `/api/v2/main-page/zksync/batches/latest-number` endpoint. + """ + def render("zksync_batch_latest_number.json", %{number: number}) do + number + end + + defp render_zksync_batches(batches) do + Enum.map(batches, fn batch -> + %{ + "number" => batch.number, + "timestamp" => batch.timestamp, + "tx_count" => batch.l1_tx_count + batch.l2_tx_count + } + |> add_l1_txs_info_and_status(batch) + end) + end + + @doc """ + Extends the json output with a sub-map containing information related + zksync: batch number and associated L1 transactions and their timestmaps. + + ## Parameters + - `out_json`: a map defining output json which will be extended + - `transaction`: transaction structure containing zksync related data + + ## Returns + A map extended with data related zksync rollup + """ + @spec extend_transaction_json_response(map(), %{ + :__struct__ => Explorer.Chain.Transaction, + :zksync_batch => any(), + :zksync_commit_transaction => any(), + :zksync_execute_transaction => any(), + :zksync_prove_transaction => any(), + optional(any()) => any() + }) :: map() + def extend_transaction_json_response(out_json, %Transaction{} = transaction) do + do_add_zksync_info(out_json, transaction) + end + + @doc """ + Extends the json output with a sub-map containing information related + zksync: batch number and associated L1 transactions and their timestmaps. + + ## Parameters + - `out_json`: a map defining output json which will be extended + - `block`: block structure containing zksync related data + + ## Returns + A map extended with data related zksync rollup + """ + @spec extend_block_json_response(map(), %{ + :__struct__ => Explorer.Chain.Block, + :zksync_batch => any(), + :zksync_commit_transaction => any(), + :zksync_execute_transaction => any(), + :zksync_prove_transaction => any(), + optional(any()) => any() + }) :: map() + def extend_block_json_response(out_json, %Block{} = block) do + do_add_zksync_info(out_json, block) + end + + defp do_add_zksync_info(out_json, zksync_entity) do + res = + %{} + |> do_add_l1_txs_info_and_status(%{ + batch_number: get_batch_number(zksync_entity), + commit_transaction: zksync_entity.zksync_commit_transaction, + prove_transaction: zksync_entity.zksync_prove_transaction, + execute_transaction: zksync_entity.zksync_execute_transaction + }) + |> Map.put("batch_number", get_batch_number(zksync_entity)) + + Map.put(out_json, "zksync", res) + end + + defp get_batch_number(zksync_entity) do + case Map.get(zksync_entity, :zksync_batch) do + nil -> nil + %Ecto.Association.NotLoaded{} -> nil + value -> value.number + end + end + + defp add_l1_txs_info_and_status(out_json, %TransactionBatch{} = batch) do + do_add_l1_txs_info_and_status(out_json, batch) + end + + defp do_add_l1_txs_info_and_status(out_json, zksync_item) do + l1_txs = get_associated_l1_txs(zksync_item) + + out_json + |> Map.merge(%{ + "status" => batch_status(zksync_item), + "commit_transaction_hash" => get_2map_data(l1_txs, :commit_transaction, :hash), + "commit_transaction_timestamp" => get_2map_data(l1_txs, :commit_transaction, :ts), + "prove_transaction_hash" => get_2map_data(l1_txs, :prove_transaction, :hash), + "prove_transaction_timestamp" => get_2map_data(l1_txs, :prove_transaction, :ts), + "execute_transaction_hash" => get_2map_data(l1_txs, :execute_transaction, :hash), + "execute_transaction_timestamp" => get_2map_data(l1_txs, :execute_transaction, :ts) + }) + end + + # Extract transaction hash and timestamp for L1 transactions associated with + # a zksync rollup entity: batch, transaction or block. + # + # ## Parameters + # - `zksync_item`: A batch, transaction, or block. + # + # ## Returns + # A map containing nesting maps describing corresponding L1 transactions + defp get_associated_l1_txs(zksync_item) do + [:commit_transaction, :prove_transaction, :execute_transaction] + |> Enum.reduce(%{}, fn key, l1_txs -> + case Map.get(zksync_item, key) do + nil -> Map.put(l1_txs, key, nil) + %Ecto.Association.NotLoaded{} -> Map.put(l1_txs, key, nil) + value -> Map.put(l1_txs, key, %{hash: value.hash, ts: value.timestamp}) + end + end) + end + + # Inspects L1 transactions of the batch to determine the batch status. + # + # ## Parameters + # - `zksync_item`: A batch, transaction, or block. + # + # ## Returns + # A string with one of predefined statuses + defp batch_status(zksync_item) do + cond do + specified?(zksync_item.execute_transaction) -> "Executed on L1" + specified?(zksync_item.prove_transaction) -> "Validated on L1" + specified?(zksync_item.commit_transaction) -> "Sent to L1" + # Batch entity itself has no batch_number + not Map.has_key?(zksync_item, :batch_number) -> "Sealed on L2" + not is_nil(zksync_item.batch_number) -> "Sealed on L2" + true -> "Processed on L2" + end + end + + # Checks if an item associated with a DB entity has actual value + # + # ## Parameters + # - `associated_item`: an item associated with a DB entity + # + # ## Returns + # - `false`: if the item is nil or not loaded + # - `true`: if the item has actual value + defp specified?(associated_item) do + case associated_item do + nil -> false + %Ecto.Association.NotLoaded{} -> false + _ -> true + end + end + + # Gets the value of an element nested in a map using two keys. + # + # Clarification: Returns `map[key1][key2]` + # + # ## Parameters + # - `map`: The high-level map. + # - `key1`: The key of the element in `map`. + # - `key2`: The key of the element in the map accessible by `map[key1]`. + # + # ## Returns + # The value of the element, or `nil` if the map accessible by `key1` does not exist. + defp get_2map_data(map, key1, key2) do + case Map.get(map, key1) do + nil -> nil + inner_map -> Map.get(inner_map, key2) + end + end +end diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index 21493948ed25..78757a211351 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -30,7 +30,8 @@ defmodule BlockScoutWeb.Mixfile do Explorer.Chain.Beacon.Reader, Explorer.Chain.Cache.OptimismFinalizationPeriod, Explorer.Chain.Optimism.OutputRoot, - Explorer.Chain.Optimism.WithdrawalEvent + Explorer.Chain.Optimism.WithdrawalEvent, + Explorer.Chain.ZkSync.Reader ] ] ] diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs index 1f0e671812ff..9e93bc58654b 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs @@ -974,142 +974,132 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do end end - describe "stability fees" do - setup %{conn: conn} do - old_env = Application.get_env(:explorer, :chain_type) + if Application.compile_env(:explorer, :chain_type) == "stability" do + describe "stability fees" do + test "check stability fees", %{conn: conn} do + tx = insert(:transaction) |> with_block() - Application.put_env(:explorer, :chain_type, "stability") - - on_exit(fn -> - Application.put_env(:explorer, :chain_type, old_env) - end) - - %{conn: conn} - end - - test "check stability fees", %{conn: conn} do - tx = insert(:transaction) |> with_block() - - _log = - insert(:log, - transaction: tx, - index: 1, - block: tx.block, - block_number: tx.block_number, - first_topic: topic(@first_topic_hex_string_1), - data: - "0x000000000000000000000000dc2b93f3291030f3f7a6d9363ac37757f7ad5c4300000000000000000000000000000000000000000000000000002824369a100000000000000000000000000046b555cb3962bf9533c437cbd04a2f702dfdb999000000000000000000000000000000000000000000000000000014121b4d0800000000000000000000000000faf7a981360c2fab3a5ab7b3d6d8d0cf97a91eb9000000000000000000000000000000000000000000000000000014121b4d0800" - ) - - insert(:token, contract_address: build(:address, hash: "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43")) - request = get(conn, "/api/v2/transactions") + _log = + insert(:log, + transaction: tx, + index: 1, + block: tx.block, + block_number: tx.block_number, + first_topic: topic(@first_topic_hex_string_1), + data: + "0x000000000000000000000000dc2b93f3291030f3f7a6d9363ac37757f7ad5c4300000000000000000000000000000000000000000000000000002824369a100000000000000000000000000046b555cb3962bf9533c437cbd04a2f702dfdb999000000000000000000000000000000000000000000000000000014121b4d0800000000000000000000000000faf7a981360c2fab3a5ab7b3d6d8d0cf97a91eb9000000000000000000000000000000000000000000000000000014121b4d0800" + ) - assert %{ - "items" => [ - %{ - "stability_fee" => %{ - "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"}, - "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"}, - "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"}, - "total_fee" => "44136000000000", - "dapp_fee" => "22068000000000", - "validator_fee" => "22068000000000" + insert(:token, contract_address: build(:address, hash: "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43")) + request = get(conn, "/api/v2/transactions") + + assert %{ + "items" => [ + %{ + "stability_fee" => %{ + "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"}, + "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"}, + "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"}, + "total_fee" => "44136000000000", + "dapp_fee" => "22068000000000", + "validator_fee" => "22068000000000" + } } + ] + } = json_response(request, 200) + + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}") + + assert %{ + "stability_fee" => %{ + "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"}, + "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"}, + "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"}, + "total_fee" => "44136000000000", + "dapp_fee" => "22068000000000", + "validator_fee" => "22068000000000" } - ] - } = json_response(request, 200) - - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}") - - assert %{ - "stability_fee" => %{ - "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"}, - "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"}, - "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"}, - "total_fee" => "44136000000000", - "dapp_fee" => "22068000000000", - "validator_fee" => "22068000000000" - } - } = json_response(request, 200) - - request = get(conn, "/api/v2/addresses/#{to_string(tx.from_address_hash)}/transactions") - - assert %{ - "items" => [ - %{ - "stability_fee" => %{ - "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"}, - "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"}, - "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"}, - "total_fee" => "44136000000000", - "dapp_fee" => "22068000000000", - "validator_fee" => "22068000000000" + } = json_response(request, 200) + + request = get(conn, "/api/v2/addresses/#{to_string(tx.from_address_hash)}/transactions") + + assert %{ + "items" => [ + %{ + "stability_fee" => %{ + "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"}, + "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"}, + "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"}, + "total_fee" => "44136000000000", + "dapp_fee" => "22068000000000", + "validator_fee" => "22068000000000" + } } - } - ] - } = json_response(request, 200) - end + ] + } = json_response(request, 200) + end - test "check stability if token absent in DB", %{conn: conn} do - tx = insert(:transaction) |> with_block() + test "check stability if token absent in DB", %{conn: conn} do + tx = insert(:transaction) |> with_block() - _log = - insert(:log, - transaction: tx, - index: 1, - block: tx.block, - block_number: tx.block_number, - first_topic: topic(@first_topic_hex_string_1), - data: - "0x000000000000000000000000dc2b93f3291030f3f7a6d9363ac37757f7ad5c4300000000000000000000000000000000000000000000000000002824369a100000000000000000000000000046b555cb3962bf9533c437cbd04a2f702dfdb999000000000000000000000000000000000000000000000000000014121b4d0800000000000000000000000000faf7a981360c2fab3a5ab7b3d6d8d0cf97a91eb9000000000000000000000000000000000000000000000000000014121b4d0800" - ) - - request = get(conn, "/api/v2/transactions") + _log = + insert(:log, + transaction: tx, + index: 1, + block: tx.block, + block_number: tx.block_number, + first_topic: topic(@first_topic_hex_string_1), + data: + "0x000000000000000000000000dc2b93f3291030f3f7a6d9363ac37757f7ad5c4300000000000000000000000000000000000000000000000000002824369a100000000000000000000000000046b555cb3962bf9533c437cbd04a2f702dfdb999000000000000000000000000000000000000000000000000000014121b4d0800000000000000000000000000faf7a981360c2fab3a5ab7b3d6d8d0cf97a91eb9000000000000000000000000000000000000000000000000000014121b4d0800" + ) - assert %{ - "items" => [ - %{ - "stability_fee" => %{ - "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"}, - "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"}, - "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"}, - "total_fee" => "44136000000000", - "dapp_fee" => "22068000000000", - "validator_fee" => "22068000000000" + request = get(conn, "/api/v2/transactions") + + assert %{ + "items" => [ + %{ + "stability_fee" => %{ + "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"}, + "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"}, + "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"}, + "total_fee" => "44136000000000", + "dapp_fee" => "22068000000000", + "validator_fee" => "22068000000000" + } } + ] + } = json_response(request, 200) + + request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}") + + assert %{ + "stability_fee" => %{ + "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"}, + "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"}, + "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"}, + "total_fee" => "44136000000000", + "dapp_fee" => "22068000000000", + "validator_fee" => "22068000000000" } - ] - } = json_response(request, 200) - - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}") - - assert %{ - "stability_fee" => %{ - "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"}, - "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"}, - "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"}, - "total_fee" => "44136000000000", - "dapp_fee" => "22068000000000", - "validator_fee" => "22068000000000" - } - } = json_response(request, 200) - - request = get(conn, "/api/v2/addresses/#{to_string(tx.from_address_hash)}/transactions") - - assert %{ - "items" => [ - %{ - "stability_fee" => %{ - "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"}, - "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"}, - "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"}, - "total_fee" => "44136000000000", - "dapp_fee" => "22068000000000", - "validator_fee" => "22068000000000" + } = json_response(request, 200) + + request = get(conn, "/api/v2/addresses/#{to_string(tx.from_address_hash)}/transactions") + + assert %{ + "items" => [ + %{ + "stability_fee" => %{ + "token" => %{"address" => "0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43"}, + "validator_address" => %{"hash" => "0x46B555CB3962bF9533c437cBD04A2f702dfdB999"}, + "dapp_address" => %{"hash" => "0xFAf7a981360c2FAb3a5Ab7b3D6d8D0Cf97a91Eb9"}, + "total_fee" => "44136000000000", + "dapp_fee" => "22068000000000", + "validator_fee" => "22068000000000" + } } - } - ] - } = json_response(request, 200) + ] + } = json_response(request, 200) + end end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex index f3c6a88662c5..012ac0ec01e3 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex @@ -174,6 +174,11 @@ defmodule EthereumJSONRPC.Log do end end + # zkSync specific log fields + defp entry_to_elixir({key, _}) when key in ~w(l1BatchNumber logType) do + {nil, nil} + end + defp put_topics(params, topics) when is_map(params) and is_list(topics) do params |> Map.put(:first_topic, Enum.at(topics, 0)) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex index f4913495582a..150fd7f18264 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex @@ -374,6 +374,12 @@ defmodule EthereumJSONRPC.Receipt do :ignore end + # zkSync specific transaction receipt fields + defp entry_to_elixir({key, _}) + when key in ~w(l1BatchNumber l1BatchTxIndex l2ToL1Logs) do + :ignore + end + defp entry_to_elixir({key, value}) do {:error, {:unknown_key, %{key: key, value: value}}} end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex index b3fb9c55ec03..85d3161556ff 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex @@ -264,11 +264,8 @@ defmodule EthereumJSONRPC.Transaction do "hash" => hash, "input" => input, "nonce" => nonce, - "r" => r, - "s" => s, "to" => to_address_hash, "transactionIndex" => index, - "v" => v, "value" => value, "type" => type, "maxPriorityFeePerGas" => max_priority_fee_per_gas, @@ -285,10 +282,7 @@ defmodule EthereumJSONRPC.Transaction do index: index, input: input, nonce: nonce, - r: r, - s: s, to_address_hash: to_address_hash, - v: v, value: value, transaction_index: index, type: type, @@ -298,7 +292,10 @@ defmodule EthereumJSONRPC.Transaction do put_if_present(transaction, result, [ {"creates", :created_contract_address_hash}, - {"block_timestamp", :block_timestamp} + {"block_timestamp", :block_timestamp}, + {"r", :r}, + {"s", :s}, + {"v", :v} ]) end @@ -313,11 +310,8 @@ defmodule EthereumJSONRPC.Transaction do "hash" => hash, "input" => input, "nonce" => nonce, - "r" => r, - "s" => s, "to" => to_address_hash, "transactionIndex" => index, - "v" => v, "value" => value, "type" => type, "maxPriorityFeePerGas" => max_priority_fee_per_gas, @@ -334,10 +328,7 @@ defmodule EthereumJSONRPC.Transaction do index: index, input: input, nonce: nonce, - r: r, - s: s, to_address_hash: to_address_hash, - v: v, value: value, transaction_index: index, type: type, @@ -347,10 +338,14 @@ defmodule EthereumJSONRPC.Transaction do put_if_present(transaction, result, [ {"creates", :created_contract_address_hash}, - {"block_timestamp", :block_timestamp} + {"block_timestamp", :block_timestamp}, + {"r", :r}, + {"s", :s}, + {"v", :v} ]) end + # for legacy txs without maxPriorityFeePerGas and maxFeePerGas def do_elixir_to_params( %{ "blockHash" => block_hash, @@ -361,11 +356,8 @@ defmodule EthereumJSONRPC.Transaction do "hash" => hash, "input" => input, "nonce" => nonce, - "r" => r, - "s" => s, "to" => to_address_hash, "transactionIndex" => index, - "v" => v, "value" => value, "type" => type } = transaction @@ -380,10 +372,7 @@ defmodule EthereumJSONRPC.Transaction do index: index, input: input, nonce: nonce, - r: r, - s: s, to_address_hash: to_address_hash, - v: v, value: value, transaction_index: index, type: type @@ -391,10 +380,14 @@ defmodule EthereumJSONRPC.Transaction do put_if_present(transaction, result, [ {"creates", :created_contract_address_hash}, - {"block_timestamp", :block_timestamp} + {"block_timestamp", :block_timestamp}, + {"r", :r}, + {"s", :s}, + {"v", :v} ]) end + # for legacy txs without type, maxPriorityFeePerGas and maxFeePerGas def do_elixir_to_params( %{ "blockHash" => block_hash, @@ -405,11 +398,8 @@ defmodule EthereumJSONRPC.Transaction do "hash" => hash, "input" => input, "nonce" => nonce, - "r" => r, - "s" => s, "to" => to_address_hash, "transactionIndex" => index, - "v" => v, "value" => value } = transaction ) do @@ -423,20 +413,21 @@ defmodule EthereumJSONRPC.Transaction do index: index, input: input, nonce: nonce, - r: r, - s: s, to_address_hash: to_address_hash, - v: v, value: value, transaction_index: index } put_if_present(transaction, result, [ {"creates", :created_contract_address_hash}, - {"block_timestamp", :block_timestamp} + {"block_timestamp", :block_timestamp}, + {"r", :r}, + {"s", :s}, + {"v", :v} ]) end + # for txs without gasPrice, maxPriorityFeePerGas and maxFeePerGas def do_elixir_to_params( %{ "blockHash" => block_hash, @@ -446,12 +437,9 @@ defmodule EthereumJSONRPC.Transaction do "hash" => hash, "input" => input, "nonce" => nonce, - "r" => r, - "s" => s, "to" => to_address_hash, "transactionIndex" => index, "type" => type, - "v" => v, "value" => value } = transaction ) do @@ -465,10 +453,7 @@ defmodule EthereumJSONRPC.Transaction do index: index, input: input, nonce: nonce, - r: r, - s: s, to_address_hash: to_address_hash, - v: v, value: value, transaction_index: index, type: type @@ -476,7 +461,10 @@ defmodule EthereumJSONRPC.Transaction do put_if_present(transaction, result, [ {"creates", :created_contract_address_hash}, - {"block_timestamp", :block_timestamp} + {"block_timestamp", :block_timestamp}, + {"r", :r}, + {"s", :s}, + {"v", :v} ]) end @@ -673,6 +661,11 @@ defmodule EthereumJSONRPC.Transaction do end end + # ZkSync fields + defp entry_to_elixir({key, _}) when key in ~w(l1BatchNumber l1BatchTxIndex) do + {:ignore, :ignore} + end + defp entry_to_elixir(_) do {nil, nil} end diff --git a/apps/explorer/config/dev.exs b/apps/explorer/config/dev.exs index 993294035684..a387ee24220c 100644 --- a/apps/explorer/config/dev.exs +++ b/apps/explorer/config/dev.exs @@ -20,6 +20,9 @@ config :explorer, Explorer.Repo.PolygonEdge, timeout: :timer.seconds(80) # Configure Polygon zkEVM database config :explorer, Explorer.Repo.PolygonZkevm, timeout: :timer.seconds(80) +# Configure ZkSync database +config :explorer, Explorer.Repo.ZkSync, timeout: :timer.seconds(80) + config :explorer, Explorer.Repo.RSK, timeout: :timer.seconds(80) config :explorer, Explorer.Repo.Shibarium, timeout: :timer.seconds(80) diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs index 4e4519adf99d..27fa8cad9575 100644 --- a/apps/explorer/config/prod.exs +++ b/apps/explorer/config/prod.exs @@ -28,6 +28,10 @@ config :explorer, Explorer.Repo.PolygonZkevm, prepare: :unnamed, timeout: :timer.seconds(60) +config :explorer, Explorer.Repo.ZkSync, + prepare: :unnamed, + timeout: :timer.seconds(60) + config :explorer, Explorer.Repo.RSK, prepare: :unnamed, timeout: :timer.seconds(60) diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index d4130b4272e2..6241f35eebd7 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -48,6 +48,7 @@ for repo <- [ Explorer.Repo.Optimism, Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, + Explorer.Repo.ZkSync, Explorer.Repo.RSK, Explorer.Repo.Shibarium, Explorer.Repo.Suave, diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 35e749c247a5..f7faba77acb6 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -147,6 +147,7 @@ defmodule Explorer.Application do Explorer.Repo.Optimism, Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, + Explorer.Repo.ZkSync, Explorer.Repo.RSK, Explorer.Repo.Shibarium, Explorer.Repo.Suave, diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index 3b20b957e496..395f3ada7044 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -3,6 +3,7 @@ defmodule Explorer.Chain.Block.Schema do alias Explorer.Chain.{Address, Block, Hash, PendingBlockOperation, Transaction, Wei, Withdrawal} alias Explorer.Chain.Block.{Reward, SecondDegreeRelation} + alias Explorer.Chain.ZkSync.BatchBlock, as: ZkSyncBatchBlock @chain_type_fields (case Application.compile_env(:explorer, :chain_type) do "ethereum" -> @@ -26,6 +27,18 @@ defmodule Explorer.Chain.Block.Schema do 2 ) + "zksync" -> + elem( + quote do + has_one(:zksync_batch_block, ZkSyncBatchBlock, foreign_key: :hash, references: :hash) + has_one(:zksync_batch, through: [:zksync_batch_block, :batch]) + has_one(:zksync_commit_transaction, through: [:zksync_batch, :commit_transaction]) + has_one(:zksync_prove_transaction, through: [:zksync_batch, :prove_transaction]) + has_one(:zksync_execute_transaction, through: [:zksync_batch, :execute_transaction]) + end, + 2 + ) + _ -> [] end) diff --git a/apps/explorer/lib/explorer/chain/import.ex b/apps/explorer/lib/explorer/chain/import.ex index 149649cd03c7..24f017c39228 100644 --- a/apps/explorer/lib/explorer/chain/import.ex +++ b/apps/explorer/lib/explorer/chain/import.ex @@ -123,7 +123,7 @@ defmodule Explorer.Chain.Import do milliseconds. #{@runner_options_doc} """ - @spec all(all_options()) :: all_result() + # @spec all(all_options()) :: all_result() def all(options) when is_map(options) do with {:ok, runner_options_pairs} <- validate_options(options), {:ok, valid_runner_option_pairs} <- validate_runner_options_pairs(runner_options_pairs), diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex index 1c2e2e8a628c..0db2d349a4a6 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex @@ -532,8 +532,10 @@ defmodule Explorer.Chain.Import.Runner.Blocks do select: map(ctb, [ :address_hash, + :block_number, :token_contract_address_hash, :token_id, + :token_type, # Used to determine if `address_hash` was a holder of `token_contract_address_hash` before # `address_current_token_balance` is deleted in `update_tokens_holder_count`. @@ -566,43 +568,28 @@ defmodule Explorer.Chain.Import.Runner.Blocks do %{timeout: timeout} = options ) when is_list(deleted_address_current_token_balances) do - final_query = derive_address_current_token_balances_grouped_query(deleted_address_current_token_balances) + new_current_token_balances_placeholders = + Enum.map(deleted_address_current_token_balances, fn deleted_balance -> + now = DateTime.utc_now() - new_current_token_balance_query = - from(new_current_token_balance in subquery(final_query), - inner_join: tb in Address.TokenBalance, - on: - tb.address_hash == new_current_token_balance.address_hash and - tb.token_contract_address_hash == new_current_token_balance.token_contract_address_hash and - ((is_nil(tb.token_id) and is_nil(new_current_token_balance.token_id)) or - (tb.token_id == new_current_token_balance.token_id and - not is_nil(tb.token_id) and not is_nil(new_current_token_balance.token_id))) and - tb.block_number == new_current_token_balance.block_number, - select: %{ - address_hash: new_current_token_balance.address_hash, - token_contract_address_hash: new_current_token_balance.token_contract_address_hash, - token_id: new_current_token_balance.token_id, - token_type: tb.token_type, - block_number: new_current_token_balance.block_number, - value: tb.value, - value_fetched_at: tb.value_fetched_at, - inserted_at: over(min(tb.inserted_at), :w), - updated_at: over(max(tb.updated_at), :w) - }, - windows: [ - w: [partition_by: [tb.address_hash, tb.token_contract_address_hash, tb.token_id]] - ] - ) - - current_token_balance = - new_current_token_balance_query - |> repo.all() + %{ + address_hash: deleted_balance.address_hash, + token_contract_address_hash: deleted_balance.token_contract_address_hash, + token_id: deleted_balance.token_id, + token_type: deleted_balance.token_type, + block_number: deleted_balance.block_number, + value: nil, + value_fetched_at: nil, + inserted_at: now, + updated_at: now + } + end) timestamps = Import.timestamps() result = CurrentTokenBalances.insert_changes_list_with_and_without_token_id( - current_token_balance, + new_current_token_balances_placeholders, repo, timestamps, timeout, @@ -787,43 +774,6 @@ defmodule Explorer.Chain.Import.Runner.Blocks do ) end - defp derive_address_current_token_balances_grouped_query(deleted_address_current_token_balances) do - initial_query = - from(tb in Address.TokenBalance, - select: %{ - address_hash: tb.address_hash, - token_contract_address_hash: tb.token_contract_address_hash, - token_id: tb.token_id, - block_number: max(tb.block_number) - }, - group_by: [tb.address_hash, tb.token_contract_address_hash, tb.token_id] - ) - - Enum.reduce(deleted_address_current_token_balances, initial_query, fn %{ - address_hash: address_hash, - token_contract_address_hash: - token_contract_address_hash, - token_id: token_id - }, - acc_query -> - if token_id do - from(tb in acc_query, - or_where: - tb.address_hash == ^address_hash and - tb.token_contract_address_hash == ^token_contract_address_hash and - tb.token_id == ^token_id - ) - else - from(tb in acc_query, - or_where: - tb.address_hash == ^address_hash and - tb.token_contract_address_hash == ^token_contract_address_hash and - is_nil(tb.token_id) - ) - end - end) - end - # `block_rewards` are linked to `blocks.hash`, but fetched by `blocks.number`, so when a block with the same number is # inserted, the old block rewards need to be deleted, so that the old and new rewards aren't combined. defp delete_rewards(repo, blocks_changes, %{timeout: timeout}) do diff --git a/apps/explorer/lib/explorer/chain/import/runner/zksync/batch_blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/zksync/batch_blocks.ex new file mode 100644 index 000000000000..33d075e93588 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/zksync/batch_blocks.ex @@ -0,0 +1,79 @@ +defmodule Explorer.Chain.Import.Runner.ZkSync.BatchBlocks do + @moduledoc """ + Bulk imports `t:Explorer.Chain.ZkSync.BatchBlock.t/0`. + """ + + require Ecto.Query + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.ZkSync.BatchBlock + alias Explorer.Prometheus.Instrumenter + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [BatchBlock.t()] + + @impl Import.Runner + def ecto_schema_module, do: BatchBlock + + @impl Import.Runner + def option_key, do: :zksync_batch_blocks + + @impl Import.Runner + @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()} + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + @spec run(Multi.t(), list(), map()) :: Multi.t() + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + Multi.run(multi, :insert_zksync_batch_blocks, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :zksync_batch_blocks, + :zksync_batch_blocks + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [BatchBlock.t()]} + | {:error, [Changeset.t()]} + def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = _options) when is_list(changes_list) do + # Enforce ZkSync.BatchBlock ShareLocks order (see docs: sharelock.md) + ordered_changes_list = Enum.sort_by(changes_list, & &1.hash) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + for: BatchBlock, + returning: true, + timeout: timeout, + timestamps: timestamps, + conflict_target: :hash, + on_conflict: :nothing + ) + + {:ok, inserted} + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/zksync/batch_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/zksync/batch_transactions.ex new file mode 100644 index 000000000000..720519a10093 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/zksync/batch_transactions.ex @@ -0,0 +1,79 @@ +defmodule Explorer.Chain.Import.Runner.ZkSync.BatchTransactions do + @moduledoc """ + Bulk imports `t:Explorer.Chain.ZkSync.BatchTransaction.t/0`. + """ + + require Ecto.Query + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.ZkSync.BatchTransaction + alias Explorer.Prometheus.Instrumenter + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [BatchTransaction.t()] + + @impl Import.Runner + def ecto_schema_module, do: BatchTransaction + + @impl Import.Runner + def option_key, do: :zksync_batch_transactions + + @impl Import.Runner + @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()} + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + @spec run(Multi.t(), list(), map()) :: Multi.t() + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + Multi.run(multi, :insert_zksync_batch_transactions, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :zksync_batch_transactions, + :zksync_batch_transactions + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [BatchTransaction.t()]} + | {:error, [Changeset.t()]} + def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = _options) when is_list(changes_list) do + # Enforce ZkSync.BatchTransaction ShareLocks order (see docs: sharelock.md) + ordered_changes_list = Enum.sort_by(changes_list, & &1.hash) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + for: BatchTransaction, + returning: true, + timeout: timeout, + timestamps: timestamps, + conflict_target: :hash, + on_conflict: :nothing + ) + + {:ok, inserted} + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/zksync/lifecycle_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/zksync/lifecycle_transactions.ex new file mode 100644 index 000000000000..b5b5e74ee89d --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/zksync/lifecycle_transactions.ex @@ -0,0 +1,103 @@ +defmodule Explorer.Chain.Import.Runner.ZkSync.LifecycleTransactions do + @moduledoc """ + Bulk imports `t:Explorer.Chain.ZkSync.LifecycleTransaction.t/0`. + """ + + require Ecto.Query + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.ZkSync.LifecycleTransaction + alias Explorer.Prometheus.Instrumenter + + import Ecto.Query, only: [from: 2] + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [LifecycleTransaction.t()] + + @impl Import.Runner + def ecto_schema_module, do: LifecycleTransaction + + @impl Import.Runner + def option_key, do: :zksync_lifecycle_transactions + + @impl Import.Runner + @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()} + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + @spec run(Multi.t(), list(), map()) :: Multi.t() + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + Multi.run(multi, :insert_zksync_lifecycle_transactions, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :zksync_lifecycle_transactions, + :zksync_lifecycle_transactions + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [LifecycleTransaction.t()]} + | {:error, [Changeset.t()]} + def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) + + # Enforce ZkSync.LifecycleTransaction ShareLocks order (see docs: sharelock.md) + ordered_changes_list = Enum.sort_by(changes_list, & &1.id) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + for: LifecycleTransaction, + returning: true, + timeout: timeout, + timestamps: timestamps, + conflict_target: :hash, + on_conflict: on_conflict + ) + + {:ok, inserted} + end + + defp default_on_conflict do + from( + tx in LifecycleTransaction, + update: [ + set: [ + # don't update `id` as it is a primary key + # don't update `hash` as it is a unique index and used for the conflict target + timestamp: fragment("EXCLUDED.timestamp"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", tx.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", tx.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.timestamp) IS DISTINCT FROM (?)", + tx.timestamp + ) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/zksync/transaction_batches.ex b/apps/explorer/lib/explorer/chain/import/runner/zksync/transaction_batches.ex new file mode 100644 index 000000000000..2c4639a43a63 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/zksync/transaction_batches.ex @@ -0,0 +1,122 @@ +defmodule Explorer.Chain.Import.Runner.ZkSync.TransactionBatches do + @moduledoc """ + Bulk imports `t:Explorer.Chain.ZkSync.TransactionBatch.t/0`. + """ + + require Ecto.Query + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.ZkSync.TransactionBatch + alias Explorer.Prometheus.Instrumenter + + import Ecto.Query, only: [from: 2] + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [TransactionBatch.t()] + + @impl Import.Runner + def ecto_schema_module, do: TransactionBatch + + @impl Import.Runner + def option_key, do: :zksync_transaction_batches + + @impl Import.Runner + @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()} + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + @spec run(Multi.t(), list(), map()) :: Multi.t() + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + Multi.run(multi, :insert_zksync_transaction_batches, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :zksync_transaction_batches, + :zksync_transaction_batches + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [TransactionBatch.t()]} + | {:error, [Changeset.t()]} + def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) + + # Enforce ZkSync.TransactionBatch ShareLocks order (see docs: sharelock.md) + ordered_changes_list = Enum.sort_by(changes_list, & &1.number) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + for: TransactionBatch, + returning: true, + timeout: timeout, + timestamps: timestamps, + conflict_target: :number, + on_conflict: on_conflict + ) + + {:ok, inserted} + end + + defp default_on_conflict do + from( + tb in TransactionBatch, + update: [ + set: [ + # don't update `number` as it is a primary key and used for the conflict target + timestamp: fragment("EXCLUDED.timestamp"), + l1_tx_count: fragment("EXCLUDED.l1_tx_count"), + l2_tx_count: fragment("EXCLUDED.l2_tx_count"), + root_hash: fragment("EXCLUDED.root_hash"), + l1_gas_price: fragment("EXCLUDED.l1_gas_price"), + l2_fair_gas_price: fragment("EXCLUDED.l2_fair_gas_price"), + start_block: fragment("EXCLUDED.start_block"), + end_block: fragment("EXCLUDED.end_block"), + commit_id: fragment("EXCLUDED.commit_id"), + prove_id: fragment("EXCLUDED.prove_id"), + execute_id: fragment("EXCLUDED.execute_id"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", tb.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", tb.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.timestamp, EXCLUDED.l1_tx_count, EXCLUDED.l2_tx_count, EXCLUDED.root_hash, EXCLUDED.l1_gas_price, EXCLUDED.l2_fair_gas_price, EXCLUDED.start_block, EXCLUDED.end_block, EXCLUDED.commit_id, EXCLUDED.prove_id, EXCLUDED.execute_id) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + tb.timestamp, + tb.l1_tx_count, + tb.l2_tx_count, + tb.root_hash, + tb.l1_gas_price, + tb.l2_fair_gas_price, + tb.start_block, + tb.end_block, + tb.commit_id, + tb.prove_id, + tb.execute_id + ) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex new file mode 100644 index 000000000000..72b62a2f9be1 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex @@ -0,0 +1,30 @@ +defmodule Explorer.Chain.Import.Stage.AddressReferencing do + @moduledoc """ + Imports any tables that reference `t:Explorer.Chain.Address.t/0` and that were imported by + `Explorer.Chain.Import.Stage.Addresses`. + """ + + alias Explorer.Chain.Import.{Runner, Stage} + + @behaviour Stage + + @impl Stage + def runners, + do: [ + Runner.Address.CoinBalances, + Runner.Blocks, + Runner.Address.CoinBalancesDaily + ] + + @impl Stage + def all_runners, + do: runners() + + @impl Stage + def multis(runner_to_changes_list, options) do + {final_multi, final_remaining_runner_to_changes_list} = + Stage.single_multi(runners(), runner_to_changes_list, options) + + {[final_multi], final_remaining_runner_to_changes_list} + end +end diff --git a/apps/explorer/lib/explorer/chain/import/stage/addresses.ex b/apps/explorer/lib/explorer/chain/import/stage/addresses.ex new file mode 100644 index 000000000000..fe91366bf74b --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/stage/addresses.ex @@ -0,0 +1,26 @@ +defmodule Explorer.Chain.Import.Stage.Addresses do + @moduledoc """ + Imports addresses before anything else that references them because an unused address is still valid and recoverable + if the other stage(s) don't commit. + """ + + alias Explorer.Chain.Import.{Runner, Stage} + + @behaviour Stage + + @runner Runner.Addresses + + @impl Stage + def runners, do: [@runner] + + @impl Stage + def all_runners, + do: runners() + + @chunk_size 50 + + @impl Stage + def multis(runner_to_changes_list, options) do + Stage.chunk_every(runner_to_changes_list, @runner, @chunk_size, options) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex index da6b01fd4d47..5f410f1f5a9d 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex @@ -43,6 +43,13 @@ defmodule Explorer.Chain.Import.Stage.BlockReferencing do Runner.PolygonZkevm.BridgeOperations ] + @zksync_runners [ + Runner.ZkSync.LifecycleTransactions, + Runner.ZkSync.TransactionBatches, + Runner.ZkSync.BatchTransactions, + Runner.ZkSync.BatchBlocks + ] + @shibarium_runners [ Runner.Shibarium.BridgeOperations ] @@ -69,6 +76,9 @@ defmodule Explorer.Chain.Import.Stage.BlockReferencing do "ethereum" -> @default_runners ++ @ethereum_runners + "zksync" -> + @default_runners ++ @zksync_runners + _ -> @default_runners end @@ -76,7 +86,8 @@ defmodule Explorer.Chain.Import.Stage.BlockReferencing do @impl Stage def all_runners do - @default_runners ++ @optimism_runners ++ @polygon_edge_runners ++ @polygon_zkevm_runners ++ @shibarium_runners + @default_runners ++ + @optimism_runners ++ @polygon_edge_runners ++ @polygon_zkevm_runners ++ @shibarium_runners ++ @zksync_runners end @impl Stage diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index cbd4e261ad67..8f168a4cd699 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -14,8 +14,9 @@ defmodule Explorer.Chain.Transaction.Schema do Wei } - alias Explorer.Chain.PolygonZkevm.BatchTransaction + alias Explorer.Chain.PolygonZkevm.BatchTransaction, as: ZkevmBatchTransaction alias Explorer.Chain.Transaction.{Fork, Status} + alias Explorer.Chain.ZkSync.BatchTransaction, as: ZkSyncBatchTransaction @chain_type_fields (case Application.compile_env(:explorer, :chain_type) do "ethereum" -> @@ -77,7 +78,11 @@ defmodule Explorer.Chain.Transaction.Schema do "polygon_zkevm" -> elem( quote do - has_one(:zkevm_batch_transaction, BatchTransaction, foreign_key: :hash, references: :hash) + has_one(:zkevm_batch_transaction, ZkevmBatchTransaction, + foreign_key: :hash, + references: :hash + ) + has_one(:zkevm_batch, through: [:zkevm_batch_transaction, :batch], references: :hash) has_one(:zkevm_sequence_transaction, @@ -93,6 +98,22 @@ defmodule Explorer.Chain.Transaction.Schema do 2 ) + "zksync" -> + elem( + quote do + has_one(:zksync_batch_transaction, ZkSyncBatchTransaction, + foreign_key: :hash, + references: :hash + ) + + has_one(:zksync_batch, through: [:zksync_batch_transaction, :batch]) + has_one(:zksync_commit_transaction, through: [:zksync_batch, :commit_transaction]) + has_one(:zksync_prove_transaction, through: [:zksync_batch, :prove_transaction]) + has_one(:zksync_execute_transaction, through: [:zksync_batch, :execute_transaction]) + end, + 2 + ) + _ -> [] end) @@ -195,7 +216,7 @@ defmodule Explorer.Chain.Transaction do alias ABI.FunctionSelector alias Ecto.Association.NotLoaded alias Ecto.Changeset - alias Explorer.{Chain, Helper, PagingOptions, Repo, SortingHelper} + alias Explorer.{Chain, PagingOptions, Repo, SortingHelper} alias Explorer.Chain.{ Block.Reward, @@ -203,10 +224,8 @@ defmodule Explorer.Chain.Transaction do Data, DenormalizationHelper, Hash, - Log, SmartContract, SmartContract.Proxy, - Token, TokenTransfer, Transaction, Wei @@ -216,12 +235,12 @@ defmodule Explorer.Chain.Transaction do @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 + to_address_hash revert_reason type has_error_in_internal_txs r s v)a @optimism_optional_attrs ~w(l1_fee l1_fee_scalar l1_gas_price l1_gas_used l1_tx_origin l1_block_number)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 - @required_attrs ~w(from_address_hash gas hash input nonce r s v value)a + @required_attrs ~w(from_address_hash gas hash input nonce value)a @empty_attrs ~w()a @@ -1155,98 +1174,6 @@ defmodule Explorer.Chain.Transaction do end end - @api_true [api?: true] - @transaction_fee_event_signature "0x99e7b0ba56da2819c37c047f0511fd2bf6c9b4e27b4a979a19d6da0f74be8155" - @transaction_fee_event_abi [ - %{ - "anonymous" => false, - "inputs" => [ - %{ - "indexed" => false, - "internalType" => "address", - "name" => "token", - "type" => "address" - }, - %{ - "indexed" => false, - "internalType" => "uint256", - "name" => "totalFee", - "type" => "uint256" - }, - %{ - "indexed" => false, - "internalType" => "address", - "name" => "validator", - "type" => "address" - }, - %{ - "indexed" => false, - "internalType" => "uint256", - "name" => "validatorFee", - "type" => "uint256" - }, - %{ - "indexed" => false, - "internalType" => "address", - "name" => "dapp", - "type" => "address" - }, - %{ - "indexed" => false, - "internalType" => "uint256", - "name" => "dappFee", - "type" => "uint256" - } - ], - "name" => "TransactionFee", - "type" => "event" - } - ] - - def maybe_prepare_stability_fees(transactions) do - if Application.get_env(:explorer, :chain_type) == "stability" do - maybe_prepare_stability_fees_inner(transactions) - else - transactions - end - end - - defp maybe_prepare_stability_fees_inner(transactions) when is_list(transactions) do - {transactions, _tokens_acc} = - Enum.map_reduce(transactions, %{}, fn transaction, tokens_acc -> - case Log.fetch_log_by_tx_hash_and_first_topic(transaction.hash, @transaction_fee_event_signature, @api_true) do - fee_log when not is_nil(fee_log) -> - {:ok, _selector, mapping} = Log.find_and_decode(@transaction_fee_event_abi, fee_log, transaction.hash) - - [{"token", "address", false, token_address_hash}, _, _, _, _, _] = mapping - - {token, new_tokens_acc} = check_tokens_acc(bytes_to_address_hash(token_address_hash), tokens_acc) - - {%Transaction{transaction | transaction_fee_log: mapping, transaction_fee_token: token}, new_tokens_acc} - - _ -> - {transaction, tokens_acc} - end - end) - - transactions - end - - defp maybe_prepare_stability_fees_inner(transaction) do - [transaction] = maybe_prepare_stability_fees_inner([transaction]) - transaction - end - - defp check_tokens_acc(token_address_hash, tokens_acc) do - if Map.has_key?(tokens_acc, token_address_hash) do - {tokens_acc[token_address_hash], tokens_acc} - else - token = Token.get_by_contract_address_hash(token_address_hash, @api_true) - - {token, Map.put(tokens_acc, token_address_hash, token)} - end - end - def bytes_to_address_hash(bytes), do: %Hash{byte_count: 20, bytes: bytes} @doc """ @@ -1689,50 +1616,6 @@ defmodule Explorer.Chain.Transaction do } end - @suave_bid_event "0x83481d5b04dea534715acad673a8177a46fc93882760f36bdc16ccac439d504e" - - @spec suave_parse_allowed_peekers(Ecto.Schema.has_many(Log.t())) :: [String.t()] - def suave_parse_allowed_peekers(%NotLoaded{}), do: [] - - def suave_parse_allowed_peekers(logs) do - suave_bid_contracts = - Application.get_all_env(:explorer)[Transaction][:suave_bid_contracts] - |> String.split(",") - |> Enum.map(fn sbc -> String.downcase(String.trim(sbc)) end) - - bid_event = - Enum.find(logs, fn log -> - sanitize_log_first_topic(log.first_topic) == @suave_bid_event && - Enum.member?(suave_bid_contracts, String.downcase(Hash.to_string(log.address_hash))) - end) - - if is_nil(bid_event) do - [] - else - [_bid_id, _decryption_condition, allowed_peekers] = - Helper.decode_data(bid_event.data, [{:bytes, 16}, {:uint, 64}, {:array, :address}]) - - Enum.map(allowed_peekers, fn peeker -> - "0x" <> Base.encode16(peeker, case: :lower) - end) - end - end - - defp sanitize_log_first_topic(first_topic) do - if is_nil(first_topic) do - "" - else - sanitized = - if is_binary(first_topic) do - first_topic - else - Hash.to_string(first_topic) - end - - String.downcase(sanitized) - end - end - @doc """ The fee a `transaction` paid for the `t:Explorer.Transaction.t/0` `gas` diff --git a/apps/explorer/lib/explorer/chain/zksync/batch_block.ex b/apps/explorer/lib/explorer/chain/zksync/batch_block.ex new file mode 100644 index 000000000000..08c9be6912d0 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/zksync/batch_block.ex @@ -0,0 +1,37 @@ +defmodule Explorer.Chain.ZkSync.BatchBlock do + @moduledoc "Models a list of blocks related to a batch for ZkSync." + + use Explorer.Schema + + alias Explorer.Chain.{Block, Hash} + alias Explorer.Chain.ZkSync.TransactionBatch + + @required_attrs ~w(batch_number hash)a + + @type t :: %__MODULE__{ + batch_number: non_neg_integer(), + batch: %Ecto.Association.NotLoaded{} | TransactionBatch.t() | nil, + hash: Hash.t(), + block: %Ecto.Association.NotLoaded{} | Block.t() | nil + } + + @primary_key false + schema "zksync_batch_l2_blocks" do + belongs_to(:batch, TransactionBatch, foreign_key: :batch_number, references: :number, type: :integer) + belongs_to(:block, Block, foreign_key: :hash, primary_key: true, references: :hash, type: Hash.Full) + + timestamps() + end + + @doc """ + Validates that the `attrs` are valid. + """ + @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() + def changeset(%__MODULE__{} = items, attrs \\ %{}) do + items + |> cast(attrs, @required_attrs) + |> validate_required(@required_attrs) + |> foreign_key_constraint(:batch_number) + |> unique_constraint(:hash) + end +end diff --git a/apps/explorer/lib/explorer/chain/zksync/batch_transaction.ex b/apps/explorer/lib/explorer/chain/zksync/batch_transaction.ex new file mode 100644 index 000000000000..ef3cfb0af8e5 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/zksync/batch_transaction.ex @@ -0,0 +1,37 @@ +defmodule Explorer.Chain.ZkSync.BatchTransaction do + @moduledoc "Models a list of transactions related to a batch for ZkSync." + + use Explorer.Schema + + alias Explorer.Chain.{Hash, Transaction} + alias Explorer.Chain.ZkSync.TransactionBatch + + @required_attrs ~w(batch_number hash)a + + @type t :: %__MODULE__{ + batch_number: non_neg_integer(), + batch: %Ecto.Association.NotLoaded{} | TransactionBatch.t() | nil, + hash: Hash.t(), + l2_transaction: %Ecto.Association.NotLoaded{} | Transaction.t() | nil + } + + @primary_key false + schema "zksync_batch_l2_transactions" do + belongs_to(:batch, TransactionBatch, foreign_key: :batch_number, references: :number, type: :integer) + belongs_to(:l2_transaction, Transaction, foreign_key: :hash, primary_key: true, references: :hash, type: Hash.Full) + + timestamps() + end + + @doc """ + Validates that the `attrs` are valid. + """ + @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() + def changeset(%__MODULE__{} = transactions, attrs \\ %{}) do + transactions + |> cast(attrs, @required_attrs) + |> validate_required(@required_attrs) + |> foreign_key_constraint(:batch_number) + |> unique_constraint(:hash) + end +end diff --git a/apps/explorer/lib/explorer/chain/zksync/lifecycle_transaction.ex b/apps/explorer/lib/explorer/chain/zksync/lifecycle_transaction.ex new file mode 100644 index 000000000000..cc2ec207a6a2 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/zksync/lifecycle_transaction.ex @@ -0,0 +1,38 @@ +defmodule Explorer.Chain.ZkSync.LifecycleTransaction do + @moduledoc "Models an L1 lifecycle transaction for ZkSync." + + use Explorer.Schema + + alias Explorer.Chain.Hash + alias Explorer.Chain.ZkSync.TransactionBatch + + @required_attrs ~w(id hash timestamp)a + + @type t :: %__MODULE__{ + hash: Hash.t(), + timestamp: DateTime.t() + } + + @primary_key {:id, :integer, autogenerate: false} + schema "zksync_lifecycle_l1_transactions" do + field(:hash, Hash.Full) + field(:timestamp, :utc_datetime_usec) + + has_many(:committed_batches, TransactionBatch, foreign_key: :commit_id) + has_many(:proven_batches, TransactionBatch, foreign_key: :prove_id) + has_many(:executed_batches, TransactionBatch, foreign_key: :execute_id) + + timestamps() + end + + @doc """ + Validates that the `attrs` are valid. + """ + @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() + def changeset(%__MODULE__{} = txn, attrs \\ %{}) do + txn + |> cast(attrs, @required_attrs) + |> validate_required(@required_attrs) + |> unique_constraint(:id) + end +end diff --git a/apps/explorer/lib/explorer/chain/zksync/reader.ex b/apps/explorer/lib/explorer/chain/zksync/reader.ex new file mode 100644 index 000000000000..2240cb23722e --- /dev/null +++ b/apps/explorer/lib/explorer/chain/zksync/reader.ex @@ -0,0 +1,339 @@ +defmodule Explorer.Chain.ZkSync.Reader do + @moduledoc "Contains read functions for zksync modules." + + import Ecto.Query, + only: [ + from: 2, + limit: 2, + order_by: 2, + where: 2, + where: 3 + ] + + import Explorer.Chain, only: [select_repo: 1] + + alias Explorer.Chain.ZkSync.{ + BatchTransaction, + LifecycleTransaction, + TransactionBatch + } + + alias Explorer.{Chain, PagingOptions, Repo} + + @doc """ + Receives total amount of batches imported to the `zksync_transaction_batches` table. + + ## Parameters + - `options`: passed to `Chain.select_repo()` + + ## Returns + Total amount of batches + """ + @spec batches_count(keyword()) :: any() + def batches_count(options) do + TransactionBatch + |> select_repo(options).aggregate(:count, timeout: :infinity) + end + + @doc """ + Receives the batch from the `zksync_transaction_batches` table by using its number or the latest batch if `:latest` is used. + + ## Parameters + - `number`: could be either the batch number or `:latest` to get the latest available in DB batch + - `options`: passed to `Chain.select_repo()` + + ## Returns + - `{:ok, Explorer.Chain.ZkSync.TransactionBatch}` if the batch found + - `{:error, :not_found}` if there is no batch with such number + """ + @spec batch(:latest | binary() | integer(), keyword()) :: + {:error, :not_found} | {:ok, Explorer.Chain.ZkSync.TransactionBatch} + def batch(number, options) + + def batch(:latest, options) when is_list(options) do + TransactionBatch + |> order_by(desc: :number) + |> limit(1) + |> select_repo(options).one() + |> case do + nil -> {:error, :not_found} + batch -> {:ok, batch} + end + end + + def batch(number, options) + when (is_integer(number) or is_binary(number)) and + is_list(options) do + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + + TransactionBatch + |> where(number: ^number) + |> Chain.join_associations(necessity_by_association) + |> select_repo(options).one() + |> case do + nil -> {:error, :not_found} + batch -> {:ok, batch} + end + end + + @doc """ + Receives a list of batches from the `zksync_transaction_batches` table within the range of batch numbers + + ## Parameters + - `start_number`: The start of the batch numbers range. + - `end_number`: The end of the batch numbers range. + - `options`: Options passed to `Chain.select_repo()`. + + ## Returns + - A list of `Explorer.Chain.ZkSync.TransactionBatch` if at least one batch exists within the range. + - An empty list (`[]`) if no batches within the range are found in the database. + """ + @spec batches(integer(), integer(), keyword()) :: [Explorer.Chain.ZkSync.TransactionBatch] + def batches(start_number, end_number, options) + when is_integer(start_number) and + is_integer(end_number) and + is_list(options) do + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + + base_query = from(tb in TransactionBatch, order_by: [desc: tb.number]) + + base_query + |> where([tb], tb.number >= ^start_number and tb.number <= ^end_number) + |> Chain.join_associations(necessity_by_association) + |> select_repo(options).all() + end + + @doc """ + Receives a list of batches from the `zksync_transaction_batches` table with the numbers defined in the input list. + + ## Parameters + - `numbers`: The list of batch numbers to retrieve from the database. + - `options`: Options passed to `Chain.select_repo()`. + + ## Returns + - A list of `Explorer.Chain.ZkSync.TransactionBatch` if at least one batch matches the numbers from the list. The output list could be less than the input list. + - An empty list (`[]`) if no batches with numbers from the list are found. + """ + @spec batches(maybe_improper_list(integer(), []), keyword()) :: [Explorer.Chain.ZkSync.TransactionBatch] + def batches(numbers, options) + when is_list(numbers) and + is_list(options) do + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + + base_query = from(tb in TransactionBatch, order_by: [desc: tb.number]) + + base_query + |> where([tb], tb.number in ^numbers) + |> Chain.join_associations(necessity_by_association) + |> select_repo(options).all() + end + + @doc """ + Receives a list of batches from the `zksync_transaction_batches` table. + + ## Parameters + - `options`: Options passed to `Chain.select_repo()`. (Optional) + + ## Returns + - If the option `confirmed?` is set, returns the ten latest committed batches (`Explorer.Chain.ZkSync.TransactionBatch`). + - Returns a list of `Explorer.Chain.ZkSync.TransactionBatch` based on the paging options if `confirmed?` is not set. + """ + @spec batches(keyword()) :: [Explorer.Chain.ZkSync.TransactionBatch] + @spec batches() :: [Explorer.Chain.ZkSync.TransactionBatch] + def batches(options \\ []) when is_list(options) do + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + + base_query = + from(tb in TransactionBatch, + order_by: [desc: tb.number] + ) + + query = + if Keyword.get(options, :confirmed?, false) do + base_query + |> Chain.join_associations(necessity_by_association) + |> where([tb], not is_nil(tb.commit_id) and tb.commit_id > 0) + |> limit(10) + else + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + + base_query + |> Chain.join_associations(necessity_by_association) + |> page_batches(paging_options) + |> limit(^paging_options.page_size) + end + + select_repo(options).all(query) + end + + @doc """ + Receives a list of transactions from the `zksync_batch_l2_transactions` table included in a specific batch. + + ## Parameters + - `batch_number`: The number of batch which transactions were included to L1 as part of. + - `options`: Options passed to `Chain.select_repo()`. (Optional) + + ## Returns + - A list of `Explorer.Chain.ZkSync.BatchTransaction` belonging to the specified batch. + """ + @spec batch_transactions(non_neg_integer()) :: [Explorer.Chain.ZkSync.BatchTransaction] + @spec batch_transactions(non_neg_integer(), keyword()) :: [Explorer.Chain.ZkSync.BatchTransaction] + def batch_transactions(batch_number, options \\ []) + when is_integer(batch_number) or + is_binary(batch_number) do + query = from(batch in BatchTransaction, where: batch.batch_number == ^batch_number) + + select_repo(options).all(query) + end + + @doc """ + Gets the number of the earliest batch in the `zksync_transaction_batches` table where the commitment transaction is not set. + Batch #0 is filtered out, as it does not have a linked commitment transaction. + + ## Returns + - The number of a batch if it exists, otherwise `nil`. `nil` could mean either no batches imported yet or all imported batches are marked as committed or Batch #0 is the only available batch. + """ + @spec earliest_sealed_batch_number() :: non_neg_integer() | nil + def earliest_sealed_batch_number do + query = + from(tb in TransactionBatch, + select: tb.number, + where: is_nil(tb.commit_id) and tb.number > 0, + order_by: [asc: tb.number], + limit: 1 + ) + + query + |> Repo.one() + end + + @doc """ + Gets the number of the earliest batch in the `zksync_transaction_batches` table where the proving transaction is not set. + Batch #0 is filtered out, as it does not have a linked proving transaction. + + ## Returns + - The number of a batch if it exists, otherwise `nil`. `nil` could mean either no batches imported yet or all imported batches are marked as proven or Batch #0 is the only available batch. + """ + @spec earliest_unproven_batch_number() :: non_neg_integer() | nil + def earliest_unproven_batch_number do + query = + from(tb in TransactionBatch, + select: tb.number, + where: is_nil(tb.prove_id) and tb.number > 0, + order_by: [asc: tb.number], + limit: 1 + ) + + query + |> Repo.one() + end + + @doc """ + Gets the number of the earliest batch in the `zksync_transaction_batches` table where the executing transaction is not set. + Batch #0 is filtered out, as it does not have a linked executing transaction. + + ## Returns + - The number of a batch if it exists, otherwise `nil`. `nil` could mean either no batches imported yet or all imported batches are marked as executed or Batch #0 is the only available batch. + """ + @spec earliest_unexecuted_batch_number() :: non_neg_integer() | nil + def earliest_unexecuted_batch_number do + query = + from(tb in TransactionBatch, + select: tb.number, + where: is_nil(tb.execute_id) and tb.number > 0, + order_by: [asc: tb.number], + limit: 1 + ) + + query + |> Repo.one() + end + + @doc """ + Gets the number of the oldest batch from the `zksync_transaction_batches` table. + + ## Returns + - The number of a batch if it exists, otherwise `nil`. `nil` means that there is no batches imported yet. + """ + @spec oldest_available_batch_number() :: non_neg_integer() | nil + def oldest_available_batch_number do + query = + from(tb in TransactionBatch, + select: tb.number, + order_by: [asc: tb.number], + limit: 1 + ) + + query + |> Repo.one() + end + + @doc """ + Gets the number of the youngest (the most recent) imported batch from the `zksync_transaction_batches` table. + + ## Returns + - The number of a batch if it exists, otherwise `nil`. `nil` means that there is no batches imported yet. + """ + @spec latest_available_batch_number() :: non_neg_integer() | nil + def latest_available_batch_number do + query = + from(tb in TransactionBatch, + select: tb.number, + order_by: [desc: tb.number], + limit: 1 + ) + + query + |> Repo.one() + end + + @doc """ + Reads a list of L1 transactions by their hashes from the `zksync_lifecycle_l1_transactions` table. + + ## Parameters + - `l1_tx_hashes`: A list of hashes to retrieve L1 transactions for. + + ## Returns + - A list of `Explorer.Chain.ZkSync.LifecycleTransaction` corresponding to the hashes from the input list. The output list may be smaller than the input list. + """ + @spec lifecycle_transactions(maybe_improper_list(binary(), [])) :: [Explorer.Chain.ZkSync.LifecycleTransaction] + def lifecycle_transactions(l1_tx_hashes) do + query = + from( + lt in LifecycleTransaction, + select: {lt.hash, lt.id}, + where: lt.hash in ^l1_tx_hashes + ) + + Repo.all(query, timeout: :infinity) + end + + @doc """ + Determines the next index for the L1 transaction available in the `zksync_lifecycle_l1_transactions` table. + + ## Returns + - The next available index. If there are no L1 transactions imported yet, it will return `1`. + """ + @spec next_id() :: non_neg_integer() + def next_id do + query = + from(lt in LifecycleTransaction, + select: lt.id, + order_by: [desc: lt.id], + limit: 1 + ) + + last_id = + query + |> Repo.one() + |> Kernel.||(0) + + last_id + 1 + end + + defp page_batches(query, %PagingOptions{key: nil}), do: query + + defp page_batches(query, %PagingOptions{key: {number}}) do + from(tb in query, where: tb.number < ^number) + end +end diff --git a/apps/explorer/lib/explorer/chain/zksync/transaction_batch.ex b/apps/explorer/lib/explorer/chain/zksync/transaction_batch.ex new file mode 100644 index 000000000000..3f6ac409cee3 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/zksync/transaction_batch.ex @@ -0,0 +1,83 @@ +defmodule Explorer.Chain.ZkSync.TransactionBatch do + @moduledoc "Models a batch of transactions for ZkSync." + + use Explorer.Schema + + alias Explorer.Chain.{ + Block, + Hash, + Wei + } + + alias Explorer.Chain.ZkSync.{BatchTransaction, LifecycleTransaction} + + @optional_attrs ~w(commit_id prove_id execute_id)a + + @required_attrs ~w(number timestamp l1_tx_count l2_tx_count root_hash l1_gas_price l2_fair_gas_price start_block end_block)a + + @type t :: %__MODULE__{ + number: non_neg_integer(), + timestamp: DateTime.t(), + l1_tx_count: non_neg_integer(), + l2_tx_count: non_neg_integer(), + root_hash: Hash.t(), + l1_gas_price: Wei.t(), + l2_fair_gas_price: Wei.t(), + start_block: Block.block_number(), + end_block: Block.block_number(), + commit_id: non_neg_integer() | nil, + commit_transaction: %Ecto.Association.NotLoaded{} | LifecycleTransaction.t() | nil, + prove_id: non_neg_integer() | nil, + prove_transaction: %Ecto.Association.NotLoaded{} | LifecycleTransaction.t() | nil, + execute_id: non_neg_integer() | nil, + execute_transaction: %Ecto.Association.NotLoaded{} | LifecycleTransaction.t() | nil + } + + @primary_key {:number, :integer, autogenerate: false} + schema "zksync_transaction_batches" do + field(:timestamp, :utc_datetime_usec) + field(:l1_tx_count, :integer) + field(:l2_tx_count, :integer) + field(:root_hash, Hash.Full) + field(:l1_gas_price, Wei) + field(:l2_fair_gas_price, Wei) + field(:start_block, :integer) + field(:end_block, :integer) + + belongs_to(:commit_transaction, LifecycleTransaction, + foreign_key: :commit_id, + references: :id, + type: :integer + ) + + belongs_to(:prove_transaction, LifecycleTransaction, + foreign_key: :prove_id, + references: :id, + type: :integer + ) + + belongs_to(:execute_transaction, LifecycleTransaction, + foreign_key: :execute_id, + references: :id, + type: :integer + ) + + has_many(:l2_transactions, BatchTransaction, foreign_key: :batch_number) + + timestamps() + end + + @doc """ + Validates that the `attrs` are valid. + """ + @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() + def changeset(%__MODULE__{} = batches, attrs \\ %{}) do + batches + |> cast(attrs, @required_attrs ++ @optional_attrs) + |> validate_required(@required_attrs) + |> foreign_key_constraint(:commit_id) + |> foreign_key_constraint(:prove_id) + |> foreign_key_constraint(:execute_id) + |> unique_constraint(:number) + end +end diff --git a/apps/explorer/lib/explorer/repo.ex b/apps/explorer/lib/explorer/repo.ex index 9dbbb9f9d40f..a1d4f35ad76d 100644 --- a/apps/explorer/lib/explorer/repo.ex +++ b/apps/explorer/lib/explorer/repo.ex @@ -181,6 +181,30 @@ defmodule Explorer.Repo do end end + defmodule ZkSync do + use Ecto.Repo, + otp_app: :explorer, + adapter: Ecto.Adapters.Postgres + + def init(_, opts) do + db_url = Application.get_env(:explorer, __MODULE__)[:url] + repo_conf = Application.get_env(:explorer, __MODULE__) + + merged = + %{url: db_url} + |> ConfigHelper.get_db_config() + |> Keyword.merge(repo_conf, fn + _key, v1, nil -> v1 + _key, nil, v2 -> v2 + _, _, v2 -> v2 + end) + + Application.put_env(:explorer, __MODULE__, merged) + + {:ok, Keyword.put(opts, :url, db_url)} + end + end + defmodule RSK do use Ecto.Repo, otp_app: :explorer, diff --git a/apps/explorer/priv/zk_sync/migrations/20211202082101_make_tranaction_r_s_v_optional.exs b/apps/explorer/priv/zk_sync/migrations/20211202082101_make_tranaction_r_s_v_optional.exs new file mode 100644 index 000000000000..7bf465fb5bda --- /dev/null +++ b/apps/explorer/priv/zk_sync/migrations/20211202082101_make_tranaction_r_s_v_optional.exs @@ -0,0 +1,17 @@ +defmodule Explorer.Repo.ZkSync.Migrations.MakeTransactionRSVOptional do + use Ecto.Migration + + def change do + alter table(:transactions) do + modify(:r, :numeric, precision: 100, null: true) + end + + alter table(:transactions) do + modify(:s, :numeric, precision: 100, null: true) + end + + alter table(:transactions) do + modify(:v, :numeric, precision: 100, null: true) + end + end +end diff --git a/apps/explorer/priv/zk_sync/migrations/20231213171043_create_zksync_tables.exs b/apps/explorer/priv/zk_sync/migrations/20231213171043_create_zksync_tables.exs new file mode 100644 index 000000000000..1e7d02c1d7c0 --- /dev/null +++ b/apps/explorer/priv/zk_sync/migrations/20231213171043_create_zksync_tables.exs @@ -0,0 +1,82 @@ +defmodule Explorer.Repo.ZkSync.Migrations.CreateZkSyncTables do + use Ecto.Migration + + def change do + create table(:zksync_lifecycle_l1_transactions, primary_key: false) do + add(:id, :integer, null: false, primary_key: true) + add(:hash, :bytea, null: false) + add(:timestamp, :"timestamp without time zone", null: false) + timestamps(null: false, type: :utc_datetime_usec) + end + + create(unique_index(:zksync_lifecycle_l1_transactions, :hash)) + + create table(:zksync_transaction_batches, primary_key: false) do + add(:number, :integer, null: false, primary_key: true) + add(:timestamp, :"timestamp without time zone", null: false) + add(:l1_tx_count, :integer, null: false) + add(:l2_tx_count, :integer, null: false) + add(:root_hash, :bytea, null: false) + add(:l1_gas_price, :numeric, precision: 100, null: false) + add(:l2_fair_gas_price, :numeric, precision: 100, null: false) + add(:start_block, :integer, null: false) + add(:end_block, :integer, null: false) + + add( + :commit_id, + references(:zksync_lifecycle_l1_transactions, on_delete: :restrict, on_update: :update_all, type: :integer), + null: true + ) + + add( + :prove_id, + references(:zksync_lifecycle_l1_transactions, on_delete: :restrict, on_update: :update_all, type: :integer), + null: true + ) + + add( + :execute_id, + references(:zksync_lifecycle_l1_transactions, on_delete: :restrict, on_update: :update_all, type: :integer), + null: true + ) + + timestamps(null: false, type: :utc_datetime_usec) + end + + create table(:zksync_batch_l2_transactions, primary_key: false) do + add( + :batch_number, + references(:zksync_transaction_batches, + column: :number, + on_delete: :delete_all, + on_update: :update_all, + type: :integer + ), + null: false + ) + + add(:hash, :bytea, null: false, primary_key: true) + timestamps(null: false, type: :utc_datetime_usec) + end + + create(index(:zksync_batch_l2_transactions, :batch_number)) + + create table(:zksync_batch_l2_blocks, primary_key: false) do + add( + :batch_number, + references(:zksync_transaction_batches, + column: :number, + on_delete: :delete_all, + on_update: :update_all, + type: :integer + ), + null: false + ) + + add(:hash, :bytea, null: false, primary_key: true) + timestamps(null: false, type: :utc_datetime_usec) + end + + create(index(:zksync_batch_l2_blocks, :batch_number)) + end +end diff --git a/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs b/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs index 0b78b53942d2..5d51bc760c1b 100644 --- a/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs @@ -99,7 +99,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do ] }} = run_block_consensus_change(block, true, options) - assert count(Address.CurrentTokenBalance) == 0 + assert %{value: nil} = Repo.one(Address.CurrentTokenBalance) end test "delete_address_current_token_balances does not delete rows with matching block number when consensus is false", @@ -118,100 +118,6 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do assert count(Address.CurrentTokenBalance) == count end - test "derive_address_current_token_balances inserts rows if there is an address_token_balance left for the rows deleted by delete_address_current_token_balances", - %{consensus_block: %{number: block_number} = block, options: options} do - token = insert(:token) - token_contract_address_hash = token.contract_address_hash - - %Address{hash: address_hash} = - insert_address_with_token_balances(%{ - previous: %{value: 1}, - current: %{block_number: block_number, value: 2}, - token_contract_address_hash: token_contract_address_hash - }) - - # Token must exist with non-`nil` `holder_count` for `blocks_update_token_holder_counts` to update - update_holder_count!(token_contract_address_hash, 1) - - assert count(Address.TokenBalance) == 2 - assert count(Address.CurrentTokenBalance) == 1 - - previous_block_number = block_number - 1 - - insert(:block, number: block_number, consensus: true) - - assert {:ok, - %{ - delete_address_current_token_balances: [ - %{ - address_hash: ^address_hash, - token_contract_address_hash: ^token_contract_address_hash - } - ], - delete_address_token_balances: [ - %{ - address_hash: ^address_hash, - token_contract_address_hash: ^token_contract_address_hash, - block_number: ^block_number - } - ], - derive_address_current_token_balances: [ - %{ - address_hash: ^address_hash, - token_contract_address_hash: ^token_contract_address_hash, - block_number: ^previous_block_number - } - ], - # no updates because it both deletes and derives a holder - blocks_update_token_holder_counts: [] - }} = run_block_consensus_change(block, true, options) - - assert count(Address.TokenBalance) == 1 - assert count(Address.CurrentTokenBalance) == 1 - - previous_value = Decimal.new(1) - - assert %Address.CurrentTokenBalance{block_number: ^previous_block_number, value: ^previous_value} = - Repo.get_by(Address.CurrentTokenBalance, - address_hash: address_hash, - token_contract_address_hash: token_contract_address_hash - ) - end - - test "a non-holder reverting to a holder increases the holder_count", - %{consensus_block: %{hash: block_hash, miner_hash: miner_hash, number: block_number}, options: options} do - token = insert(:token) - token_contract_address_hash = token.contract_address_hash - - non_holder_reverts_to_holder(%{ - current: %{block_number: block_number}, - token_contract_address_hash: token_contract_address_hash - }) - - # Token must exist with non-`nil` `holder_count` for `blocks_update_token_holder_counts` to update - update_holder_count!(token_contract_address_hash, 0) - - insert(:block, number: block_number, consensus: true) - - block_params = params_for(:block, hash: block_hash, miner_hash: miner_hash, number: block_number, consensus: true) - - %Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, block_params) - changes_list = [block_changes] - - assert {:ok, - %{ - blocks_update_token_holder_counts: [ - %{ - contract_address_hash: ^token_contract_address_hash, - holder_count: 1 - } - ] - }} = - Multi.new() - |> Blocks.run(changes_list, options) - |> Repo.transaction() - end - test "a holder reverting to a non-holder decreases the holder_count", %{consensus_block: %{hash: block_hash, miner_hash: miner_hash, number: block_number}, options: options} do token = insert(:token) diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs index fa16643507f8..484aca9f17c5 100644 --- a/apps/explorer/test/explorer/chain/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -2144,12 +2144,11 @@ defmodule Explorer.Chain.ImportTest do } }) - assert is_nil( + assert %{value: nil} = Repo.get_by(Address.CurrentTokenBalance, address_hash: address_hash, token_contract_address_hash: token_contract_address_hash ) - ) assert is_nil( Repo.get_by(Address.TokenBalance, @@ -2159,186 +2158,5 @@ defmodule Explorer.Chain.ImportTest do ) ) end - - test "address_current_token_balances is derived during reorgs" do - %Block{number: block_number} = insert(:block, consensus: true) - previous_block_number = block_number - 1 - - %Address.TokenBalance{ - address_hash: address_hash, - token_contract_address_hash: token_contract_address_hash, - value: previous_value, - block_number: previous_block_number - } = insert(:token_balance, block_number: previous_block_number) - - address = Repo.get(Address, address_hash) - - %Address.TokenBalance{ - address_hash: ^address_hash, - token_contract_address_hash: token_contract_address_hash, - value: current_value, - block_number: ^block_number - } = - insert(:token_balance, - address: address, - token_contract_address_hash: token_contract_address_hash, - block_number: block_number - ) - - refute current_value == previous_value - - %Address.CurrentTokenBalance{ - address_hash: ^address_hash, - token_contract_address_hash: ^token_contract_address_hash, - block_number: ^block_number - } = - insert(:address_current_token_balance, - address: address, - token_contract_address_hash: token_contract_address_hash, - block_number: block_number, - value: current_value - ) - - miner_hash_after = address_hash() - from_address_hash_after = address_hash() - block_hash_after = block_hash() - - assert {:ok, _} = - Import.all(%{ - addresses: %{ - params: [ - %{hash: miner_hash_after}, - %{hash: from_address_hash_after} - ] - }, - blocks: %{ - params: [ - %{ - consensus: true, - difficulty: 1, - gas_limit: 1, - gas_used: 1, - hash: block_hash_after, - miner_hash: miner_hash_after, - nonce: 1, - number: block_number, - parent_hash: block_hash(), - size: 1, - timestamp: Timex.parse!("2019-01-01T02:00:00Z", "{ISO:Extended:Z}"), - total_difficulty: 1 - } - ] - } - }) - - assert %Address.CurrentTokenBalance{block_number: ^previous_block_number, value: ^previous_value} = - Repo.get_by(Address.CurrentTokenBalance, - address_hash: address_hash, - token_contract_address_hash: token_contract_address_hash - ) - - assert is_nil( - Repo.get_by(Address.TokenBalance, - address_hash: address_hash, - token_contract_address_hash: token_contract_address_hash, - block_number: block_number - ) - ) - end - - test "address_token_balances and address_current_token_balances can be replaced during reorgs" do - %Block{number: block_number} = insert(:block, consensus: true) - value_before = Decimal.new(1) - - %Address{hash: address_hash} = address = insert(:address) - - %Address.TokenBalance{ - address_hash: ^address_hash, - token_contract_address_hash: token_contract_address_hash, - block_number: ^block_number - } = insert(:token_balance, address: address, block_number: block_number, value: value_before) - - %Address.CurrentTokenBalance{ - address_hash: ^address_hash, - token_contract_address_hash: ^token_contract_address_hash, - block_number: ^block_number - } = - insert(:address_current_token_balance, - address: address, - token_contract_address_hash: token_contract_address_hash, - block_number: block_number, - value: value_before - ) - - miner_hash_after = address_hash() - from_address_hash_after = address_hash() - block_hash_after = block_hash() - value_after = Decimal.add(value_before, 1) - - assert {:ok, _} = - Import.all(%{ - addresses: %{ - params: [ - %{hash: address_hash}, - %{hash: token_contract_address_hash}, - %{hash: miner_hash_after}, - %{hash: from_address_hash_after} - ] - }, - address_token_balances: %{ - params: [ - %{ - address_hash: address_hash, - token_contract_address_hash: token_contract_address_hash, - block_number: block_number, - value: value_after, - token_type: "ERC-20" - } - ] - }, - address_current_token_balances: %{ - params: [ - %{ - address_hash: address_hash, - token_contract_address_hash: token_contract_address_hash, - block_number: block_number, - value: value_after, - token_type: "ERC-20" - } - ] - }, - blocks: %{ - params: [ - %{ - consensus: true, - difficulty: 1, - gas_limit: 1, - gas_used: 1, - hash: block_hash_after, - miner_hash: miner_hash_after, - nonce: 1, - number: block_number, - parent_hash: block_hash(), - size: 1, - timestamp: Timex.parse!("2019-01-01T02:00:00Z", "{ISO:Extended:Z}"), - total_difficulty: 1 - } - ] - } - }) - - assert %Address.CurrentTokenBalance{value: ^value_after} = - Repo.get_by(Address.CurrentTokenBalance, - address_hash: address_hash, - token_contract_address_hash: token_contract_address_hash - ) - - assert %Address.TokenBalance{value: ^value_after} = - Repo.get_by(Address.TokenBalance, - address_hash: address_hash, - token_contract_address_hash: token_contract_address_hash, - block_number: block_number - ) - end end end diff --git a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex index 0f04d8302a19..834fca842b9b 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex @@ -155,6 +155,7 @@ defmodule Indexer.Fetcher.PolygonZkevm.TransactionBatch do end defp fetch_and_save_batches(batch_start, batch_end, json_rpc_named_arguments) do + # For every batch from batch_start to batch_end request the batch info requests = batch_start |> Range.new(batch_end, 1) @@ -171,6 +172,7 @@ defmodule Indexer.Fetcher.PolygonZkevm.TransactionBatch do {:ok, responses} = Helper.repeated_call(&json_rpc/2, [requests, json_rpc_named_arguments], error_message, 3) + # For every batch info extract batches' L1 sequence tx and L1 verify tx {sequence_hashes, verify_hashes} = responses |> Enum.reduce({[], []}, fn res, {sequences, verifies} = _acc -> @@ -194,8 +196,10 @@ defmodule Indexer.Fetcher.PolygonZkevm.TransactionBatch do {sequences, verifies} end) + # All L1 transactions in one list without repetition l1_tx_hashes = Enum.uniq(sequence_hashes ++ verify_hashes) + # Receive all IDs for L1 txs hash_to_id = l1_tx_hashes |> Reader.lifecycle_transactions() @@ -203,6 +207,7 @@ defmodule Indexer.Fetcher.PolygonZkevm.TransactionBatch do Map.put(acc, hash.bytes, id) end) + # For every batch build batch representation, collect associated L1 and L2 transactions {batches_to_import, l2_txs_to_import, l1_txs_to_import, _, _} = responses |> Enum.reduce({[], [], [], Reader.next_id(), hash_to_id}, fn res, @@ -222,16 +227,19 @@ defmodule Indexer.Fetcher.PolygonZkevm.TransactionBatch do acc_input_hash = Map.get(res.result, "accInputHash") state_root = Map.get(res.result, "stateRoot") + # Get ID for sequence transaction (new ID if the batch is just sequenced) {sequence_id, l1_txs, next_id, hash_to_id} = res.result |> get_tx_hash("sendSequencesTxHash") |> handle_tx_hash(hash_to_id, next_id, l1_txs, false) + # Get ID for verify transaction (new ID if the batch is just verified) {verify_id, l1_txs, next_id, hash_to_id} = res.result |> get_tx_hash("verifyBatchTxHash") |> handle_tx_hash(hash_to_id, next_id, l1_txs, true) + # Associate every transaction from batch with the batch number l2_txs_append = l2_transaction_hashes |> Kernel.||([]) @@ -256,6 +264,7 @@ defmodule Indexer.Fetcher.PolygonZkevm.TransactionBatch do {[batch | batches], l2_txs ++ l2_txs_append, l1_txs, next_id, hash_to_id} end) + # Update batches list, L1 transactions list and L2 transaction list {:ok, _} = Chain.import(%{ polygon_zkevm_lifecycle_transactions: %{params: l1_txs_to_import}, @@ -267,6 +276,7 @@ defmodule Indexer.Fetcher.PolygonZkevm.TransactionBatch do confirmed_batches = Enum.filter(batches_to_import, fn batch -> not is_nil(batch.sequence_id) and batch.sequence_id > 0 end) + # Publish update for open batches Views in BS app with the new confirmed batches if not Enum.empty?(confirmed_batches) do Publisher.broadcast([{:zkevm_confirmed_batches, confirmed_batches}], :realtime) end diff --git a/apps/indexer/lib/indexer/fetcher/zksync/batches_status_tracker.ex b/apps/indexer/lib/indexer/fetcher/zksync/batches_status_tracker.ex new file mode 100644 index 000000000000..74d7ec5b3bd8 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/zksync/batches_status_tracker.ex @@ -0,0 +1,242 @@ +defmodule Indexer.Fetcher.ZkSync.BatchesStatusTracker do + @moduledoc """ + Updates batches statuses and imports historical batches to the `zksync_transaction_batches` table. + + Repetitiveness is supported by sending the following statuses every `recheck_interval` seconds: + - `:check_committed`: Discover batches committed to L1 + - `:check_proven`: Discover batches proven in L1 + - `:check_executed`: Discover batches executed on L1 + - `:recover_batches`: Recover missed batches found during the handling of the three previous messages + - `:check_historical`: Check if the imported batches chain does not start with Batch #0 + + The initial message is `:check_committed`. If it is discovered that updating batches + in the `zksync_transaction_batches` table is not possible because some are missing, + `:recover_batches` is sent. The next messages are `:check_proven` and `:check_executed`. + Both could result in sending `:recover_batches` as well. + + The logic ensures that every handler emits the `:recover_batches` message to return to + the previous "progressing" state. If `:recover_batches` is called during handling `:check_committed`, + it will be sent again after finishing batch recovery. Similar logic applies to `:check_proven` and + `:check_executed`. + + The last message in the loop is `:check_historical`. + + |---------------------------------------------------------------------------| + |-> check_committed -> check_proven -> check_executed -> check_historical ->| + | ^ | ^ | ^ + v | v | v | + recover_batches recover_batches recover_batches + + If a batch status change is discovered during handling of `check_committed`, `check_proven`, + or `check_executed` messages, the corresponding L1 transactions are imported and associated + with the batches. Rollup transactions and blocks are not re-associated since it is assumed + to be done by `Indexer.Fetcher.ZkSync.TransactionBatch` or during handling of + the `recover_batches` message. + + The `recover_batches` handler downloads batch information from RPC and sets its actual L1 state + by linking with L1 transactions. + + The `check_historical` message initiates the check if the tail of the batch chain is Batch 0. + If the tail is missing, batches are downloaded from RPC in chunks of `batches_max_range` in every + iteration. The batches are imported together with associated L1 transactions. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + # alias Explorer.Chain.Events.Publisher + # TODO: publish event when new committed batches appear + + alias Indexer.Fetcher.ZkSync.Discovery.Workers + alias Indexer.Fetcher.ZkSync.StatusTracking.{Committed, Executed, Proven} + + def child_spec(start_link_arguments) do + spec = %{ + id: __MODULE__, + start: {__MODULE__, :start_link, start_link_arguments}, + restart: :transient, + type: :worker + } + + Supervisor.child_spec(spec, []) + end + + def start_link(args, gen_server_options \\ []) do + GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__)) + end + + @impl GenServer + def init(args) do + Logger.metadata(fetcher: :zksync_batches_tracker) + + config_tracker = Application.get_all_env(:indexer)[Indexer.Fetcher.ZkSync.BatchesStatusTracker] + l1_rpc = config_tracker[:zksync_l1_rpc] + recheck_interval = config_tracker[:recheck_interval] + config_fetcher = Application.get_all_env(:indexer)[Indexer.Fetcher.ZkSync.TransactionBatch] + chunk_size = config_fetcher[:chunk_size] + batches_max_range = config_fetcher[:batches_max_range] + + Process.send(self(), :check_committed, []) + + {:ok, + %{ + config: %{ + json_l2_rpc_named_arguments: args[:json_rpc_named_arguments], + json_l1_rpc_named_arguments: [ + transport: EthereumJSONRPC.HTTP, + transport_options: [ + http: EthereumJSONRPC.HTTP.HTTPoison, + url: l1_rpc, + http_options: [ + recv_timeout: :timer.minutes(10), + timeout: :timer.minutes(10), + hackney: [pool: :ethereum_jsonrpc] + ] + ] + ], + recheck_interval: recheck_interval, + chunk_size: chunk_size, + batches_max_range: batches_max_range + }, + data: %{} + }} + end + + @impl GenServer + def handle_info({ref, _result}, state) do + Process.demonitor(ref, [:flush]) + {:noreply, state} + end + + # Handles the `:check_historical` message to download historical batches from RPC if necessary and + # import them to the `zksync_transaction_batches` table. The batches are imported together with L1 + # transactions associations, rollup blocks and transactions. + # Since it is the final handler in the loop, it schedules sending the `:check_committed` message + # to initiate the next iteration. The sending of the message is delayed, taking into account + # the time remaining after the previous handlers' execution. + # + # ## Parameters + # - `:check_historical`: the message triggering the handler + # - `state`: current state of the fetcher containing both the fetcher configuration + # and data re-used by different handlers. + # + # ## Returns + # - `{:noreply, new_state}` where `new_state` contains `data` empty + @impl GenServer + def handle_info(:check_historical, state) + when is_map(state) and is_map_key(state, :config) and is_map_key(state, :data) and + is_map_key(state.config, :recheck_interval) and is_map_key(state.config, :batches_max_range) and + is_map_key(state.config, :json_l2_rpc_named_arguments) and + is_map_key(state.config, :chunk_size) do + {handle_duration, _} = + :timer.tc(&Workers.batches_catchup/1, [ + %{ + batches_max_range: state.config.batches_max_range, + chunk_size: state.config.chunk_size, + json_rpc_named_arguments: state.config.json_l2_rpc_named_arguments + } + ]) + + Process.send_after( + self(), + :check_committed, + max(:timer.seconds(state.config.recheck_interval) - div(update_duration(state.data, handle_duration), 1000), 0) + ) + + {:noreply, %{state | data: %{}}} + end + + # Handles the `:recover_batches` message to download a set of batches from RPC and imports them + # to the `zksync_transaction_batches` table. It is expected that the message is sent from handlers updating + # batches statuses when they discover the absence of batches in the `zksync_transaction_batches` table. + # The batches are imported together with L1 transactions associations, rollup blocks, and transactions. + # + # ## Parameters + # - `:recover_batches`: the message triggering the handler + # - `state`: current state of the fetcher containing both the fetcher configuration + # and data related to the batches recovery: + # - `state.data.batches`: list of the batches to recover + # - `state.data.switched_from`: the message to send after the batch recovery + # + # ## Returns + # - `{:noreply, new_state}` where `new_state` contains updated `duration` of the iteration + @impl GenServer + def handle_info(:recover_batches, state) + when is_map(state) and is_map_key(state, :config) and is_map_key(state, :data) and + is_map_key(state.config, :json_l2_rpc_named_arguments) and is_map_key(state.config, :chunk_size) and + is_map_key(state.data, :batches) and is_map_key(state.data, :switched_from) do + {handle_duration, _} = + :timer.tc( + &Workers.get_full_batches_info_and_import/2, + [ + state.data.batches, + %{ + chunk_size: state.config.chunk_size, + json_rpc_named_arguments: state.config.json_l2_rpc_named_arguments + } + ] + ) + + Process.send(self(), state.data.switched_from, []) + + {:noreply, %{state | data: %{duration: update_duration(state.data, handle_duration)}}} + end + + # Handles `:check_committed`, `:check_proven`, and `:check_executed` messages to update the + # statuses of batches by associating L1 transactions with them. For different messages, it invokes + # different underlying functions due to different natures of discovering batches with changed status. + # Another reason why statuses are being tracked differently is the different pace of status changes: + # a batch is committed in a few minutes after sealing, proven in a few hours, and executed once in a day. + # Depending on the value returned from the underlying function, either a message (`:check_proven`, + # `:check_executed`, or `:check_historical`) to switch to the next status checker is sent, or a list + # of batches to recover is provided together with `:recover_batches`. + # + # ## Parameters + # - `input`: one of `:check_committed`, `:check_proven`, and `:check_executed` + # - `state`: the current state of the fetcher containing both the fetcher configuration + # and data reused by different handlers. + # + # ## Returns + # - `{:noreply, new_state}` where `new_state` contains the updated `duration` of the iteration, + # could also contain the list of batches to recover and the message to return back to + # the corresponding status update checker. + @impl GenServer + def handle_info(input, state) + when input in [:check_committed, :check_proven, :check_executed] do + {output, func} = + case input do + :check_committed -> {:check_proven, &Committed.look_for_batches_and_update/1} + :check_proven -> {:check_executed, &Proven.look_for_batches_and_update/1} + :check_executed -> {:check_historical, &Executed.look_for_batches_and_update/1} + end + + {handle_duration, result} = :timer.tc(func, [state.config]) + + {switch_to, state_data} = + case result do + :ok -> + {output, %{duration: update_duration(state.data, handle_duration)}} + + {:recovery_required, batches} -> + {:recover_batches, + %{ + switched_from: input, + batches: batches, + duration: update_duration(state.data, handle_duration) + }} + end + + Process.send(self(), switch_to, []) + {:noreply, %{state | data: state_data}} + end + + defp update_duration(data, cur_duration) do + if Map.has_key?(data, :duration) do + data.duration + cur_duration + else + cur_duration + end + end +end diff --git a/apps/indexer/lib/indexer/fetcher/zksync/discovery/batches_data.ex b/apps/indexer/lib/indexer/fetcher/zksync/discovery/batches_data.ex new file mode 100644 index 000000000000..75b514ba74d0 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/zksync/discovery/batches_data.ex @@ -0,0 +1,413 @@ +defmodule Indexer.Fetcher.ZkSync.Discovery.BatchesData do + @moduledoc """ + Provides main functionality to extract data for batches and associated with them + rollup blocks, rollup and L1 transactions. + """ + + alias EthereumJSONRPC.Block.ByNumber + alias Indexer.Fetcher.ZkSync.Utils.Rpc + + import Indexer.Fetcher.ZkSync.Utils.Logging, only: [log_info: 1, log_details_chunk_handling: 4] + import EthereumJSONRPC, only: [quantity_to_integer: 1] + + @doc """ + Downloads batches, associates rollup blocks and transactions, and imports the results into the database. + Data is retrieved from the RPC endpoint in chunks of `chunk_size`. + + ## Parameters + - `batches`: Either a tuple of two integers, `start_batch_number` and `end_batch_number`, defining + the range of batches to receive, or a list of batch numbers, `batches_list`. + - `config`: Configuration containing `chunk_size` to limit the amount of data requested from the RPC endpoint, + and `json_rpc_named_arguments` defining parameters for the RPC connection. + + ## Returns + - `{batches_to_import, l2_blocks_to_import, l2_txs_to_import}` + where + - `batches_to_import` is a map of batches data + - `l2_blocks_to_import` is a list of blocks associated with batches by batch numbers + - `l2_txs_to_import` is a list of transactions associated with batches by batch numbers + """ + @spec extract_data_from_batches([integer()] | {integer(), integer()}, %{ + :chunk_size => pos_integer(), + :json_rpc_named_arguments => any(), + optional(any()) => any() + }) :: {map(), list(), list()} + def extract_data_from_batches(batches, config) + + def extract_data_from_batches({start_batch_number, end_batch_number}, config) + when is_integer(start_batch_number) and is_integer(end_batch_number) and + is_map(config) do + start_batch_number..end_batch_number + |> Enum.to_list() + |> do_extract_data_from_batches(config) + end + + def extract_data_from_batches(batches_list, config) + when is_list(batches_list) and + is_map(config) do + batches_list + |> do_extract_data_from_batches(config) + end + + defp do_extract_data_from_batches(batches_list, config) when is_list(batches_list) do + initial_batches_to_import = collect_batches_details(batches_list, config) + log_info("Collected details for #{length(Map.keys(initial_batches_to_import))} batches") + + batches_to_import = get_block_ranges(initial_batches_to_import, config) + + {l2_blocks_to_import, l2_txs_to_import} = get_l2_blocks_and_transactions(batches_to_import, config) + log_info("Linked #{length(l2_blocks_to_import)} L2 blocks and #{length(l2_txs_to_import)} L2 transactions") + + {batches_to_import, l2_blocks_to_import, l2_txs_to_import} + end + + @doc """ + Collects all unique L1 transactions from the given list of batches, including transactions + that change the status of a batch and their timestamps. + + **Note**: Every map describing an L1 transaction in the response is not ready for importing into + the database since it does not contain `:id` elements. + + ## Parameters + - `batches`: A list of maps describing batches. Each map is expected to define the following + elements: `commit_tx_hash`, `commit_timestamp`, `prove_tx_hash`, `prove_timestamp`, + `executed_tx_hash`, `executed_timestamp`. + + ## Returns + - `l1_txs`: A map where keys are L1 transaction hashes, and values are maps containing + transaction hashes and timestamps. + """ + @spec collect_l1_transactions(list()) :: map() + def collect_l1_transactions(batches) + when is_list(batches) do + l1_txs = + batches + |> Enum.reduce(%{}, fn batch, l1_txs -> + [ + %{hash: batch.commit_tx_hash, timestamp: batch.commit_timestamp}, + %{hash: batch.prove_tx_hash, timestamp: batch.prove_timestamp}, + %{hash: batch.executed_tx_hash, timestamp: batch.executed_timestamp} + ] + |> Enum.reduce(l1_txs, fn l1_tx, acc -> + # checks if l1_tx is not empty and adds to acc + add_l1_tx_to_list(acc, l1_tx) + end) + end) + + log_info("Collected #{length(Map.keys(l1_txs))} L1 hashes") + + l1_txs + end + + defp add_l1_tx_to_list(l1_txs, l1_tx) do + if l1_tx.hash != Rpc.get_binary_zero_hash() do + Map.put(l1_txs, l1_tx.hash, l1_tx) + else + l1_txs + end + end + + # Divides the list of batch numbers into chunks of size `chunk_size` to combine + # `zks_getL1BatchDetails` calls in one chunk together. To simplify further handling, + # each call is combined with the batch number in the JSON request identifier field. + # This allows parsing and associating every response with a particular batch, producing + # a list of maps describing the batches, ready for further handling. + # + # **Note**: The batches in the resulting map are not ready for importing into the DB. L1 transaction + # indices as well as the rollup blocks range must be added, and then batch descriptions + # must be pruned (see Indexer.Fetcher.ZkSync.Utils.Db.prune_json_batch/1). + # + # ## Parameters + # - `batches_list`: A list of batch numbers. + # - `config`: A map containing `chunk_size` specifying the number of `zks_getL1BatchDetails` in + # one HTTP request, and `json_rpc_named_arguments` describing parameters for + # RPC connection. + # + # ## Returns + # - `batches_details`: A map where keys are batch numbers, and values are maps produced + # after parsing responses of `zks_getL1BatchDetails` calls. + defp collect_batches_details( + batches_list, + %{json_rpc_named_arguments: json_rpc_named_arguments, chunk_size: chunk_size} = _config + ) + when is_list(batches_list) do + batches_list_length = length(batches_list) + + {batches_details, _} = + batches_list + |> Enum.chunk_every(chunk_size) + |> Enum.reduce({%{}, 0}, fn chunk, {details, a} -> + log_details_chunk_handling("Collecting details", chunk, a * chunk_size, batches_list_length) + + requests = + chunk + |> Enum.map(fn batch_number -> + EthereumJSONRPC.request(%{ + id: batch_number, + method: "zks_getL1BatchDetails", + params: [batch_number] + }) + end) + + details = + requests + |> Rpc.fetch_batches_details(json_rpc_named_arguments) + |> Enum.reduce( + details, + fn resp, details -> + Map.put(details, resp.id, Rpc.transform_batch_details_to_map(resp.result)) + end + ) + + {details, a + 1} + end) + + batches_details + end + + # Extends each batch description with the block numbers specifying the start and end of + # a range of blocks included in the batch. The block ranges are obtained through the RPC call + # `zks_getL1BatchBlockRange`. The calls are combined in chunks of `chunk_size`. To distinguish + # each call in the chunk, they are combined with the batch number in the JSON request + # identifier field. + # + # ## Parameters + # - `batches`: A map of batch descriptions. + # - `config`: A map containing `chunk_size`, specifying the number of `zks_getL1BatchBlockRange` + # in one HTTP request, and `json_rpc_named_arguments` describing parameters for + # RPC connection. + # + # ## Returns + # - `updated_batches`: A map of batch descriptions where each description is updated with + # a range (elements `:start_block` and `:end_block`) of rollup blocks included in the batch. + defp get_block_ranges( + batches, + %{json_rpc_named_arguments: json_rpc_named_arguments, chunk_size: chunk_size} = _config + ) + when is_map(batches) do + keys = Map.keys(batches) + batches_list_length = length(keys) + + {updated_batches, _} = + keys + |> Enum.chunk_every(chunk_size) + |> Enum.reduce({batches, 0}, fn batches_chunk, {batches_with_block_ranges, a} -> + log_details_chunk_handling("Collecting block ranges", batches_chunk, a * chunk_size, batches_list_length) + + {request_block_ranges_for_batches(batches_chunk, batches, batches_with_block_ranges, json_rpc_named_arguments), + a + 1} + end) + + updated_batches + end + + # For a given list of rollup batch numbers, this function builds a list of requests + # to `zks_getL1BatchBlockRange`, executes them, and extends the batches' descriptions with + # ranges of rollup blocks associated with each batch. + # + # ## Parameters + # - `batches_numbers`: A list with batch numbers. + # - `batches_src`: A list containing original batches descriptions. + # - `batches_dst`: A map with extended batch descriptions containing rollup block ranges. + # - `json_rpc_named_arguments`: Describes parameters for RPC connection. + # + # ## Returns + # - An updated version of `batches_dst` with new entities containing rollup block ranges. + defp request_block_ranges_for_batches(batches_numbers, batches_src, batches_dst, json_rpc_named_arguments) do + batches_numbers + |> Enum.reduce([], fn batch_number, requests -> + batch = Map.get(batches_src, batch_number) + # Prepare requests list to get blocks ranges + case is_nil(batch.start_block) or is_nil(batch.end_block) do + true -> + [ + EthereumJSONRPC.request(%{ + id: batch_number, + method: "zks_getL1BatchBlockRange", + params: [batch_number] + }) + | requests + ] + + false -> + requests + end + end) + |> Rpc.fetch_blocks_ranges(json_rpc_named_arguments) + |> Enum.reduce(batches_dst, fn resp, updated_batches -> + Map.update!(updated_batches, resp.id, fn batch -> + [start_block, end_block] = resp.result + + Map.merge(batch, %{ + start_block: quantity_to_integer(start_block), + end_block: quantity_to_integer(end_block) + }) + end) + end) + end + + # Unfolds the ranges of rollup blocks in each batch description, makes RPC `eth_getBlockByNumber` calls, + # and builds two lists: a list of rollup blocks associated with each batch and a list of rollup transactions + # associated with each batch. RPC calls are made in chunks of `chunk_size`. To distinguish + # each call in the chunk, they are combined with the block number in the JSON request + # identifier field. + # + # ## Parameters + # - `batches`: A map of batch descriptions. Each description must contain `start_block` and + # `end_block`, specifying the range of blocks associated with the batch. + # - `config`: A map containing `chunk_size`, specifying the number of `eth_getBlockByNumber` + # in one HTTP request, and `json_rpc_named_arguments` describing parameters for + # RPC connection. + # + # ## Returns + # - {l2_blocks_to_import, l2_txs_to_import}, where + # - `l2_blocks_to_import` contains a list of all rollup blocks with their associations with + # the provided batches. The association is a map with the block hash and the batch number. + # - `l2_txs_to_import` contains a list of all rollup transactions with their associations + # with the provided batches. The association is a map with the transaction hash and + # the batch number. + defp get_l2_blocks_and_transactions( + batches, + %{json_rpc_named_arguments: json_rpc_named_arguments, chunk_size: chunk_size} = _config + ) do + # Extracts the rollup block range for every batch, unfolds it and + # build chunks of `eth_getBlockByNumber` calls + {blocks_to_batches, chunked_requests, cur_chunk, cur_chunk_size} = + batches + |> Map.keys() + |> Enum.reduce({%{}, [], [], 0}, fn batch_number, cur_batch_acc -> + batch = Map.get(batches, batch_number) + + batch.start_block..batch.end_block + |> Enum.chunk_every(chunk_size) + |> Enum.reduce(cur_batch_acc, fn blocks_range, cur_chunk_acc -> + build_blocks_map_and_chunks_of_rpc_requests(batch_number, blocks_range, cur_chunk_acc, chunk_size) + end) + end) + + # After the last iteration of the reduce loop it is a valid case + # when the calls from the last chunk are not in the chunks list, + # so it is appended + finalized_chunked_requests = + if cur_chunk_size > 0 do + [cur_chunk | chunked_requests] + else + chunked_requests + end + + # The chunks requests are sent to the RPC node and parsed to + # extract rollup block hashes and rollup transactions. + {blocks_associations, l2_txs_to_import} = + finalized_chunked_requests + |> Enum.reduce({blocks_to_batches, []}, fn requests, {blocks, l2_txs} -> + requests + |> Rpc.fetch_blocks_details(json_rpc_named_arguments) + |> extract_block_hash_and_transactions_list(blocks, l2_txs) + end) + + # Check that amount of received transactions for a batch is correct + batches + |> Map.keys() + |> Enum.each(fn batch_number -> + batch = Map.get(batches, batch_number) + txs_in_batch = batch.l1_tx_count + batch.l2_tx_count + + ^txs_in_batch = + Enum.count(l2_txs_to_import, fn tx -> + tx.batch_number == batch_number + end) + end) + + {Map.values(blocks_associations), l2_txs_to_import} + end + + # For a given list of rollup block numbers, this function extends: + # - a map containing the linkage between rollup block numbers and batch numbers + # - a list of chunks of `eth_getBlockByNumber` requests + # - an uncompleted chunk of `eth_getBlockByNumber` requests + # + # ## Parameters + # - `batch_number`: The number of the batch to which the list of rollup blocks is linked. + # - `blocks_numbers`: A list of rollup block numbers. + # - `cur_chunk_acc`: The current state of the accumulator containing: + # - the current state of the map containing the linkage between rollup block numbers and batch numbers + # - the current state of the list of chunks of `eth_getBlockByNumber` requests + # - the current state of the uncompleted chunk of `eth_getBlockByNumber` requests + # - the size of the uncompleted chunk + # - `chunk_size`: The maximum size of the chunk of `eth_getBlockByNumber` requests + # + # ## Returns + # - {blocks_to_batches, chunked_requests, cur_chunk, cur_chunk_size}, where: + # - `blocks_to_batches`: An updated map with new blocks added. + # - `chunked_requests`: An updated list of lists of `eth_getBlockByNumber` requests. + # - `cur_chunk`: An uncompleted chunk of `eth_getBlockByNumber` requests or an empty list. + # - `cur_chunk_size`: The size of the uncompleted chunk. + defp build_blocks_map_and_chunks_of_rpc_requests(batch_number, blocks_numbers, cur_chunk_acc, chunk_size) do + blocks_numbers + |> Enum.reduce(cur_chunk_acc, fn block_number, {blocks_to_batches, chunked_requests, cur_chunk, cur_chunk_size} -> + blocks_to_batches = Map.put(blocks_to_batches, block_number, %{batch_number: batch_number}) + + cur_chunk = [ + ByNumber.request( + %{ + id: block_number, + number: block_number + }, + false + ) + | cur_chunk + ] + + if cur_chunk_size + 1 == chunk_size do + {blocks_to_batches, [cur_chunk | chunked_requests], [], 0} + else + {blocks_to_batches, chunked_requests, cur_chunk, cur_chunk_size + 1} + end + end) + end + + # Parses responses from `eth_getBlockByNumber` calls and extracts the block hash and the + # transactions lists. The block hash and transaction hashes are used to build associations + # with the corresponding batches by utilizing their numbers. + # + # This function is not part of the `Indexer.Fetcher.ZkSync.Utils.Rpc` module since the resulting + # lists are too specific for further import to the database. + # + # ## Parameters + # - `json_responses`: A list of responses to `eth_getBlockByNumber` calls. + # - `l2_blocks`: A map of accumulated associations between rollup blocks and batches. + # - `l2_txs`: A list of accumulated associations between rollup transactions and batches. + # + # ## Returns + # - {l2_blocks, l2_txs}, where + # - `l2_blocks`: Updated map of accumulated associations between rollup blocks and batches. + # - `l2_txs`: Updated list of accumulated associations between rollup transactions and batches. + defp extract_block_hash_and_transactions_list(json_responses, l2_blocks, l2_txs) do + json_responses + |> Enum.reduce({l2_blocks, l2_txs}, fn resp, {l2_blocks, l2_txs} -> + {block, l2_blocks} = + Map.get_and_update(l2_blocks, resp.id, fn block -> + {block, Map.put(block, :hash, Map.get(resp.result, "hash"))} + end) + + l2_txs = + case Map.get(resp.result, "transactions") do + nil -> + l2_txs + + new_txs -> + Enum.reduce(new_txs, l2_txs, fn l2_tx_hash, l2_txs -> + [ + %{ + batch_number: block.batch_number, + hash: l2_tx_hash + } + | l2_txs + ] + end) + end + + {l2_blocks, l2_txs} + end) + end +end diff --git a/apps/indexer/lib/indexer/fetcher/zksync/discovery/workers.ex b/apps/indexer/lib/indexer/fetcher/zksync/discovery/workers.ex new file mode 100644 index 000000000000..43ad89b7f124 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/zksync/discovery/workers.ex @@ -0,0 +1,163 @@ +defmodule Indexer.Fetcher.ZkSync.Discovery.Workers do + @moduledoc """ + Provides functions to download a set of batches from RPC and import them to DB. + """ + + alias Indexer.Fetcher.ZkSync.Utils.Db + + import Indexer.Fetcher.ZkSync.Discovery.BatchesData, + only: [ + collect_l1_transactions: 1, + extract_data_from_batches: 2 + ] + + import Indexer.Fetcher.ZkSync.Utils.Logging, only: [log_info: 1] + + @doc """ + Downloads minimal batches data (batch, associated rollup blocks and transactions hashes) from RPC + and imports them to the DB. Data is retrieved from the RPC endpoint in chunks of `chunk_size`. + Import of associated L1 transactions does not happen, assuming that the batch import happens regularly + enough and last downloaded batches does not contain L1 associations anyway. + Later `Indexer.Fetcher.ZkSync.BatchesStatusTracker` will update any batch state changes and + import required L1 transactions. + + ## Parameters + - `start_batch_number`: The first batch in the range to download. + - `end_batch_number`: The last batch in the range to download. + - `config`: Configuration containing `chunk_size` to limit the amount of data requested from the RPC endpoint, + and `json_rpc_named_arguments` defining parameters for the RPC connection. + + ## Returns + - `:ok` + """ + @spec get_minimal_batches_info_and_import(non_neg_integer(), non_neg_integer(), %{ + :chunk_size => integer(), + :json_rpc_named_arguments => EthereumJSONRPC.json_rpc_named_arguments(), + optional(any()) => any() + }) :: :ok + def get_minimal_batches_info_and_import(start_batch_number, end_batch_number, config) + when is_integer(start_batch_number) and + is_integer(end_batch_number) and + (is_map(config) and is_map_key(config, :json_rpc_named_arguments) and + is_map_key(config, :chunk_size)) do + {batches_to_import, l2_blocks_to_import, l2_txs_to_import} = + extract_data_from_batches({start_batch_number, end_batch_number}, config) + + batches_list_to_import = + batches_to_import + |> Map.values() + |> Enum.reduce([], fn batch, batches_list -> + [Db.prune_json_batch(batch) | batches_list] + end) + + Db.import_to_db( + batches_list_to_import, + [], + l2_txs_to_import, + l2_blocks_to_import + ) + + :ok + end + + @doc """ + Downloads batches, associates L1 transactions, rollup blocks and transactions with the given list of batch numbers, + and imports the results into the database. Data is retrieved from the RPC endpoint in chunks of `chunk_size`. + + ## Parameters + - `batches_numbers_list`: List of batch numbers to be retrieved. + - `config`: Configuration containing `chunk_size` to limit the amount of data requested from the RPC endpoint, + and `json_rpc_named_arguments` defining parameters for the RPC connection. + + ## Returns + - `:ok` + """ + @spec get_full_batches_info_and_import([integer()], %{ + :chunk_size => integer(), + :json_rpc_named_arguments => EthereumJSONRPC.json_rpc_named_arguments(), + optional(any()) => any() + }) :: :ok + def get_full_batches_info_and_import(batches_numbers_list, config) + when is_list(batches_numbers_list) and + (is_map(config) and is_map_key(config, :json_rpc_named_arguments) and + is_map_key(config, :chunk_size)) do + # Collect batches and linked L2 blocks and transaction + {batches_to_import, l2_blocks_to_import, l2_txs_to_import} = extract_data_from_batches(batches_numbers_list, config) + + # Collect L1 transactions associated with batches + l1_txs = + batches_to_import + |> Map.values() + |> collect_l1_transactions() + |> Db.get_indices_for_l1_transactions() + + # Update batches with l1 transactions indices and prune unnecessary fields + batches_list_to_import = + batches_to_import + |> Map.values() + |> Enum.reduce([], fn batch, batches -> + [ + batch + |> Map.put(:commit_id, get_l1_tx_id_by_hash(l1_txs, batch.commit_tx_hash)) + |> Map.put(:prove_id, get_l1_tx_id_by_hash(l1_txs, batch.prove_tx_hash)) + |> Map.put(:execute_id, get_l1_tx_id_by_hash(l1_txs, batch.executed_tx_hash)) + |> Db.prune_json_batch() + | batches + ] + end) + + Db.import_to_db( + batches_list_to_import, + Map.values(l1_txs), + l2_txs_to_import, + l2_blocks_to_import + ) + + :ok + end + + @doc """ + Retrieves the minimal batch number from the database. If the minimum batch number is not zero, + downloads `batches_max_range` batches older than the retrieved batch, along with associated + L1 transactions, rollup blocks, and transactions, and imports everything to the database. + + ## Parameters + - `config`: Configuration containing `chunk_size` to limit the amount of data requested from + the RPC endpoint and `json_rpc_named_arguments` defining parameters for the + RPC connection, `batches_max_range` defines how many of older batches must be downloaded. + + ## Returns + - `:ok` + """ + @spec batches_catchup(%{ + :batches_max_range => integer(), + :chunk_size => integer(), + :json_rpc_named_arguments => EthereumJSONRPC.json_rpc_named_arguments(), + optional(any()) => any() + }) :: :ok + def batches_catchup(config) + when is_map(config) and is_map_key(config, :json_rpc_named_arguments) and + is_map_key(config, :batches_max_range) and + is_map_key(config, :chunk_size) do + oldest_batch_number = Db.get_earliest_batch_number() + + if not is_nil(oldest_batch_number) && oldest_batch_number > 0 do + log_info("The oldest batch number is not zero. Historical baches will be fetched.") + start_batch_number = max(0, oldest_batch_number - config.batches_max_range) + end_batch_number = oldest_batch_number - 1 + + start_batch_number..end_batch_number + |> Enum.to_list() + |> get_full_batches_info_and_import(config) + end + + :ok + end + + defp get_l1_tx_id_by_hash(l1_txs, hash) do + l1_txs + |> Map.get(hash) + |> Kernel.||(%{id: nil}) + |> Map.get(:id) + end +end diff --git a/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/committed.ex b/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/committed.ex new file mode 100644 index 000000000000..ed1a0464b63c --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/committed.ex @@ -0,0 +1,78 @@ +defmodule Indexer.Fetcher.ZkSync.StatusTracking.Committed do + @moduledoc """ + Functionality to discover committed batches + """ + + alias Indexer.Fetcher.ZkSync.Utils.{Db, Rpc} + + import Indexer.Fetcher.ZkSync.StatusTracking.CommonUtils, + only: [ + check_if_batch_status_changed: 3, + associate_and_import_or_prepare_for_recovery: 4 + ] + + import Indexer.Fetcher.ZkSync.Utils.Logging, only: [log_info: 1] + + # keccak256("BlockCommit(uint256,bytes32,bytes32)") + @block_commit_event "0x8f2916b2f2d78cc5890ead36c06c0f6d5d112c7e103589947e8e2f0d6eddb763" + + @doc """ + Checks if the oldest uncommitted batch in the database has the associated L1 commitment transaction + by requesting new batch details from RPC. If so, analyzes the `BlockCommit` event emitted by + the transaction to explore all the batches committed by it. For all discovered batches, it updates + the database with new associations, importing information about L1 transactions. + If it is found that some of the discovered batches are absent in the database, the function + interrupts and returns the list of batch numbers that can be attempted to be recovered. + + ## Parameters + - `config`: Configuration containing `json_l1_rpc_named_arguments` and + `json_l2_rpc_named_arguments` defining parameters for the RPC connections. + + ## Returns + - `:ok` if no new committed batches are found, or if all found batches and the corresponding L1 + transactions are imported successfully. + - `{:recovery_required, batches_to_recover}` if the absence of new committed batches is + discovered; `batches_to_recover` contains the list of batch numbers. + """ + @spec look_for_batches_and_update(%{ + :json_l1_rpc_named_arguments => EthereumJSONRPC.json_rpc_named_arguments(), + :json_l2_rpc_named_arguments => EthereumJSONRPC.json_rpc_named_arguments(), + optional(any()) => any() + }) :: :ok | {:recovery_required, list()} + def look_for_batches_and_update( + %{ + json_l1_rpc_named_arguments: json_l1_rpc_named_arguments, + json_l2_rpc_named_arguments: json_l2_rpc_named_arguments + } = _config + ) do + case Db.get_earliest_sealed_batch_number() do + nil -> + :ok + + expected_batch_number -> + log_info("Checking if the batch #{expected_batch_number} was committed") + + {next_action, tx_hash, l1_txs} = + check_if_batch_status_changed(expected_batch_number, :commit_tx, json_l2_rpc_named_arguments) + + case next_action do + :skip -> + :ok + + :look_for_batches -> + log_info("The batch #{expected_batch_number} looks like committed") + commit_tx_receipt = Rpc.fetch_tx_receipt_by_hash(tx_hash, json_l1_rpc_named_arguments) + batches_numbers_from_rpc = get_committed_batches_from_logs(commit_tx_receipt["logs"]) + + associate_and_import_or_prepare_for_recovery(batches_numbers_from_rpc, l1_txs, tx_hash, :commit_id) + end + end + end + + defp get_committed_batches_from_logs(logs) do + committed_batches = Rpc.filter_logs_and_extract_topic_at(logs, @block_commit_event, 1) + log_info("Discovered #{length(committed_batches)} committed batches in the commitment tx") + + committed_batches + end +end diff --git a/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/common.ex b/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/common.ex new file mode 100644 index 000000000000..0c8cccffc30d --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/common.ex @@ -0,0 +1,173 @@ +defmodule Indexer.Fetcher.ZkSync.StatusTracking.CommonUtils do + @moduledoc """ + Common functions for status changes trackers + """ + + alias Explorer.Chain.ZkSync.Reader + alias Indexer.Fetcher.ZkSync.Utils.{Db, Rpc} + import Indexer.Fetcher.ZkSync.Utils.Logging, only: [log_warning: 1] + + @doc """ + Fetches the details of the batch with the given number and checks if the representation of + the same batch in the database refers to the same commitment, proving, or executing transaction + depending on `tx_type`. If the transaction state changes, the new transaction is prepared for + import to the database. + + ## Parameters + - `batch_number`: the number of the batch to check L1 transaction state. + - `tx_type`: a type of the transaction to check, one of :commit_tx, :execute_tx, or :prove_tx. + - `json_l2_rpc_named_arguments`: parameters for the RPC connections. + + ## Returns + - `{:look_for_batches, l1_tx_hash, l1_txs}` where + - `l1_tx_hash` is the hash of the L1 transaction. + - `l1_txs` is a map containing the transaction hash as a key, and values are maps + with transaction hashes and transaction timestamps. + - `{:skip, "", %{}}` means the batch is not found in the database or the state of the transaction + in the batch representation is the same as the state of the transaction for the batch + received from RPC. + """ + @spec check_if_batch_status_changed( + binary() | non_neg_integer(), + :commit_tx | :execute_tx | :prove_tx, + EthereumJSONRPC.json_rpc_named_arguments() + ) :: {:look_for_batches, any(), any()} | {:skip, <<>>, %{}} + def check_if_batch_status_changed(batch_number, tx_type, json_l2_rpc_named_arguments) + when (is_binary(batch_number) or is_integer(batch_number)) and + tx_type in [:commit_tx, :prove_tx, :execute_tx] and + is_list(json_l2_rpc_named_arguments) do + batch_from_rpc = Rpc.fetch_batch_details_by_batch_number(batch_number, json_l2_rpc_named_arguments) + + status_changed_or_error = + case Reader.batch( + batch_number, + necessity_by_association: %{ + get_association(tx_type) => :optional + } + ) do + {:ok, batch_from_db} -> transactions_of_batch_changed?(batch_from_db, batch_from_rpc, tx_type) + {:error, :not_found} -> :error + end + + l1_tx = get_l1_tx_from_batch(batch_from_rpc, tx_type) + + if l1_tx.hash != Rpc.get_binary_zero_hash() and status_changed_or_error in [true, :error] do + l1_txs = Db.get_indices_for_l1_transactions(%{l1_tx.hash => l1_tx}) + + {:look_for_batches, l1_tx.hash, l1_txs} + else + {:skip, "", %{}} + end + end + + defp get_association(tx_type) do + case tx_type do + :commit_tx -> :commit_transaction + :prove_tx -> :prove_transaction + :execute_tx -> :execute_transaction + end + end + + defp transactions_of_batch_changed?(batch_db, batch_json, tx_type) do + tx_hash_json = + case tx_type do + :commit_tx -> batch_json.commit_tx_hash + :prove_tx -> batch_json.prove_tx_hash + :execute_tx -> batch_json.executed_tx_hash + end + + tx_hash_db = + case tx_type do + :commit_tx -> batch_db.commit_transaction + :prove_tx -> batch_db.prove_transaction + :execute_tx -> batch_db.execute_transaction + end + + tx_hash_db_bytes = + if is_nil(tx_hash_db) do + Rpc.get_binary_zero_hash() + else + tx_hash_db.hash.bytes + end + + tx_hash_json != tx_hash_db_bytes + end + + defp get_l1_tx_from_batch(batch_from_rpc, tx_type) do + case tx_type do + :commit_tx -> %{hash: batch_from_rpc.commit_tx_hash, timestamp: batch_from_rpc.commit_timestamp} + :prove_tx -> %{hash: batch_from_rpc.prove_tx_hash, timestamp: batch_from_rpc.prove_timestamp} + :execute_tx -> %{hash: batch_from_rpc.executed_tx_hash, timestamp: batch_from_rpc.executed_timestamp} + end + end + + @doc """ + Receives batches from the database, establishes an association between each batch and + the corresponding L1 transactions, and imports batches and L1 transactions into the database. + If the number of batches returned from the database does not match the requested batches, + the initial list of batch numbers is returned, assuming that they can be + used for the missed batch recovery procedure. + + ## Parameters + - `batches_numbers`: the list of batch numbers that must be updated. + - `l1_txs`: a map containing transaction hashes as keys, and values are maps + with transaction hashes and transaction timestamps of L1 transactions to import to the database. + - `tx_hash`: the hash of the L1 transaction to build an association with. + - `association_key`: the field in the batch description to build an association with L1 + transactions. + + ## Returns + - `:ok` if batches and the corresponding L1 transactions are imported successfully. + - `{:recovery_required, batches_to_recover}` if the absence of batches is discovered; + `batches_to_recover` contains the list of batch numbers. + """ + @spec associate_and_import_or_prepare_for_recovery([integer()], map(), binary(), :commit_id | :execute_id | :prove_id) :: + :ok | {:recovery_required, [integer()]} + def associate_and_import_or_prepare_for_recovery(batches_numbers, l1_txs, tx_hash, association_key) + when is_list(batches_numbers) and is_map(l1_txs) and is_binary(tx_hash) and + association_key in [:commit_id, :prove_id, :execute_id] do + case prepare_batches_to_import(batches_numbers, %{association_key => l1_txs[tx_hash][:id]}) do + {:error, batches_to_recover} -> + {:recovery_required, batches_to_recover} + + {:ok, batches_to_import} -> + Db.import_to_db(batches_to_import, Map.values(l1_txs)) + :ok + end + end + + # Receives batches from the database and merges each batch's data with the data provided + # in `map_to_update`. If the number of batches returned from the database does not match + # with the requested batches, the initial list of batch numbers is returned, assuming that they + # can be used for the missed batch recovery procedure. + # + # ## Parameters + # - `batches`: the list of batch numbers that must be updated. + # - `map_to_update`: a map containing new data that must be applied to all requested batches. + # + # ## Returns + # - `{:ok, batches_to_import}` where `batches_to_import` is the list of batches ready to import + # with updated data. + # - `{:error, batches}` where `batches` contains the input list of batch numbers. + defp prepare_batches_to_import(batches, map_to_update) do + batches_from_db = Reader.batches(batches, []) + + if length(batches_from_db) == length(batches) do + batches_to_import = + batches_from_db + |> Enum.reduce([], fn batch, batches -> + [ + batch + |> Rpc.transform_transaction_batch_to_map() + |> Map.merge(map_to_update) + | batches + ] + end) + + {:ok, batches_to_import} + else + log_warning("Lack of batches received from DB to update") + {:error, batches} + end + end +end diff --git a/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/executed.ex b/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/executed.ex new file mode 100644 index 000000000000..38d7db9d81a1 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/executed.ex @@ -0,0 +1,78 @@ +defmodule Indexer.Fetcher.ZkSync.StatusTracking.Executed do + @moduledoc """ + Functionality to discover executed batches + """ + + alias Indexer.Fetcher.ZkSync.Utils.{Db, Rpc} + + import Indexer.Fetcher.ZkSync.StatusTracking.CommonUtils, + only: [ + check_if_batch_status_changed: 3, + associate_and_import_or_prepare_for_recovery: 4 + ] + + import Indexer.Fetcher.ZkSync.Utils.Logging, only: [log_info: 1] + + # keccak256("BlockExecution(uint256,bytes32,bytes32)") + @block_execution_event "0x2402307311a4d6604e4e7b4c8a15a7e1213edb39c16a31efa70afb06030d3165" + + @doc """ + Checks if the oldest unexecuted batch in the database has the associated L1 executing transaction + by requesting new batch details from RPC. If so, analyzes the `BlockExecution` event emitted by + the transaction to explore all the batches executed by it. For all discovered batches, it updates + the database with new associations, importing information about L1 transactions. + If it is found that some of the discovered batches are absent in the database, the function + interrupts and returns the list of batch numbers that can be attempted to be recovered. + + ## Parameters + - `config`: Configuration containing `json_l1_rpc_named_arguments` and + `json_l2_rpc_named_arguments` defining parameters for the RPC connections. + + ## Returns + - `:ok` if no new executed batches are found, or if all found batches and the corresponding L1 + transactions are imported successfully. + - `{:recovery_required, batches_to_recover}` if the absence of new executed batches is + discovered; `batches_to_recover` contains the list of batch numbers. + """ + @spec look_for_batches_and_update(%{ + :json_l1_rpc_named_arguments => EthereumJSONRPC.json_rpc_named_arguments(), + :json_l2_rpc_named_arguments => EthereumJSONRPC.json_rpc_named_arguments(), + optional(any()) => any() + }) :: :ok | {:recovery_required, list()} + def look_for_batches_and_update( + %{ + json_l1_rpc_named_arguments: json_l1_rpc_named_arguments, + json_l2_rpc_named_arguments: json_l2_rpc_named_arguments + } = _config + ) do + case Db.get_earliest_unexecuted_batch_number() do + nil -> + :ok + + expected_batch_number -> + log_info("Checking if the batch #{expected_batch_number} was executed") + + {next_action, tx_hash, l1_txs} = + check_if_batch_status_changed(expected_batch_number, :execute_tx, json_l2_rpc_named_arguments) + + case next_action do + :skip -> + :ok + + :look_for_batches -> + log_info("The batch #{expected_batch_number} looks like executed") + execute_tx_receipt = Rpc.fetch_tx_receipt_by_hash(tx_hash, json_l1_rpc_named_arguments) + batches_numbers_from_rpc = get_executed_batches_from_logs(execute_tx_receipt["logs"]) + + associate_and_import_or_prepare_for_recovery(batches_numbers_from_rpc, l1_txs, tx_hash, :execute_id) + end + end + end + + defp get_executed_batches_from_logs(logs) do + executed_batches = Rpc.filter_logs_and_extract_topic_at(logs, @block_execution_event, 1) + log_info("Discovered #{length(executed_batches)} executed batches in the executing tx") + + executed_batches + end +end diff --git a/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/proven.ex b/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/proven.ex new file mode 100644 index 000000000000..52165ef8f0eb --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/proven.ex @@ -0,0 +1,137 @@ +defmodule Indexer.Fetcher.ZkSync.StatusTracking.Proven do + @moduledoc """ + Functionality to discover proven batches + """ + + alias ABI.{FunctionSelector, TypeDecoder} + alias Indexer.Fetcher.ZkSync.Utils.{Db, Rpc} + + import Indexer.Fetcher.ZkSync.StatusTracking.CommonUtils, + only: [ + check_if_batch_status_changed: 3, + associate_and_import_or_prepare_for_recovery: 4 + ] + + import Indexer.Fetcher.ZkSync.Utils.Logging, only: [log_info: 1] + + @doc """ + Checks if the oldest unproven batch in the database has the associated L1 proving transaction + by requesting new batch details from RPC. If so, analyzes the calldata of the transaction + to explore all the batches proven by it. For all discovered batches, it updates + the database with new associations, importing information about L1 transactions. + If it is found that some of the discovered batches are absent in the database, the function + interrupts and returns the list of batch numbers that can be attempted to be recovered. + + ## Parameters + - `config`: Configuration containing `json_l1_rpc_named_arguments` and + `json_l2_rpc_named_arguments` defining parameters for the RPC connections. + + ## Returns + - `:ok` if no new proven batches are found, or if all found batches and the corresponding L1 + transactions are imported successfully. + - `{:recovery_required, batches_to_recover}` if the absence of new proven batches is + discovered; `batches_to_recover` contains the list of batch numbers. + """ + @spec look_for_batches_and_update(%{ + :json_l1_rpc_named_arguments => EthereumJSONRPC.json_rpc_named_arguments(), + :json_l2_rpc_named_arguments => EthereumJSONRPC.json_rpc_named_arguments(), + optional(any()) => any() + }) :: :ok | {:recovery_required, list()} + def look_for_batches_and_update( + %{ + json_l1_rpc_named_arguments: json_l1_rpc_named_arguments, + json_l2_rpc_named_arguments: json_l2_rpc_named_arguments + } = _config + ) do + case Db.get_earliest_unproven_batch_number() do + nil -> + :ok + + expected_batch_number -> + log_info("Checking if the batch #{expected_batch_number} was proven") + + {next_action, tx_hash, l1_txs} = + check_if_batch_status_changed(expected_batch_number, :prove_tx, json_l2_rpc_named_arguments) + + case next_action do + :skip -> + :ok + + :look_for_batches -> + log_info("The batch #{expected_batch_number} looks like proven") + prove_tx = Rpc.fetch_tx_by_hash(tx_hash, json_l1_rpc_named_arguments) + batches_numbers_from_rpc = get_proven_batches_from_calldata(prove_tx["input"]) + + associate_and_import_or_prepare_for_recovery(batches_numbers_from_rpc, l1_txs, tx_hash, :prove_id) + end + end + end + + defp get_proven_batches_from_calldata(calldata) do + "0x7f61885c" <> encoded_params = calldata + + # /// @param batchNumber Rollup batch number + # /// @param batchHash Hash of L2 batch + # /// @param indexRepeatedStorageChanges The serial number of the shortcut index that's used as a unique identifier for storage keys that were used twice or more + # /// @param numberOfLayer1Txs Number of priority operations to be processed + # /// @param priorityOperationsHash Hash of all priority operations from this batch + # /// @param l2LogsTreeRoot Root hash of tree that contains L2 -> L1 messages from this batch + # /// @param timestamp Rollup batch timestamp, have the same format as Ethereum batch constant + # /// @param commitment Verified input for the zkSync circuit + # struct StoredBatchInfo { + # uint64 batchNumber; + # bytes32 batchHash; + # uint64 indexRepeatedStorageChanges; + # uint256 numberOfLayer1Txs; + # bytes32 priorityOperationsHash; + # bytes32 l2LogsTreeRoot; + # uint256 timestamp; + # bytes32 commitment; + # } + # /// @notice Recursive proof input data (individual commitments are constructed onchain) + # struct ProofInput { + # uint256[] recursiveAggregationInput; + # uint256[] serializedProof; + # } + # proveBatches(StoredBatchInfo calldata _prevBatch, StoredBatchInfo[] calldata _committedBatches, ProofInput calldata _proof) + + # IO.inspect(FunctionSelector.decode("proveBatches((uint64,bytes32,uint64,uint256,bytes32,bytes32,uint256,bytes32),(uint64,bytes32,uint64,uint256,bytes32,bytes32,uint256,bytes32)[],(uint256[],uint256[]))")) + [_prev_batch, proven_batches, _proof] = + TypeDecoder.decode( + Base.decode16!(encoded_params, case: :lower), + %FunctionSelector{ + function: "proveBatches", + types: [ + tuple: [ + uint: 64, + bytes: 32, + uint: 64, + uint: 256, + bytes: 32, + bytes: 32, + uint: 256, + bytes: 32 + ], + array: + {:tuple, + [ + uint: 64, + bytes: 32, + uint: 64, + uint: 256, + bytes: 32, + bytes: 32, + uint: 256, + bytes: 32 + ]}, + tuple: [array: {:uint, 256}, array: {:uint, 256}] + ] + } + ) + + log_info("Discovered #{length(proven_batches)} proven batches in the prove tx") + + proven_batches + |> Enum.map(fn batch_info -> elem(batch_info, 0) end) + end +end diff --git a/apps/indexer/lib/indexer/fetcher/zksync/transaction_batch.ex b/apps/indexer/lib/indexer/fetcher/zksync/transaction_batch.ex new file mode 100644 index 000000000000..dac1b1d84304 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/zksync/transaction_batch.ex @@ -0,0 +1,149 @@ +defmodule Indexer.Fetcher.ZkSync.TransactionBatch do + @moduledoc """ + Discovers new batches and populates the `zksync_transaction_batches` table. + + Repetitiveness is supported by sending a `:continue` message to itself every `recheck_interval` seconds. + + Each iteration compares the number of the last handled batch stored in the state with the + latest batch available on the RPC node. If the rollup progresses, all batches between the + last handled batch (exclusively) and the latest available batch (inclusively) are downloaded from RPC + in chunks of `chunk_size` and imported into the `zksync_transaction_batches` table. If the latest + available batch is too far from the last handled batch, only `batches_max_range` batches are downloaded. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + alias Explorer.Chain.ZkSync.Reader + alias Indexer.Fetcher.ZkSync.Discovery.Workers + alias Indexer.Fetcher.ZkSync.Utils.Rpc + + import Indexer.Fetcher.ZkSync.Utils.Logging, only: [log_info: 1] + + def child_spec(start_link_arguments) do + spec = %{ + id: __MODULE__, + start: {__MODULE__, :start_link, start_link_arguments}, + restart: :transient, + type: :worker + } + + Supervisor.child_spec(spec, []) + end + + def start_link(args, gen_server_options \\ []) do + GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__)) + end + + @impl GenServer + def init(args) do + Logger.metadata(fetcher: :zksync_transaction_batches) + + config = Application.get_all_env(:indexer)[Indexer.Fetcher.ZkSync.TransactionBatch] + chunk_size = config[:chunk_size] + recheck_interval = config[:recheck_interval] + batches_max_range = config[:batches_max_range] + + Process.send(self(), :init, []) + + {:ok, + %{ + config: %{ + chunk_size: chunk_size, + batches_max_range: batches_max_range, + json_rpc_named_arguments: args[:json_rpc_named_arguments], + recheck_interval: recheck_interval + }, + data: %{latest_handled_batch_number: 0} + }} + end + + @impl GenServer + def handle_info(:init, state) do + latest_handled_batch_number = + case Reader.latest_available_batch_number() do + nil -> + log_info("No batches found in DB. Will start with the latest batch available by RPC") + # The value received from RPC is decremented in order to not waste + # the first iteration of handling `:continue` message. + Rpc.fetch_latest_sealed_batch_number(state.config.json_rpc_named_arguments) - 1 + + latest_handled_batch_number -> + latest_handled_batch_number + end + + Process.send_after(self(), :continue, 2000) + + log_info("All batches including #{latest_handled_batch_number} are considered as handled") + + {:noreply, %{state | data: %{latest_handled_batch_number: latest_handled_batch_number}}} + end + + # Checks if the rollup progresses by comparing the recently stored batch + # with the latest batch received from RPC. If progress is detected, it downloads + # batches, builds their associations with rollup blocks and transactions, and + # imports the received data to the database. If the latest batch received from RPC + # is too far from the most recently stored batch, only `batches_max_range` batches + # are downloaded. All RPC calls to get batch details and receive transactions + # included in batches are made in chunks of `chunk_size`. + # + # After importing batch information, it schedules the next iteration by sending + # the `:continue` message. The sending of the message is delayed, taking into account + # the time remaining after downloading and importing processes. + # + # ## Parameters + # - `:continue`: The message triggering the handler. + # - `state`: The current state of the fetcher containing both the fetcher configuration + # and the latest handled batch number. + # + # ## Returns + # - `{:noreply, new_state}` where the latest handled batch number is updated with the largest + # of the batch numbers imported in the current iteration. + @impl GenServer + def handle_info( + :continue, + %{ + data: %{latest_handled_batch_number: latest_handled_batch_number}, + config: %{ + batches_max_range: batches_max_range, + json_rpc_named_arguments: json_rpc_named_arguments, + recheck_interval: recheck_interval, + chunk_size: _ + } + } = state + ) do + log_info("Checking for a new batch or batches") + + latest_sealed_batch_number = Rpc.fetch_latest_sealed_batch_number(json_rpc_named_arguments) + + {new_state, handle_duration} = + if latest_handled_batch_number < latest_sealed_batch_number do + start_batch_number = latest_handled_batch_number + 1 + end_batch_number = min(latest_sealed_batch_number, latest_handled_batch_number + batches_max_range) + + log_info("Handling the batch range #{start_batch_number}..#{end_batch_number}") + + {handle_duration, _} = + :timer.tc(&Workers.get_minimal_batches_info_and_import/3, [start_batch_number, end_batch_number, state.config]) + + { + %{state | data: %{latest_handled_batch_number: end_batch_number}}, + div(handle_duration, 1000) + } + else + {state, 0} + end + + Process.send_after(self(), :continue, max(:timer.seconds(recheck_interval) - handle_duration, 0)) + + {:noreply, new_state} + end + + @impl GenServer + def handle_info({ref, _result}, state) do + Process.demonitor(ref, [:flush]) + {:noreply, state} + end +end diff --git a/apps/indexer/lib/indexer/fetcher/zksync/utils/db.ex b/apps/indexer/lib/indexer/fetcher/zksync/utils/db.ex new file mode 100644 index 000000000000..12f7e51ba986 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/zksync/utils/db.ex @@ -0,0 +1,204 @@ +defmodule Indexer.Fetcher.ZkSync.Utils.Db do + @moduledoc """ + Common functions to simplify DB routines for Indexer.Fetcher.ZkSync fetchers + """ + + alias Explorer.Chain + alias Explorer.Chain.ZkSync.Reader + import Indexer.Fetcher.ZkSync.Utils.Logging, only: [log_warning: 1, log_info: 1] + + @json_batch_fields_absent_in_db_batch [ + :commit_tx_hash, + :commit_timestamp, + :prove_tx_hash, + :prove_timestamp, + :executed_tx_hash, + :executed_timestamp + ] + + @doc """ + Deletes elements in the batch description map to prepare the batch for importing to + the database. + + ## Parameters + - `batch_with_json_fields`: a map describing a batch with elements that could remain + after downloading batch details from RPC. + + ## Returns + - A map describing the batch compatible with the database import operation. + """ + @spec prune_json_batch(map()) :: map() + def prune_json_batch(batch_with_json_fields) + when is_map(batch_with_json_fields) do + Map.drop(batch_with_json_fields, @json_batch_fields_absent_in_db_batch) + end + + @doc """ + Gets the oldest imported batch number. + + ## Parameters + - none + + ## Returns + - A batch number or `nil` if there are no batches in the database. + """ + @spec get_earliest_batch_number() :: nil | non_neg_integer() + def get_earliest_batch_number do + case Reader.oldest_available_batch_number() do + nil -> + log_warning("No batches found in DB") + nil + + value -> + value + end + end + + @doc """ + Gets the oldest imported batch number without an associated commitment L1 transaction. + + ## Parameters + - none + + ## Returns + - A batch number or `nil` in cases where there are no batches in the database or + all batches in the database are marked as committed. + """ + @spec get_earliest_sealed_batch_number() :: nil | non_neg_integer() + def get_earliest_sealed_batch_number do + case Reader.earliest_sealed_batch_number() do + nil -> + log_info("No uncommitted batches found in DB") + nil + + value -> + value + end + end + + @doc """ + Gets the oldest imported batch number without an associated proving L1 transaction. + + ## Parameters + - none + + ## Returns + - A batch number or `nil` in cases where there are no batches in the database or + all batches in the database are marked as proven. + """ + @spec get_earliest_unproven_batch_number() :: nil | non_neg_integer() + def get_earliest_unproven_batch_number do + case Reader.earliest_unproven_batch_number() do + nil -> + log_info("No unproven batches found in DB") + nil + + value -> + value + end + end + + @doc """ + Gets the oldest imported batch number without an associated executing L1 transaction. + + ## Parameters + - none + + ## Returns + - A batch number or `nil` in cases where there are no batches in the database or + all batches in the database are marked as executed. + """ + @spec get_earliest_unexecuted_batch_number() :: nil | non_neg_integer() + def get_earliest_unexecuted_batch_number do + case Reader.earliest_unexecuted_batch_number() do + nil -> + log_info("No not executed batches found in DB") + nil + + value -> + value + end + end + + @doc """ + Indexes L1 transactions provided in the input map. For transactions that + are already in the database, existing indices are taken. For new transactions, + the next available indices are assigned. + + ## Parameters + - `new_l1_txs`: A map of L1 transaction descriptions. The keys of the map are + transaction hashes. + + ## Returns + - `l1_txs`: A map of L1 transaction descriptions. Each element is extended with + the key `:id`, representing the index of the L1 transaction in the + `zksync_lifecycle_l1_transactions` table. + """ + @spec get_indices_for_l1_transactions(map()) :: any() + def get_indices_for_l1_transactions(new_l1_txs) + when is_map(new_l1_txs) do + # Get indices for l1 transactions previously handled + l1_txs = + new_l1_txs + |> Map.keys() + |> Reader.lifecycle_transactions() + |> Enum.reduce(new_l1_txs, fn {hash, id}, txs -> + {_, txs} = + Map.get_and_update!(txs, hash.bytes, fn l1_tx -> + {l1_tx, Map.put(l1_tx, :id, id)} + end) + + txs + end) + + # Get the next index for the first new transaction based + # on the indices existing in DB + l1_tx_next_id = Reader.next_id() + + # Assign new indices for the transactions which are not in + # the l1 transactions table yet + {updated_l1_txs, _} = + l1_txs + |> Map.keys() + |> Enum.reduce( + {l1_txs, l1_tx_next_id}, + fn hash, {txs, next_id} -> + tx = txs[hash] + id = Map.get(tx, :id) + + if is_nil(id) do + {Map.put(txs, hash, Map.put(tx, :id, next_id)), next_id + 1} + else + {txs, next_id} + end + end + ) + + updated_l1_txs + end + + @doc """ + Imports provided lists of batches and their associations with L1 transactions, rollup blocks, + and transactions to the database. + + ## Parameters + - `batches`: A list of maps with batch descriptions. + - `l1_txs`: A list of maps with L1 transaction descriptions. Optional. + - `l2_txs`: A list of maps with rollup transaction associations. Optional. + - `l2_blocks`: A list of maps with rollup block associations. Optional. + + ## Returns + n/a + """ + def import_to_db(batches, l1_txs \\ [], l2_txs \\ [], l2_blocks \\ []) + when is_list(batches) and is_list(l1_txs) and is_list(l2_txs) and is_list(l2_blocks) do + {:ok, _} = + Chain.import(%{ + zksync_lifecycle_transactions: %{params: l1_txs}, + zksync_transaction_batches: %{params: batches}, + zksync_batch_transactions: %{params: l2_txs}, + zksync_batch_blocks: %{params: l2_blocks}, + timeout: :infinity + }) + end +end diff --git a/apps/indexer/lib/indexer/fetcher/zksync/utils/logging.ex b/apps/indexer/lib/indexer/fetcher/zksync/utils/logging.ex new file mode 100644 index 000000000000..eb7fe6058797 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/zksync/utils/logging.ex @@ -0,0 +1,143 @@ +defmodule Indexer.Fetcher.ZkSync.Utils.Logging do + @moduledoc """ + Common logging functions for Indexer.Fetcher.ZkSync fetchers + """ + require Logger + + @doc """ + A helper function to log a message with warning severity. Uses `Logger.warning` facility. + + ## Parameters + - `msg`: a message to log + + ## Returns + `:ok` + """ + @spec log_warning(any()) :: :ok + def log_warning(msg) do + Logger.warning(msg) + end + + @doc """ + A helper function to log a message with info severity. Uses `Logger.info` facility. + + ## Parameters + - `msg`: a message to log + + ## Returns + `:ok` + """ + @spec log_info(any()) :: :ok + def log_info(msg) do + Logger.info(msg) + end + + @doc """ + A helper function to log a message with error severity. Uses `Logger.error` facility. + + ## Parameters + - `msg`: a message to log + + ## Returns + `:ok` + """ + @spec log_error(any()) :: :ok + def log_error(msg) do + Logger.error(msg) + end + + @doc """ + A helper function to log progress when handling batches in chunks. + + ## Parameters + - `prefix`: A prefix for the logging message. + - `chunk`: A list of batch numbers in the current chunk. + - `current_progress`: The total number of batches handled up to this moment. + - `total`: The total number of batches across all chunks. + + ## Returns + `:ok` + + ## Examples: + - `log_details_chunk_handling("A message", [1, 2, 3], 0, 10)` produces + `A message for batches 1..3. Progress 30%` + - `log_details_chunk_handling("A message", [2], 1, 10)` produces + `A message for batch 2. Progress 20%` + - `log_details_chunk_handling("A message", [35], 0, 1)` produces + `A message for batch 35.` + - `log_details_chunk_handling("A message", [45, 50, 51, 52, 60], 1, 1)` produces + `A message for batches 45, 50..52, 60.` + """ + @spec log_details_chunk_handling(binary(), list(), non_neg_integer(), non_neg_integer()) :: :ok + def log_details_chunk_handling(prefix, chunk, current_progress, total) + when is_binary(prefix) and is_list(chunk) and (is_integer(current_progress) and current_progress >= 0) and + (is_integer(total) and total > 0) do + chunk_length = length(chunk) + + progress = + case chunk_length == total do + true -> + "" + + false -> + percentage = + (current_progress + chunk_length) + |> Decimal.div(total) + |> Decimal.mult(100) + |> Decimal.round(2) + |> Decimal.to_string() + + " Progress: #{percentage}%" + end + + if chunk_length == 1 do + log_info("#{prefix} for batch ##{Enum.at(chunk, 0)}.") + else + log_info("#{prefix} for batches #{Enum.join(shorten_numbers_list(chunk), ", ")}.#{progress}") + end + end + + # Transform list of numbers to the list of string where consequent values + # are combined to be displayed as a range. + # + # ## Parameters + # - `msg`: a message to log + # + # ## Returns + # `shorten_list` - resulting list after folding + # + # ## Examples: + # [1, 2, 3] => ["1..3"] + # [1, 3] => ["1", "3"] + # [1, 2] => ["1..2"] + # [1, 3, 4, 5] => ["1", "3..5"] + defp shorten_numbers_list(numbers_list) do + {shorten_list, _, _} = + numbers_list + |> Enum.sort() + |> Enum.reduce({[], nil, nil}, fn number, {shorten_list, prev_range_start, prev_number} -> + shorten_numbers_list_impl(number, shorten_list, prev_range_start, prev_number) + end) + |> then(fn {shorten_list, prev_range_start, prev_number} -> + shorten_numbers_list_impl(prev_number, shorten_list, prev_range_start, prev_number) + end) + + Enum.reverse(shorten_list) + end + + defp shorten_numbers_list_impl(number, shorten_list, prev_range_start, prev_number) do + cond do + is_nil(prev_number) -> + {[], number, number} + + prev_number + 1 != number and prev_range_start == prev_number -> + {["#{prev_range_start}" | shorten_list], number, number} + + prev_number + 1 != number -> + {["#{prev_range_start}..#{prev_number}" | shorten_list], number, number} + + true -> + {shorten_list, prev_range_start, number} + end + end +end diff --git a/apps/indexer/lib/indexer/fetcher/zksync/utils/rpc.ex b/apps/indexer/lib/indexer/fetcher/zksync/utils/rpc.ex new file mode 100644 index 000000000000..282d60b35146 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/zksync/utils/rpc.ex @@ -0,0 +1,403 @@ +defmodule Indexer.Fetcher.ZkSync.Utils.Rpc do + @moduledoc """ + Common functions to handle RPC calls for Indexer.Fetcher.ZkSync fetchers + """ + + import EthereumJSONRPC, only: [json_rpc: 2, quantity_to_integer: 1] + import Indexer.Fetcher.ZkSync.Utils.Logging, only: [log_error: 1] + + @zero_hash "0000000000000000000000000000000000000000000000000000000000000000" + @zero_hash_binary <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>> + + @rpc_resend_attempts 20 + + def get_zero_hash do + @zero_hash + end + + def get_binary_zero_hash do + @zero_hash_binary + end + + @doc """ + Filters out logs from a list of transactions logs where topic #0 is `topic_0` and + builds a list of values located at position `position` in such logs. + + ## Parameters + - `logs`: The list of transaction logs to filter logs with a specific topic. + - `topic_0`: The value of topic #0 in the required logs. + - `position`: The topic number to be extracted from the topic lists of every log + and appended to the resulting list. + + ## Returns + - A list of values extracted from the required transaction logs. + - An empty list if no logs with the specified topic are found. + """ + @spec filter_logs_and_extract_topic_at(maybe_improper_list(), binary(), integer()) :: list() + def filter_logs_and_extract_topic_at(logs, topic_0, position) + when is_list(logs) and + is_binary(topic_0) and + (is_integer(position) and position >= 0 and position <= 3) do + logs + |> Enum.reduce([], fn log_entity, result -> + topics = log_entity["topics"] + + if Enum.at(topics, 0) == topic_0 do + [quantity_to_integer(Enum.at(topics, position)) | result] + else + result + end + end) + end + + defp from_ts_to_datetime(time_ts) do + {_, unix_epoch_starts} = DateTime.from_unix(0) + + case is_nil(time_ts) or time_ts == 0 do + true -> + unix_epoch_starts + + false -> + case DateTime.from_unix(time_ts) do + {:ok, datetime} -> + datetime + + {:error, _} -> + unix_epoch_starts + end + end + end + + defp from_iso8601_to_datetime(time_string) do + case is_nil(time_string) do + true -> + from_ts_to_datetime(0) + + false -> + case DateTime.from_iso8601(time_string) do + {:ok, datetime, _} -> + datetime + + {:error, _} -> + from_ts_to_datetime(0) + end + end + end + + defp json_txid_to_hash(hash) do + case hash do + "0x" <> tx_hash -> tx_hash + nil -> @zero_hash + end + end + + defp strhash_to_byteshash(hash) do + hash + |> json_txid_to_hash() + |> Base.decode16!(case: :mixed) + end + + @doc """ + Transforms a map with batch data received from the `zks_getL1BatchDetails` call + into a map that can be used by Indexer.Fetcher.ZkSync fetchers for further handling. + All hexadecimal hashes are converted to their decoded binary representation, + Unix and ISO8601 timestamps are converted to DateTime objects. + + ## Parameters + - `json_response`: Raw data received from the JSON RPC call. + + ## Returns + - A map containing minimal information about the batch. `start_block` and `end_block` + elements are set to `nil`. + """ + @spec transform_batch_details_to_map(map()) :: map() + def transform_batch_details_to_map(json_response) + when is_map(json_response) do + %{ + "number" => {:number, :ok}, + "timestamp" => {:timestamp, :ts_to_datetime}, + "l1TxCount" => {:l1_tx_count, :ok}, + "l2TxCount" => {:l2_tx_count, :ok}, + "rootHash" => {:root_hash, :str_to_byteshash}, + "commitTxHash" => {:commit_tx_hash, :str_to_byteshash}, + "committedAt" => {:commit_timestamp, :iso8601_to_datetime}, + "proveTxHash" => {:prove_tx_hash, :str_to_byteshash}, + "provenAt" => {:prove_timestamp, :iso8601_to_datetime}, + "executeTxHash" => {:executed_tx_hash, :str_to_byteshash}, + "executedAt" => {:executed_timestamp, :iso8601_to_datetime}, + "l1GasPrice" => {:l1_gas_price, :ok}, + "l2FairGasPrice" => {:l2_fair_gas_price, :ok} + # :start_block added by request_block_ranges_by_rpc + # :end_block added by request_block_ranges_by_rpc + } + |> Enum.reduce(%{start_block: nil, end_block: nil}, fn {key, {key_atom, transform_type}}, batch_details_map -> + value_in_json_response = Map.get(json_response, key) + + Map.put( + batch_details_map, + key_atom, + case transform_type do + :iso8601_to_datetime -> from_iso8601_to_datetime(value_in_json_response) + :ts_to_datetime -> from_ts_to_datetime(value_in_json_response) + :str_to_txhash -> json_txid_to_hash(value_in_json_response) + :str_to_byteshash -> strhash_to_byteshash(value_in_json_response) + _ -> value_in_json_response + end + ) + end) + end + + @doc """ + Transforms a map with batch data received from the database into a map that + can be used by Indexer.Fetcher.ZkSync fetchers for further handling. + + ## Parameters + - `batch`: A map containing a batch description received from the database. + + ## Returns + - A map containing simplified representation of the batch. Compatible with + the database import operation. + """ + def transform_transaction_batch_to_map(batch) + when is_map(batch) do + %{ + number: batch.number, + timestamp: batch.timestamp, + l1_tx_count: batch.l1_tx_count, + l2_tx_count: batch.l2_tx_count, + root_hash: batch.root_hash.bytes, + l1_gas_price: batch.l1_gas_price, + l2_fair_gas_price: batch.l2_fair_gas_price, + start_block: batch.start_block, + end_block: batch.end_block, + commit_id: batch.commit_id, + prove_id: batch.prove_id, + execute_id: batch.execute_id + } + end + + @doc """ + Retrieves batch details from the RPC endpoint using the `zks_getL1BatchDetails` call. + + ## Parameters + - `batch_number`: The batch number or identifier. + - `json_rpc_named_arguments`: Configuration parameters for the JSON RPC connection. + + ## Returns + - A map containing minimal batch details. It includes `start_block` and `end_block` + elements, both set to `nil`. + """ + @spec fetch_batch_details_by_batch_number(binary() | non_neg_integer(), EthereumJSONRPC.json_rpc_named_arguments()) :: + map() + def fetch_batch_details_by_batch_number(batch_number, json_rpc_named_arguments) + when (is_integer(batch_number) or is_binary(batch_number)) and is_list(json_rpc_named_arguments) do + req = + EthereumJSONRPC.request(%{ + id: batch_number, + method: "zks_getL1BatchDetails", + params: [batch_number] + }) + + error_message = &"Cannot call zks_getL1BatchDetails. Error: #{inspect(&1)}" + + {:ok, resp} = repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, @rpc_resend_attempts) + + transform_batch_details_to_map(resp) + end + + @doc """ + Fetches transaction details from the RPC endpoint using the `eth_getTransactionByHash` call. + + ## Parameters + - `raw_hash`: The hash of the Ethereum transaction. It can be provided as a decoded binary + or hexadecimal string. + - `json_rpc_named_arguments`: Configuration parameters for the JSON RPC connection. + + ## Returns + - A map containing details of the transaction. + """ + @spec fetch_tx_by_hash(binary(), EthereumJSONRPC.json_rpc_named_arguments()) :: map() + def fetch_tx_by_hash(raw_hash, json_rpc_named_arguments) + when is_binary(raw_hash) and is_list(json_rpc_named_arguments) do + hash = + case raw_hash do + "0x" <> _ -> raw_hash + _ -> "0x" <> Base.encode16(raw_hash) + end + + req = + EthereumJSONRPC.request(%{ + id: 0, + method: "eth_getTransactionByHash", + params: [hash] + }) + + error_message = &"Cannot call eth_getTransactionByHash for hash #{hash}. Error: #{inspect(&1)}" + + {:ok, resp} = repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, @rpc_resend_attempts) + + resp + end + + @doc """ + Fetches the transaction receipt from the RPC endpoint using the `eth_getTransactionReceipt` call. + + ## Parameters + - `raw_hash`: The hash of the Ethereum transaction. It can be provided as a decoded binary + or hexadecimal string. + - `json_rpc_named_arguments`: Configuration parameters for the JSON RPC connection. + + ## Returns + - A map containing the receipt details of the transaction. + """ + @spec fetch_tx_receipt_by_hash(binary(), EthereumJSONRPC.json_rpc_named_arguments()) :: map() + def fetch_tx_receipt_by_hash(raw_hash, json_rpc_named_arguments) + when is_binary(raw_hash) and is_list(json_rpc_named_arguments) do + hash = + case raw_hash do + "0x" <> _ -> raw_hash + _ -> "0x" <> Base.encode16(raw_hash) + end + + req = + EthereumJSONRPC.request(%{ + id: 0, + method: "eth_getTransactionReceipt", + params: [hash] + }) + + error_message = &"Cannot call eth_getTransactionReceipt for hash #{hash}. Error: #{inspect(&1)}" + + {:ok, resp} = repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, @rpc_resend_attempts) + + resp + end + + @doc """ + Fetches the latest sealed batch number from the RPC endpoint using the `zks_L1BatchNumber` call. + + ## Parameters + - `json_rpc_named_arguments`: Configuration parameters for the JSON RPC connection. + + ## Returns + - A non-negative integer representing the latest sealed batch number. + """ + @spec fetch_latest_sealed_batch_number(EthereumJSONRPC.json_rpc_named_arguments()) :: nil | non_neg_integer() + def fetch_latest_sealed_batch_number(json_rpc_named_arguments) + when is_list(json_rpc_named_arguments) do + req = EthereumJSONRPC.request(%{id: 0, method: "zks_L1BatchNumber", params: []}) + + error_message = &"Cannot call zks_L1BatchNumber. Error: #{inspect(&1)}" + + {:ok, resp} = repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, @rpc_resend_attempts) + + quantity_to_integer(resp) + end + + @doc """ + Fetches block details using multiple `eth_getBlockByNumber` RPC calls. + + ## Parameters + - `requests_list`: A list of `EthereumJSONRPC.Transport.request()` representing multiple + `eth_getBlockByNumber` RPC calls for different block numbers. + - `json_rpc_named_arguments`: Configuration parameters for the JSON RPC connection. + + ## Returns + - A list of responses containing details of the requested blocks. + """ + @spec fetch_blocks_details([EthereumJSONRPC.Transport.request()], EthereumJSONRPC.json_rpc_named_arguments()) :: + list() + def fetch_blocks_details(requests_list, json_rpc_named_arguments) + + def fetch_blocks_details([], _) do + [] + end + + def fetch_blocks_details(requests_list, json_rpc_named_arguments) + when is_list(requests_list) and is_list(json_rpc_named_arguments) do + error_message = &"Cannot call eth_getBlockByNumber. Error: #{inspect(&1)}" + + {:ok, responses} = + repeated_call(&json_rpc/2, [requests_list, json_rpc_named_arguments], error_message, @rpc_resend_attempts) + + responses + end + + @doc """ + Fetches batches details using multiple `zks_getL1BatchDetails` RPC calls. + + ## Parameters + - `requests_list`: A list of `EthereumJSONRPC.Transport.request()` representing multiple + `zks_getL1BatchDetails` RPC calls for different block numbers. + - `json_rpc_named_arguments`: Configuration parameters for the JSON RPC connection. + + ## Returns + - A list of responses containing details of the requested batches. + """ + @spec fetch_batches_details([EthereumJSONRPC.Transport.request()], EthereumJSONRPC.json_rpc_named_arguments()) :: + list() + def fetch_batches_details(requests_list, json_rpc_named_arguments) + + def fetch_batches_details([], _) do + [] + end + + def fetch_batches_details(requests_list, json_rpc_named_arguments) + when is_list(requests_list) and is_list(json_rpc_named_arguments) do + error_message = &"Cannot call zks_getL1BatchDetails. Error: #{inspect(&1)}" + + {:ok, responses} = + repeated_call(&json_rpc/2, [requests_list, json_rpc_named_arguments], error_message, @rpc_resend_attempts) + + responses + end + + @doc """ + Fetches block ranges included in the specified batches by using multiple + `zks_getL1BatchBlockRange` RPC calls. + + ## Parameters + - `requests_list`: A list of `EthereumJSONRPC.Transport.request()` representing multiple + `zks_getL1BatchBlockRange` RPC calls for different batch numbers. + - `json_rpc_named_arguments`: Configuration parameters for the JSON RPC connection. + + ## Returns + - A list of responses containing block ranges associated with the requested batches. + """ + @spec fetch_blocks_ranges([EthereumJSONRPC.Transport.request()], EthereumJSONRPC.json_rpc_named_arguments()) :: + list() + def fetch_blocks_ranges(requests_list, json_rpc_named_arguments) + + def fetch_blocks_ranges([], _) do + [] + end + + def fetch_blocks_ranges(requests_list, json_rpc_named_arguments) + when is_list(requests_list) and is_list(json_rpc_named_arguments) do + error_message = &"Cannot call zks_getL1BatchBlockRange. Error: #{inspect(&1)}" + + {:ok, responses} = + repeated_call(&json_rpc/2, [requests_list, json_rpc_named_arguments], error_message, @rpc_resend_attempts) + + responses + end + + defp repeated_call(func, args, error_message, retries_left) do + case apply(func, args) do + {:ok, _} = res -> + res + + {:error, message} = err -> + retries_left = retries_left - 1 + + if retries_left <= 0 do + log_error(error_message.(message)) + err + else + log_error("#{error_message.(message)} Retrying...") + :timer.sleep(3000) + repeated_call(func, args, error_message, retries_left) + end + end + end +end diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index e46927a0e040..7ddb8be98b2b 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -46,6 +46,9 @@ defmodule Indexer.Supervisor do Withdrawal } + alias Indexer.Fetcher.ZkSync.BatchesStatusTracker, as: ZkSyncBatchesStatusTracker + alias Indexer.Fetcher.ZkSync.TransactionBatch, as: ZkSyncTransactionBatch + alias Indexer.Temporary.{ BlocksTransactionsMismatch, UncatalogedTokenTransfers, @@ -167,6 +170,12 @@ defmodule Indexer.Supervisor do configure(Indexer.Fetcher.PolygonZkevm.BridgeL2.Supervisor, [ [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), + configure(ZkSyncTransactionBatch.Supervisor, [ + [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] + ]), + configure(ZkSyncBatchesStatusTracker.Supervisor, [ + [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] + ]), configure(Indexer.Fetcher.PolygonZkevm.TransactionBatch.Supervisor, [ [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), diff --git a/config/config_helper.exs b/config/config_helper.exs index 6891ae75910c..b67e23e0e495 100644 --- a/config/config_helper.exs +++ b/config/config_helper.exs @@ -18,6 +18,7 @@ defmodule ConfigHelper do "suave" -> base_repos ++ [Explorer.Repo.Suave] "filecoin" -> base_repos ++ [Explorer.Repo.Filecoin] "stability" -> base_repos ++ [Explorer.Repo.Stability] + "zksync" -> base_repos ++ [Explorer.Repo.ZkSync] _ -> base_repos end diff --git a/config/runtime.exs b/config/runtime.exs index 5ba06802677f..5cac7d87b6e9 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -754,6 +754,21 @@ config :indexer, Indexer.Fetcher.PolygonEdge.WithdrawalExit, start_block_l1: System.get_env("INDEXER_POLYGON_EDGE_L1_WITHDRAWALS_START_BLOCK"), exit_helper: System.get_env("INDEXER_POLYGON_EDGE_L1_EXIT_HELPER_CONTRACT") +config :indexer, Indexer.Fetcher.ZkSync.TransactionBatch, + chunk_size: ConfigHelper.parse_integer_env_var("INDEXER_ZKSYNC_BATCHES_CHUNK_SIZE", 50), + batches_max_range: ConfigHelper.parse_integer_env_var("INDEXER_ZKSYNC_NEW_BATCHES_MAX_RANGE", 50), + recheck_interval: ConfigHelper.parse_integer_env_var("INDEXER_ZKSYNC_NEW_BATCHES_RECHECK_INTERVAL", 60) + +config :indexer, Indexer.Fetcher.ZkSync.TransactionBatch.Supervisor, + enabled: ConfigHelper.parse_bool_env_var("INDEXER_ZKSYNC_BATCHES_ENABLED") + +config :indexer, Indexer.Fetcher.ZkSync.BatchesStatusTracker, + zksync_l1_rpc: System.get_env("INDEXER_ZKSYNC_L1_RPC"), + recheck_interval: ConfigHelper.parse_integer_env_var("INDEXER_ZKSYNC_BATCHES_STATUS_RECHECK_INTERVAL", 60) + +config :indexer, Indexer.Fetcher.ZkSync.BatchesStatusTracker.Supervisor, + enabled: ConfigHelper.parse_bool_env_var("INDEXER_ZKSYNC_BATCHES_ENABLED") + config :indexer, Indexer.Fetcher.RootstockData.Supervisor, disabled?: ConfigHelper.chain_type() != "rsk" || ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_ROOTSTOCK_DATA_FETCHER") diff --git a/config/runtime/dev.exs b/config/runtime/dev.exs index d889a4bbd6dd..a8f21fdbbbbd 100644 --- a/config/runtime/dev.exs +++ b/config/runtime/dev.exs @@ -117,6 +117,15 @@ config :explorer, Explorer.Repo.PolygonZkevm, # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type pool_size: 1 +# Configure ZkSync database +config :explorer, Explorer.Repo.ZkSync, + database: database, + hostname: hostname, + url: System.get_env("DATABASE_URL"), + # actually this repo is not started, and its pool size remains unused. + # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type + pool_size: 1 + # Configure Rootstock database config :explorer, Explorer.Repo.RSK, database: database, @@ -152,8 +161,6 @@ config :explorer, Explorer.Repo.Stability, database: database, hostname: hostname, url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type pool_size: 1 variant = Variant.get() diff --git a/config/runtime/prod.exs b/config/runtime/prod.exs index 3fc64ea80b1b..cabdab7b9429 100644 --- a/config/runtime/prod.exs +++ b/config/runtime/prod.exs @@ -87,6 +87,14 @@ config :explorer, Explorer.Repo.PolygonZkevm, pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() +# Configures ZkSync database +config :explorer, Explorer.Repo.ZkSync, + url: System.get_env("DATABASE_URL"), + # actually this repo is not started, and its pool size remains unused. + # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type + pool_size: 1, + ssl: ExplorerConfigHelper.ssl_enabled?() + # Configures Rootstock database config :explorer, Explorer.Repo.RSK, url: System.get_env("DATABASE_URL"), @@ -116,8 +124,6 @@ config :explorer, Explorer.Repo.Filecoin, # Configures Stability database config :explorer, Explorer.Repo.Stability, url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() diff --git a/cspell.json b/cspell.json index a1f1f7f12751..cad6b4ad11e7 100644 --- a/cspell.json +++ b/cspell.json @@ -9,28 +9,113 @@ "apps/block_scout_web/assets/js/lib/ace/src-min/*.js" ], "words": [ + "AION", + "AIRTABLE", + "ARGMAX", + "Aiubo", + "Arbitrum", + "Asfpp", + "Asfpp", + "Autodetection", + "Autonity", + "Blockchair", + "CALLCODE", + "CBOR", + "Cldr", + "Consolas", + "Cyclomatic", + "DATETIME", + "DELEGATECALL", + "Decompiler", + "DefiLlama", + "DefiLlama", + "Denormalization", + "Denormalized", + "ECTO", + "EDCSA", + "Ebhwp", + "Encryptor", + "Erigon", + "Ethash", + "Faileddi", + "Filesize", + "Floki", + "Fuov", + "Hazkne", + "Hodl", + "Iframe", + "Iframes", + "Incrementer", + "Instrumenter", + "Karnaugh", + "Keepalive", + "LUKSO", + "Limegreen", + "MARKETCAP", + "MDWW", + "Mainnets", + "Mendonça", + "Menlo", + "Merkle", + "Mixfile", + "NOTOK", + "Nerg", + "Nerg", + "Nethermind", + "Neue", + "Njhr", + "Nodealus", + "NovesFi", + "Numbe", + "Nunito", + "PGDATABASE", + "PGHOST", + "PGPASSWORD", + "PGPORT", + "PGUSER", + "POSDAO", + "Posix", + "Postrge", + "Qebz", + "Qmbgk", + "REINDEX", + "RPC's", + "RPCs", + "SENDGRID", + "SJONRPC", + "SOLIDITYSCAN", + "SOLIDITYSCAN", + "STATICCALL", + "Secon", + "Segoe", + "Sokol", + "Synthereum", + "Sérgio", + "Tcnwg", + "Testinit", + "Testit", + "Testname", + "Txns", + "UUPS", + "Unitarion", + "Unitorius", + "Unitorus", + "Utqn", + "Wanchain", "aave", "absname", "acbs", "accs", "actb", "addedfile", - "AION", - "AIRTABLE", - "Aiubo", "alloc", "amzootyukbugmx", "apikey", - "Arbitrum", - "ARGMAX", "arounds", "asda", - "Asfpp", "atoken", "autodetectfalse", - "Autodetection", "autodetecttrue", - "Autonity", "autoplay", "backoff", "badhash", @@ -46,7 +131,6 @@ "bigserial", "binwrite", "bizbuz", - "Blockchair", "blockheight", "blockless", "blockno", @@ -62,15 +146,14 @@ "buildx", "bytea", "bytecodes", + "byteshash", "byts", "bzzr", "cacerts", "callcode", - "CALLCODE", "calltracer", "capturelog", "cattributes", - "CBOR", "cellspacing", "certifi", "cfasync", @@ -78,11 +161,11 @@ "chainlink", "chakra", "chartjs", + "checkproxyverification", "checksummed", "checkverifystatus", "childspec", "citext", - "Cldr", "clearfix", "clickover", "codeformat", @@ -100,8 +183,8 @@ "compilerversion", "concache", "cond", - "Consolas", "contractaddress", + "contractaddresses", "contractname", "cooldown", "cooltesthost", @@ -109,30 +192,23 @@ "ctbs", "ctid", "cumalative", - "Cyclomatic", "cypherpunk", "czilladx", "datapoint", "datepicker", - "DATETIME", "deae", "decamelize", "decompiled", "decompiler", - "Decompiler", "dedup", - "DefiLlama", "defmock", "defsupervisor", "dejob", "dejobio", "delegatecall", - "DELEGATECALL", "delegators", "demonitor", "denormalization", - "Denormalization", - "Denormalized", "descr", "describedby", "differenceby", @@ -140,22 +216,17 @@ "dropzone", "dxgd", "dyntsrohg", - "Ebhwp", "econnrefused", - "ECTO", - "EDCSA", "edhygl", "efkuga", - "Encryptor", "endregion", "enetunreach", "enoent", "epns", - "Erigon", "errora", "errorb", "erts", - "Ethash", + "erts", "etherchain", "ethprice", "ethsupply", @@ -163,6 +234,7 @@ "etimedout", "eveem", "evenodd", + "evmversion", "exitor", "explorable", "exponention", @@ -170,18 +242,16 @@ "extname", "extremums", "exvcr", - "Faileddi", "falala", "FEVM", "Filesize", "Filecoin", "fkey", - "Floki", + "fkey", "fontawesome", "fortawesome", "fsym", "fullwidth", - "Fuov", "fvdskvjglav", "fwrite", "fwupv", @@ -190,6 +260,7 @@ "getblockcountdown", "getblocknobytime", "getblockreward", + "getcontractcreation", "getlogs", "getminedblocks", "getsourcecode", @@ -208,7 +279,6 @@ "gtag", "happygokitty", "haspopup", - "Hazkne", "histoday", "hljs", "Hodl", @@ -217,15 +287,11 @@ "hyperledger", "ifdef", "ifeq", - "Iframe", "iframes", - "Iframes", "ilike", "illustr", "inapp", - "Incrementer", "insertable", - "Instrumenter", "intersectionby", "ints", "invalidend", @@ -241,19 +307,19 @@ "johnnny", "jsons", "juon", - "Karnaugh", "keccak", - "Keepalive", "keyout", "kittencream", "labeledby", "labelledby", "lastmod", + "lastmod", "lastname", "lastword", "lformat", + "libraryaddress", + "libraryname", "libsecp", - "Limegreen", "linecap", "linejoin", "listaccounts", @@ -261,30 +327,23 @@ "lkve", "llhauc", "loggable", - "LUKSO", "luxon", "mabi", - "Mainnets", "malihu", "mallowance", - "MARKETCAP", "maxlength", "mcap", "mconst", "mdef", - "MDWW", "meer", - "Mendonça", - "Menlo", + "meer", "mergeable", - "Merkle", "metatags", "microsecs", "millis", "mintings", "mistmatches", "miterlimit", - "Mixfile", "mmem", "mname", "mnot", @@ -306,17 +365,12 @@ "mydep", "nanomorph", "nbsp", - "Nerg", - "Nethermind", - "Neue", "newkey", "nftproduct", "ngettext", "nillifies", - "Njhr", "nlmyzui", "nocheck", - "Nodealus", "nohighlight", "nolink", "nonconsensus", @@ -325,12 +379,9 @@ "noreferrer", "noreply", "noves", - "NovesFi", "nowarn", "nowrap", "ntoa", - "Numbe", - "Nunito", "nxdomain", "omni", "onclick", @@ -346,11 +397,6 @@ "pendingtxlist", "perc", "persistable", - "PGDATABASE", - "PGHOST", - "PGPASSWORD", - "PGPORT", - "PGUSER", "phash", "pikaday", "pkey", @@ -363,9 +409,6 @@ "pocc", "polyline", "poolboy", - "POSDAO", - "Posix", - "Postrge", "prederive", "prederived", "progressbar", @@ -373,15 +416,15 @@ "psql", "purrstige", "qdai", - "Qebz", "qitmeer", - "Qmbgk", + "qitmeer", "qrcode", "queriable", "questiona", "questionb", "qwertyufhgkhiop", "qwertyuioiuytrewertyuioiuytrertyuio", + "qwertyuioiuytrewertyuioiuytrertyuio", "racecar", "raisedbrow", "rangeright", @@ -414,9 +457,8 @@ "RPCs", "safelow", "savechives", - "Secon", "secp", - "Segoe", + "secp", "seindexed", "selfdestruct", "selfdestructed", @@ -428,13 +470,10 @@ "shibarium", "shortdoc", "shortify", - "SJONRPC", "smallint", "smth", "snapshotted", "snapshotting", - "Sokol", - "SOLIDITYSCAN", "soljson", "someout", "sourcecode", @@ -445,8 +484,8 @@ "stakers", "stateroot", "staticcall", - "STATICCALL", "strftime", + "strhash", "stringly", "stylelint", "stylesheet", @@ -464,26 +503,23 @@ "supernet", "swal", "sweetalert", - "Synthereum", "tabindex", "tablist", "tabpanel", "tarekraafat", "tbody", "tbrf", - "Tcnwg", "tems", - "Testinit", - "Testit", - "Testname", "testpassword", "testtest", "testuser", "thead", "thicccbrowz", "throttleable", + "timestmaps", "tokenbalance", "tokenlist", + "tokennfttx", "tokensupply", "tokentx", "topbar", @@ -496,8 +532,8 @@ "tsquery", "tsvector", "tsym", + "txid", "txlistinternal", - "Txns", "txpool", "txreceipt", "ueberauth", @@ -506,9 +542,6 @@ "unclosable", "unfetched", "unfinalized", - "Unitarion", - "Unitorius", - "Unitorus", "unknownc", "unknowne", "unmarshal", @@ -523,8 +556,7 @@ "upserts", "urijs", "urlset", - "Utqn", - "UUPS", + "urlset", "valign", "valuemax", "valuemin", @@ -536,7 +568,6 @@ "volumeto", "vyper", "walletconnect", - "Wanchain", "warninga", "warningb", "watchlist", diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 6268f16202af..9ba9c686ee0f 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -188,6 +188,12 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false # INDEXER_POLYGON_ZKEVM_L1_BRIDGE_NATIVE_DECIMALS= # INDEXER_POLYGON_ZKEVM_L2_BRIDGE_START_BLOCK= # INDEXER_POLYGON_ZKEVM_L2_BRIDGE_CONTRACT= +# INDEXER_ZKSYNC_BATCHES_ENABLED= +# INDEXER_ZKSYNC_BATCHES_CHUNK_SIZE= +# INDEXER_ZKSYNC_NEW_BATCHES_MAX_RANGE= +# INDEXER_ZKSYNC_NEW_BATCHES_RECHECK_INTERVAL= +# INDEXER_ZKSYNC_L1_RPC= +# INDEXER_ZKSYNC_BATCHES_STATUS_RECHECK_INTERVAL= # INDEXER_REALTIME_FETCHER_MAX_GAP= # INDEXER_FETCHER_INIT_QUERY_LIMIT= # INDEXER_TOKEN_BALANCES_FETCHER_INIT_QUERY_LIMIT= From 5505206714c62ee37ac934312e666c1a456405ed Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop <105209995+Qwerty5Uiop@users.noreply.github.com> Date: Thu, 14 Mar 2024 16:11:57 +0400 Subject: [PATCH 593/607] Don't insert pbo for not inserted blocks (#9629) --- CHANGELOG.md | 1 + .../explorer/chain/import/runner/blocks.ex | 25 ++++++------------- .../chain/import/runner/blocks_test.exs | 19 ++++++++++++++ 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e261aa327bf1..5acd36e7f49e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ ### Fixes +- [#9629](https://github.com/blockscout/blockscout/pull/9629) - Don't insert pbo for not inserted blocks - [#9601](https://github.com/blockscout/blockscout/pull/9601) - Fix token instance transform for some unconventional tokens - [#9597](https://github.com/blockscout/blockscout/pull/9597) - Update token transfers block_consensus by block_number - [#9596](https://github.com/blockscout/blockscout/pull/9596) - Fix logging diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex index 0db2d349a4a6..293a746b5f45 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex @@ -64,12 +64,6 @@ defmodule Explorer.Chain.Import.Runner.Blocks do hashes = Enum.map(changes_list, & &1.hash) - items_for_pending_ops = - changes_list - |> filter_by_height_range(&RangesHelper.traceable_block_number?(&1.number)) - |> Enum.filter(& &1.consensus) - |> Enum.map(&{&1.number, &1.hash}) - consensus_block_numbers = consensus_block_numbers(changes_list) # Enforce ShareLocks tables order (see docs: sharelocks.md) @@ -100,10 +94,10 @@ defmodule Explorer.Chain.Import.Runner.Blocks do :blocks ) end) - |> Multi.run(:new_pending_operations, fn repo, %{lose_consensus: nonconsensus_items} -> + |> Multi.run(:new_pending_operations, fn repo, %{blocks: blocks} -> Instrumenter.block_import_stage_runner( fn -> - new_pending_operations(repo, nonconsensus_items, items_for_pending_ops, insert_options) + new_pending_operations(repo, blocks, insert_options) end, :address_referencing, :blocks, @@ -441,18 +435,13 @@ defmodule Explorer.Chain.Import.Runner.Blocks do lose_consensus(ExplorerRepo, [], block_numbers, [], opts) end - defp new_pending_operations(repo, nonconsensus_items, items, %{ - timeout: timeout, - timestamps: timestamps - }) do + defp new_pending_operations(repo, inserted_blocks, %{timeout: timeout, timestamps: timestamps}) do sorted_pending_ops = - items - |> MapSet.new() - |> MapSet.difference(MapSet.new(nonconsensus_items)) + inserted_blocks + |> filter_by_height_range(&RangesHelper.traceable_block_number?(&1.number)) + |> Enum.filter(& &1.consensus) + |> Enum.map(&%{block_hash: &1.hash, block_number: &1.number}) |> Enum.sort() - |> Enum.map(fn {number, hash} -> - %{block_hash: hash, block_number: number} - end) Import.insert_changes_list( repo, diff --git a/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs b/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs index 5d51bc760c1b..a317767a96cf 100644 --- a/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs @@ -275,6 +275,25 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do assert %{block_number: ^number, block_hash: ^hash} = Repo.one(PendingBlockOperation) end + test "inserts pending_block_operations only for actually inserted blocks", + %{consensus_block: %{miner_hash: miner_hash}, options: options} do + %{number: number, hash: hash} = new_block = params_for(:block, miner_hash: miner_hash, consensus: true) + new_block1 = params_for(:block, miner_hash: miner_hash, consensus: true) + + miner = Repo.get_by(Address, hash: miner_hash) + + insert(:block, Map.put(new_block1, :miner, miner)) + + %Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, new_block) + %Ecto.Changeset{valid?: true, changes: block_changes1} = Block.changeset(%Block{}, new_block1) + + Multi.new() + |> Blocks.run([block_changes, block_changes1], options) + |> Repo.transaction() + + assert %{block_number: ^number, block_hash: ^hash} = Repo.one(PendingBlockOperation) + end + test "change instance owner if was token transfer in older blocks", %{consensus_block: %{hash: block_hash, miner_hash: miner_hash, number: block_number}, options: options} do block_number = block_number + 2 From 0eb750121137332eb51b13b3282cc043cf2959a0 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 14 Mar 2024 16:07:39 +0300 Subject: [PATCH 594/607] Rewrite query for token transfers on address to eliminate "or" (#9576) * Rewrite query for token transfers on address to eliminate "or" * Add token_transfers [:to_address_hash, :block_number] index * Review processing * Review processing #2 * Update apps/explorer/lib/explorer/chain/token_transfer.ex Co-authored-by: Kirill Fedoseev --------- Co-authored-by: Kirill Fedoseev --- CHANGELOG.md | 1 + .../controllers/api/v2/address_controller.ex | 3 +- apps/explorer/lib/explorer/chain.ex | 3 +- .../lib/explorer/chain/token_transfer.ex | 46 ++++++++++++++----- ...d_from_address_hash_block_number_index.exs | 9 ++++ ...add_to_address_hash_block_number_index.exs | 9 ++++ .../lib/indexer/transform/token_transfers.ex | 28 +++++++---- 7 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 apps/explorer/priv/repo/migrations/20240308123508_token_transfers_add_from_address_hash_block_number_index.exs create mode 100644 apps/explorer/priv/repo/migrations/20240313195728_token_transfers_add_to_address_hash_block_number_index.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5acd36e7f49e..3ad4cad924c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - [#9597](https://github.com/blockscout/blockscout/pull/9597) - Update token transfers block_consensus by block_number - [#9596](https://github.com/blockscout/blockscout/pull/9596) - Fix logging - [#9585](https://github.com/blockscout/blockscout/pull/9585) - Fix Geth block internal transactions fetching +- [#9576](https://github.com/blockscout/blockscout/pull/9576) - Rewrite query for token transfers on address to eliminate "or" - [#9572](https://github.com/blockscout/blockscout/pull/9572) - Fix Shibarium L1 fetcher - [#9563](https://github.com/blockscout/blockscout/pull/9563) - Fix timestamp handler for unfinalized zkEVM batches - [#9560](https://github.com/blockscout/blockscout/pull/9560) - Fix fetch pending transaction for hyperledger besu client diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex index e77b2a0c1f00..6a02b832d96b 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -48,7 +48,8 @@ defmodule BlockScoutWeb.API.V2.AddressController do :to_address => :optional, :from_address => :optional, :block => :optional, - :transaction => :optional + :transaction => :optional, + :token => :optional }, api?: true ] diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 8657c4b5f039..6e58566709e0 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -307,9 +307,8 @@ defmodule Explorer.Chain do necessity_by_association = Keyword.get(options, :necessity_by_association) direction - |> TokenTransfer.token_transfers_by_address_hash(address_hash, filters) + |> TokenTransfer.token_transfers_by_address_hash(address_hash, filters, paging_options) |> join_associations(necessity_by_association) - |> TokenTransfer.handle_paging_options(paging_options) |> select_repo(options).all() end diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index 758c5707178d..a185e43b49a5 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -311,13 +311,40 @@ defmodule Explorer.Chain.TokenTransfer do |> order_by([tt], desc: tt.block_number, desc: tt.log_index) end - def token_transfers_by_address_hash(direction, address_hash, token_types) do - only_consensus_transfers_query() - |> filter_by_direction(direction, address_hash) - |> order_by([tt], desc: tt.block_number, desc: tt.log_index) - |> join(:inner, [tt], token in assoc(tt, :token), as: :token) - |> preload([token: token], [{:token, token}]) - |> filter_by_type(token_types) + def token_transfers_by_address_hash(direction, address_hash, token_types, paging_options) do + if direction == :to || direction == :from do + only_consensus_transfers_query() + |> filter_by_direction(direction, address_hash) + |> order_by([tt], desc: tt.block_number, desc: tt.log_index) + |> join(:inner, [tt], token in assoc(tt, :token), as: :token) + |> preload([token: token], [{:token, token}]) + |> filter_by_type(token_types) + |> handle_paging_options(paging_options) + else + to_address_hash_query = + only_consensus_transfers_query() + |> join(:inner, [tt], token in assoc(tt, :token), as: :token) + |> filter_by_direction(:to, address_hash) + |> filter_by_type(token_types) + |> order_by([tt], desc: tt.block_number, desc: tt.log_index) + |> handle_paging_options(paging_options) + |> Chain.wrapped_union_subquery() + + from_address_hash_query = + only_consensus_transfers_query() + |> join(:inner, [tt], token in assoc(tt, :token), as: :token) + |> filter_by_direction(:from, address_hash) + |> filter_by_type(token_types) + |> order_by([tt], desc: tt.block_number, desc: tt.log_index) + |> handle_paging_options(paging_options) + |> Chain.wrapped_union_subquery() + + to_address_hash_query + |> union(^from_address_hash_query) + |> Chain.wrapped_union_subquery() + |> order_by([tt], desc: tt.block_number, desc: tt.log_index) + |> limit(^paging_options.page_size) + end end def filter_by_direction(query, :to, address_hash) do @@ -330,11 +357,6 @@ defmodule Explorer.Chain.TokenTransfer do |> where([tt], tt.from_address_hash == ^address_hash) end - def filter_by_direction(query, _, address_hash) do - query - |> where([tt], tt.from_address_hash == ^address_hash or tt.to_address_hash == ^address_hash) - end - def filter_by_type(query, []), do: query def filter_by_type(query, token_types) when is_list(token_types) do diff --git a/apps/explorer/priv/repo/migrations/20240308123508_token_transfers_add_from_address_hash_block_number_index.exs b/apps/explorer/priv/repo/migrations/20240308123508_token_transfers_add_from_address_hash_block_number_index.exs new file mode 100644 index 000000000000..5d7d27f14474 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240308123508_token_transfers_add_from_address_hash_block_number_index.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.TokenTransfersAddFromAddressHashBlockNumberIndex do + use Ecto.Migration + @disable_ddl_transaction true + @disable_migration_lock true + + def change do + create_if_not_exists(index(:token_transfers, [:from_address_hash, :block_number], concurrently: true)) + end +end diff --git a/apps/explorer/priv/repo/migrations/20240313195728_token_transfers_add_to_address_hash_block_number_index.exs b/apps/explorer/priv/repo/migrations/20240313195728_token_transfers_add_to_address_hash_block_number_index.exs new file mode 100644 index 000000000000..cef07e10e777 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240313195728_token_transfers_add_to_address_hash_block_number_index.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.TokenTransfersAddToAddressHashBlockNumberIndex do + use Ecto.Migration + @disable_ddl_transaction true + @disable_migration_lock true + + def change do + create_if_not_exists(index(:token_transfers, [:to_address_hash, :block_number], concurrently: true)) + end +end diff --git a/apps/indexer/lib/indexer/transform/token_transfers.ex b/apps/indexer/lib/indexer/transform/token_transfers.ex index 5acc4ce0fd7a..8d7f7f66ba3b 100644 --- a/apps/indexer/lib/indexer/transform/token_transfers.ex +++ b/apps/indexer/lib/indexer/transform/token_transfers.ex @@ -178,13 +178,16 @@ defmodule Indexer.Transform.TokenTransfers do when not is_nil(second_topic) and not is_nil(third_topic) do [amount] = Helper.decode_data(log.data, [{:uint, 256}]) + from_address_hash = truncate_address_hash(log.second_topic) + to_address_hash = truncate_address_hash(log.third_topic) + token_transfer = %{ amount: Decimal.new(amount || 0), block_number: log.block_number, block_hash: log.block_hash, log_index: log.index, - from_address_hash: truncate_address_hash(log.second_topic), - to_address_hash: truncate_address_hash(log.third_topic), + from_address_hash: from_address_hash, + to_address_hash: to_address_hash, token_contract_address_hash: log.address_hash, transaction_hash: log.transaction_hash, token_ids: nil, @@ -237,12 +240,15 @@ defmodule Indexer.Transform.TokenTransfers do when not is_nil(second_topic) and not is_nil(third_topic) and not is_nil(fourth_topic) do [token_id] = Helper.decode_data(fourth_topic, [{:uint, 256}]) + from_address_hash = truncate_address_hash(log.second_topic) + to_address_hash = truncate_address_hash(log.third_topic) + token_transfer = %{ block_number: log.block_number, log_index: log.index, block_hash: log.block_hash, - from_address_hash: truncate_address_hash(log.second_topic), - to_address_hash: truncate_address_hash(log.third_topic), + from_address_hash: from_address_hash, + to_address_hash: to_address_hash, token_contract_address_hash: log.address_hash, token_ids: [token_id || 0], transaction_hash: log.transaction_hash, @@ -302,12 +308,15 @@ defmodule Indexer.Transform.TokenTransfers do if is_nil(token_ids) or token_ids == [] or is_nil(values) or values == [] do nil else + from_address_hash = truncate_address_hash(third_topic) + to_address_hash = truncate_address_hash(fourth_topic) + token_transfer = %{ block_number: log.block_number, block_hash: log.block_hash, log_index: log.index, - from_address_hash: truncate_address_hash(third_topic), - to_address_hash: truncate_address_hash(fourth_topic), + from_address_hash: from_address_hash, + to_address_hash: to_address_hash, token_contract_address_hash: log.address_hash, transaction_hash: log.transaction_hash, token_type: "ERC-1155", @@ -327,13 +336,16 @@ defmodule Indexer.Transform.TokenTransfers do def parse_erc1155_params(%{third_topic: third_topic, fourth_topic: fourth_topic, data: data} = log) do [token_id, value] = Helper.decode_data(data, [{:uint, 256}, {:uint, 256}]) + from_address_hash = truncate_address_hash(third_topic) + to_address_hash = truncate_address_hash(fourth_topic) + token_transfer = %{ amount: value, block_number: log.block_number, block_hash: log.block_hash, log_index: log.index, - from_address_hash: truncate_address_hash(third_topic), - to_address_hash: truncate_address_hash(fourth_topic), + from_address_hash: from_address_hash, + to_address_hash: to_address_hash, token_contract_address_hash: log.address_hash, transaction_hash: log.transaction_hash, token_type: "ERC-1155", From 208192d9185dbe89c2674c009cacf7b7f34fb3b6 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Fri, 15 Mar 2024 12:26:58 +0300 Subject: [PATCH 595/607] Add last output root size counter (#9532) --- CHANGELOG.md | 1 + .../controllers/api/v2/stats_controller.ex | 38 ++++-- apps/explorer/config/runtime/test.exs | 1 + apps/explorer/lib/explorer/application.ex | 1 + apps/explorer/lib/explorer/chain.ex | 6 +- .../counters/last_output_root_size_counter.ex | 112 ++++++++++++++++++ .../last_output_root_size_counter_test.exs | 47 ++++++++ apps/explorer/test/support/factory.ex | 30 +++++ config/runtime.exs | 5 + 9 files changed, 228 insertions(+), 13 deletions(-) create mode 100644 apps/explorer/lib/explorer/counters/last_output_root_size_counter.ex create mode 100644 apps/explorer/test/explorer/counters/last_output_root_size_counter_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad4cad924c4..9d787745b4a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - [#9631](https://github.com/blockscout/blockscout/pull/9631) - Initial support of zksync chain type +- [#9532](https://github.com/blockscout/blockscout/pull/9532) - Add last output root size counter - [#9490](https://github.com/blockscout/blockscout/pull/9490) - Add blob transaction counter and filter in block view - [#9486](https://github.com/blockscout/blockscout/pull/9486) - Massive blocks fetcher - [#9473](https://github.com/blockscout/blockscout/pull/9473) - Add user_op interpretation diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex index 6ba3b57e7b5c..f4bb234f2494 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex @@ -3,11 +3,10 @@ defmodule BlockScoutWeb.API.V2.StatsController do alias BlockScoutWeb.API.V2.Helper alias BlockScoutWeb.Chain.MarketHistoryChartController - alias EthereumJSONRPC.Variant alias Explorer.{Chain, Market} alias Explorer.Chain.Address.Counters alias Explorer.Chain.Cache.Block, as: BlockCache - alias Explorer.Chain.Cache.{GasPriceOracle, GasUsage, RootstockLockedBTC} + alias Explorer.Chain.Cache.{GasPriceOracle, GasUsage} alias Explorer.Chain.Cache.Transaction, as: TransactionCache alias Explorer.Chain.Supply.RSK alias Explorer.Chain.Transaction.History.TransactionStats @@ -78,7 +77,7 @@ defmodule BlockScoutWeb.API.V2.StatsController do "tvl" => exchange_rate.tvl_usd, "network_utilization_percentage" => network_utilization_percentage() } - |> add_rootstock_locked_btc() + |> add_chain_type_fields() |> backward_compatibility(conn) ) end @@ -148,15 +147,6 @@ defmodule BlockScoutWeb.API.V2.StatsController do }) end - defp add_rootstock_locked_btc(stats) do - with "rsk" <- Variant.get(), - rootstock_locked_btc when not is_nil(rootstock_locked_btc) <- RootstockLockedBTC.get_locked_value() do - stats |> Map.put("rootstock_locked_btc", rootstock_locked_btc) - else - _ -> stats - end - end - defp backward_compatibility(response, conn) do case Conn.get_req_header(conn, "updated-gas-oracle") do ["true"] -> @@ -170,4 +160,28 @@ defmodule BlockScoutWeb.API.V2.StatsController do end) end end + + case Application.compile_env(:explorer, :chain_type) do + "rsk" -> + defp add_chain_type_fields(response) do + alias Explorer.Chain.Cache.RootstockLockedBTC + + case RootstockLockedBTC.get_locked_value() do + rootstock_locked_btc when not is_nil(rootstock_locked_btc) -> + response |> Map.put("rootstock_locked_btc", rootstock_locked_btc) + + _ -> + response + end + end + + "optimism" -> + defp add_chain_type_fields(response) do + import Explorer.Counters.LastOutputRootSizeCounter, only: [fetch: 1] + response |> Map.put("last_output_root_size", fetch(@api_true)) + end + + _ -> + defp add_chain_type_fields(response), do: response + end end diff --git a/apps/explorer/config/runtime/test.exs b/apps/explorer/config/runtime/test.exs index 27e34b729c45..c54b7129378f 100644 --- a/apps/explorer/config/runtime/test.exs +++ b/apps/explorer/config/runtime/test.exs @@ -18,6 +18,7 @@ config :explorer, Explorer.Chain.Transaction.History.Historian, enabled: false config :explorer, Explorer.Market.History.Historian, enabled: false config :explorer, Explorer.Counters.AddressesCounter, enabled: false, enable_consolidation: false +config :explorer, Explorer.Counters.LastOutputRootSizeCounter, enabled: false, enable_consolidation: false config :explorer, Explorer.Chain.Cache.ContractsCounter, enabled: false, enable_consolidation: false config :explorer, Explorer.Chain.Cache.NewContractsCounter, enabled: false, enable_consolidation: false config :explorer, Explorer.Chain.Cache.VerifiedContractsCounter, enabled: false, enable_consolidation: false diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index f7faba77acb6..b5d33647d200 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -118,6 +118,7 @@ defmodule Explorer.Application do configure(Explorer.Counters.BlockBurntFeeCounter), configure(Explorer.Counters.BlockPriorityFeeCounter), configure(Explorer.Counters.AverageBlockTime), + configure(Explorer.Counters.LastOutputRootSizeCounter), configure(Explorer.Validator.MetadataProcessor), configure(Explorer.Tags.AddressTag.Cataloger), configure(MinMissingBlockNumber), diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 6e58566709e0..fb9ee353dccd 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -2120,7 +2120,11 @@ defmodule Explorer.Chain do select: last_fetched_counter.value ) - select_repo(options).one(query) || Decimal.new(0) + if options[:nullable] do + select_repo(options).one(query) + else + select_repo(options).one(query) || Decimal.new(0) + end end defp block_status({number, timestamp}) do diff --git a/apps/explorer/lib/explorer/counters/last_output_root_size_counter.ex b/apps/explorer/lib/explorer/counters/last_output_root_size_counter.ex new file mode 100644 index 000000000000..c910dbe6dae7 --- /dev/null +++ b/apps/explorer/lib/explorer/counters/last_output_root_size_counter.ex @@ -0,0 +1,112 @@ +defmodule Explorer.Counters.LastOutputRootSizeCounter do + @moduledoc """ + Caches number of transactions in last output root. + + It loads the count asynchronously and in a time interval of :cache_period (default to 5 minutes). + """ + + use GenServer + + import Ecto.Query + + alias Explorer.{Chain, Repo} + alias Explorer.Chain.Optimism.OutputRoot + alias Explorer.Chain.Transaction + + @counter_type "last_output_root_size_count" + + @doc """ + Starts a process to periodically update the counter. + """ + @spec start_link(term()) :: GenServer.on_start() + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + @impl true + def init(_args) do + {:ok, %{consolidate?: enable_consolidation?()}, {:continue, :ok}} + end + + defp schedule_next_consolidation do + Process.send_after(self(), :consolidate, cache_interval()) + end + + @impl true + def handle_continue(:ok, %{consolidate?: true} = state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @impl true + def handle_continue(:ok, state) do + {:noreply, state} + end + + @impl true + def handle_info(:consolidate, state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @doc """ + Fetches the value for a `#{@counter_type}` counter type from the `last_fetched_counters` table. + """ + def fetch(options) do + Chain.get_last_fetched_counter(@counter_type, options |> Keyword.put_new(:nullable, true)) + end + + @doc """ + Consolidates the info by populating the `last_fetched_counters` table with the current database information. + """ + def consolidate do + output_root_query = + from(root in OutputRoot, + select: {root.l2_block_number}, + order_by: [desc: root.l2_output_index], + limit: 2 + ) + + count = + case output_root_query |> Repo.all() do + [{last_block_number}, {prev_block_number}] -> + query = + from(transaction in Transaction, + where: + not is_nil(transaction.block_hash) and transaction.block_number > ^prev_block_number and + transaction.block_number <= ^last_block_number, + select: count(transaction.hash) + ) + + Repo.one!(query, timeout: :infinity) + + _ -> + nil + end + + Chain.upsert_last_fetched_counter(%{ + counter_type: @counter_type, + value: count + }) + end + + @doc """ + Returns a boolean that indicates whether consolidation is enabled + + In order to choose whether or not to enable the scheduler and the initial + consolidation, change the following Explorer config: + + `config :explorer, #{__MODULE__}, enable_consolidation: true` + + to: + + `config :explorer, #{__MODULE__}, enable_consolidation: false` + """ + def enable_consolidation?, do: Application.get_env(:explorer, __MODULE__)[:enable_consolidation] + + defp cache_interval, do: Application.get_env(:explorer, __MODULE__)[:cache_period] +end diff --git a/apps/explorer/test/explorer/counters/last_output_root_size_counter_test.exs b/apps/explorer/test/explorer/counters/last_output_root_size_counter_test.exs new file mode 100644 index 000000000000..4f00da7cb23a --- /dev/null +++ b/apps/explorer/test/explorer/counters/last_output_root_size_counter_test.exs @@ -0,0 +1,47 @@ +defmodule Explorer.Counters.LastOutputRootSizeCounterTest do + use Explorer.DataCase + + alias Explorer.Counters.LastOutputRootSizeCounter + + if Application.compile_env(:explorer, :chain_type) == "optimism" do + test "populates the cache with the number of transactions in last output root" do + first_block = insert(:block) + + insert(:op_output_root, l2_block_number: first_block.number) + + second_block = insert(:block, number: first_block.number + 10) + insert(:op_output_root, l2_block_number: second_block.number) + + insert(:transaction) |> with_block(first_block) + insert(:transaction) |> with_block(second_block) + insert(:transaction) |> with_block(second_block) + + start_supervised!(LastOutputRootSizeCounter) + LastOutputRootSizeCounter.consolidate() + + assert LastOutputRootSizeCounter.fetch([]) == Decimal.new("2") + end + + test "does not count transactions that are not in output root yet" do + first_block = insert(:block) + + insert(:op_output_root, l2_block_number: first_block.number) + + second_block = insert(:block, number: first_block.number + 10) + insert(:op_output_root, l2_block_number: second_block.number) + + insert(:transaction) |> with_block(first_block) + insert(:transaction) |> with_block(second_block) + insert(:transaction) |> with_block(second_block) + + third_block = insert(:block, number: second_block.number + 1) + insert(:transaction) |> with_block(third_block) + insert(:transaction) |> with_block(third_block) + + start_supervised!(LastOutputRootSizeCounter) + LastOutputRootSizeCounter.consolidate() + + assert LastOutputRootSizeCounter.fetch([]) == Decimal.new("2") + end + end +end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 37302050684f..0c9a98e43a47 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -48,6 +48,8 @@ defmodule Explorer.Factory do Withdrawal } + alias Explorer.Chain.Optimism.OutputRoot + alias Explorer.SmartContract.Helper alias Explorer.Tags.{AddressTag, AddressToTag} alias Explorer.Market.MarketHistory @@ -1116,6 +1118,34 @@ defmodule Explorer.Factory do } end + def op_output_root_factory do + %OutputRoot{ + l2_output_index: op_output_root_l2_output_index(), + l2_block_number: insert(:block) |> Map.get(:number), + l1_transaction_hash: transaction_hash(), + l1_timestamp: DateTime.utc_now(), + l1_block_number: op_output_root_l1_block_number(), + output_root: op_output_root_hash() + } + end + + defp op_output_root_l2_output_index do + sequence("op_output_root_l2_output_index", & &1) + end + + defp op_output_root_l1_block_number do + sequence("op_output_root_l1_block_number", & &1) + end + + defp op_output_root_hash do + {:ok, hash} = + "op_output_root_hash" + |> sequence(& &1) + |> Hash.Full.cast() + + hash + end + def random_bool, do: Enum.random([true, false]) def validator_stability_factory do diff --git a/config/runtime.exs b/config/runtime.exs index 5cac7d87b6e9..96e2938e4499 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -291,6 +291,11 @@ config :explorer, Explorer.Counters.AddressTokenUsdSum, config :explorer, Explorer.Counters.AddressTokenTransfersCounter, cache_period: ConfigHelper.parse_time_env_var("CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD", "1h") +config :explorer, Explorer.Counters.LastOutputRootSizeCounter, + enabled: true, + enable_consolidation: true, + cache_period: ConfigHelper.parse_time_env_var("CACHE_OPTIMISM_LAST_OUTPUT_ROOT_SIZE_COUNTER_PERIOD", "5m") + config :explorer, Explorer.ExchangeRates, store: :ets, enabled: !disable_exchange_rates?, From 010758fc0fc353e7c9e6b126dc800013dda7d2bc Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Fri, 15 Mar 2024 13:06:05 +0300 Subject: [PATCH 596/607] Fix no function clause matching in BENS.item_to_address_hash_strings/1 (#9640) --- CHANGELOG.md | 1 + .../lib/explorer/microservice_interfaces/bens.ex | 14 ++++---------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d787745b4a9..795c872eed70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ ### Fixes +- [#9640](https://github.com/blockscout/blockscout/pull/9640) - Fix no function clause matching in `BENS.item_to_address_hash_strings/1` - [#9629](https://github.com/blockscout/blockscout/pull/9629) - Don't insert pbo for not inserted blocks - [#9601](https://github.com/blockscout/blockscout/pull/9601) - Fix token instance transform for some unconventional tokens - [#9597](https://github.com/blockscout/blockscout/pull/9597) - Update token transfers block_consensus by block_number diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex index 8dc28534690b..251919f28900 100644 --- a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -293,17 +293,9 @@ defmodule Explorer.MicroserviceInterfaces.BENS do defp parse_get_address_response(_), do: nil - defp item_to_address_hash_strings(%Transaction{ - to_address_hash: nil, - created_contract_address_hash: created_contract_address_hash, - from_address_hash: from_address_hash - }) do - [to_string(created_contract_address_hash), to_string(from_address_hash)] - end - defp item_to_address_hash_strings(%Transaction{ to_address_hash: to_address_hash, - created_contract_address_hash: nil, + created_contract_address_hash: created_contract_address_hash, from_address_hash: from_address_hash, token_transfers: token_transfers }) do @@ -316,7 +308,9 @@ defmodule Explorer.MicroserviceInterfaces.BENS do [] end - [to_string(to_address_hash), to_string(from_address_hash)] ++ token_transfers_addresses + ([to_address_hash, created_contract_address_hash, from_address_hash] + |> Enum.reject(&is_nil/1) + |> Enum.map(&to_string/1)) ++ token_transfers_addresses end defp item_to_address_hash_strings(%TokenTransfer{ From da5bd2fff32c0449a6617df2384b19043e876141 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Fri, 15 Mar 2024 17:18:56 +0400 Subject: [PATCH 597/607] Re-add missing blob transactions count (#9644) * fix: add missing blob tx count * chore: refactor * chore: changelog --- CHANGELOG.md | 2 +- .../views/api/v2/block_view.ex | 6 ------ .../views/api/v2/ethereum_view.ex | 20 +++++++++++++------ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 795c872eed70..6c79228c5abe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - [#9631](https://github.com/blockscout/blockscout/pull/9631) - Initial support of zksync chain type - [#9532](https://github.com/blockscout/blockscout/pull/9532) - Add last output root size counter -- [#9490](https://github.com/blockscout/blockscout/pull/9490) - Add blob transaction counter and filter in block view +- [#9490](https://github.com/blockscout/blockscout/pull/9490), [#9644](https://github.com/blockscout/blockscout/pull/9644) - Add blob transaction counter and filter in block view - [#9486](https://github.com/blockscout/blockscout/pull/9486) - Massive blocks fetcher - [#9473](https://github.com/blockscout/blockscout/pull/9473) - Add user_op interpretation - [#9461](https://github.com/blockscout/blockscout/pull/9461) - Fetch blocks without internal transactions backwards diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex index 35081b8ab27f..ba23d27f3d60 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex @@ -96,12 +96,6 @@ defmodule BlockScoutWeb.API.V2.BlockView do def count_transactions(%Block{transactions: txs}) when is_list(txs), do: Enum.count(txs) def count_transactions(_), do: nil - def count_blob_transactions(%Block{transactions: txs}) when is_list(txs), - # EIP-2718 blob transaction type - do: Enum.count(txs, &(&1.type == 3)) - - def count_blob_transactions(_), do: nil - def count_withdrawals(%Block{withdrawals: withdrawals}) when is_list(withdrawals), do: Enum.count(withdrawals) def count_withdrawals(_), do: nil diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/ethereum_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/ethereum_view.ex index 7fce94ef6297..6f7af666458a 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/ethereum_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/ethereum_view.ex @@ -1,6 +1,12 @@ defmodule BlockScoutWeb.API.V2.EthereumView do alias Explorer.Chain.{Block, Transaction} + defp count_blob_transactions(%Block{transactions: txs}) when is_list(txs), + # EIP-2718 blob transaction type + do: Enum.count(txs, &(&1.type == 3)) + + defp count_blob_transactions(_), do: nil + def extend_transaction_json_response(out_json, %Transaction{} = transaction) do case Map.get(transaction, :beacon_blob_transaction) do nil -> @@ -23,19 +29,21 @@ defmodule BlockScoutWeb.API.V2.EthereumView do blob_gas_used = Map.get(block, :blob_gas_used) excess_blob_gas = Map.get(block, :excess_blob_gas) + extended_out_json = + out_json + |> Map.put("blob_tx_count", count_blob_transactions(block)) + |> Map.put("blob_gas_used", blob_gas_used) + |> Map.put("excess_blob_gas", excess_blob_gas) + if single_block? do blob_gas_price = Block.transaction_blob_gas_price(block.transactions) burnt_blob_transaction_fees = Decimal.mult(blob_gas_used || 0, blob_gas_price || 0) - out_json - |> Map.put("blob_gas_used", blob_gas_used) - |> Map.put("excess_blob_gas", excess_blob_gas) + extended_out_json |> Map.put("blob_gas_price", blob_gas_price) |> Map.put("burnt_blob_fees", burnt_blob_transaction_fees) else - out_json - |> Map.put("blob_gas_used", blob_gas_used) - |> Map.put("excess_blob_gas", excess_blob_gas) + extended_out_json end end end From 675fdebfc9e631e5abec83eb9023f19d00ce3af1 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Fri, 15 Mar 2024 16:20:28 +0300 Subject: [PATCH 598/607] Fix fetch_coin_balance query to compare between balances with values (#9638) * Fix fetch_coin_balance query to compare between balances with non-nil values * Review fix --- CHANGELOG.md | 1 + .../channels/address_channel.ex | 32 +++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c79228c5abe..ce5585a4dcd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ ### Fixes - [#9640](https://github.com/blockscout/blockscout/pull/9640) - Fix no function clause matching in `BENS.item_to_address_hash_strings/1` +- [#9638](https://github.com/blockscout/blockscout/pull/9638) - Fix fetch_coin_balance query to compare between balances with values - [#9629](https://github.com/blockscout/blockscout/pull/9629) - Don't insert pbo for not inserted blocks - [#9601](https://github.com/blockscout/blockscout/pull/9601) - Fix token instance transform for some unconventional tokens - [#9597](https://github.com/blockscout/blockscout/pull/9597) - Update token transfers block_consensus by block_number diff --git a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex index 89795fb8a76f..378e09e02dd2 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex @@ -193,11 +193,13 @@ defmodule BlockScoutWeb.AddressChannel do ) do coin_balance = Chain.get_coin_balance(socket.assigns.address_hash, block_number) - rendered_coin_balance = AddressViewAPI.render("coin_balance.json", %{coin_balance: coin_balance}) + if coin_balance.value && coin_balance.delta do + rendered_coin_balance = AddressViewAPI.render("coin_balance.json", %{coin_balance: coin_balance}) - push(socket, "coin_balance", %{coin_balance: rendered_coin_balance}) + push(socket, "coin_balance", %{coin_balance: rendered_coin_balance}) - push_current_coin_balance(socket, block_number, coin_balance) + push_current_coin_balance(socket, block_number, coin_balance) + end {:noreply, socket} end @@ -207,19 +209,21 @@ defmodule BlockScoutWeb.AddressChannel do Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) - rendered_coin_balance = - View.render_to_string( - AddressCoinBalanceView, - "_coin_balances.html", - conn: socket, - coin_balance: coin_balance - ) + if coin_balance.value && coin_balance.delta do + rendered_coin_balance = + View.render_to_string( + AddressCoinBalanceView, + "_coin_balances.html", + conn: socket, + coin_balance: coin_balance + ) - push(socket, "coin_balance", %{ - coin_balance_html: rendered_coin_balance - }) + push(socket, "coin_balance", %{ + coin_balance_html: rendered_coin_balance + }) - push_current_coin_balance(socket, block_number, coin_balance) + push_current_coin_balance(socket, block_number, coin_balance) + end {:noreply, socket} end From 4bb0809aa7cd3ddd58640a6d25da2861100ad2ee Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 15 Mar 2024 16:21:57 +0300 Subject: [PATCH 599/607] Update CHANGELOG record --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce5585a4dcd6..e833b1c98aad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ ### Fixes - [#9640](https://github.com/blockscout/blockscout/pull/9640) - Fix no function clause matching in `BENS.item_to_address_hash_strings/1` -- [#9638](https://github.com/blockscout/blockscout/pull/9638) - Fix fetch_coin_balance query to compare between balances with values +- [#9638](https://github.com/blockscout/blockscout/pull/9638) - Do not broadcast coin balance changes with empty value/delta - [#9629](https://github.com/blockscout/blockscout/pull/9629) - Don't insert pbo for not inserted blocks - [#9601](https://github.com/blockscout/blockscout/pull/9601) - Fix token instance transform for some unconventional tokens - [#9597](https://github.com/blockscout/blockscout/pull/9597) - Update token transfers block_consensus by block_number From 91fbfa8e7814344d2d0af858c130835d87eda797 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop <105209995+Qwerty5Uiop@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:54:06 +0400 Subject: [PATCH 600/607] Reset missing ranges collector to max number after the cycle is done (#9635) --- CHANGELOG.md | 1 + .../indexer/block/catchup/missing_ranges_collector.ex | 9 +-------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e833b1c98aad..508748c8703c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - [#9640](https://github.com/blockscout/blockscout/pull/9640) - Fix no function clause matching in `BENS.item_to_address_hash_strings/1` - [#9638](https://github.com/blockscout/blockscout/pull/9638) - Do not broadcast coin balance changes with empty value/delta +- [#9635](https://github.com/blockscout/blockscout/pull/9635) - Reset missing ranges collector to max number after the cycle is done - [#9629](https://github.com/blockscout/blockscout/pull/9629) - Don't insert pbo for not inserted blocks - [#9601](https://github.com/blockscout/blockscout/pull/9601) - Fix token instance transform for some unconventional tokens - [#9597](https://github.com/blockscout/blockscout/pull/9597) - Update token transfers block_consensus by block_number diff --git a/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex b/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex index f8c97c3d72ef..d4c19358eb29 100644 --- a/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex +++ b/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex @@ -145,14 +145,7 @@ defmodule Indexer.Block.Catchup.MissingRangesCollector do {:noreply, %{state | min_fetched_block_number: new_min_number}} else Process.send_after(self(), :update_past, @past_check_interval * 100) - {:noreply, %{state | min_fetched_block_number: reset_min_fetched_block_number(state.max_fetched_block_number)}} - end - end - - defp reset_min_fetched_block_number(max_fetched_block_number) do - case MissingBlockRange.fetch_min_max() do - %{min: nil} -> max_fetched_block_number - %{min: min} -> min + {:noreply, %{state | min_fetched_block_number: state.max_fetched_block_number}} end end From c1ac435b9b780f9d3aff34be02538ca1d86d4bcd Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Fri, 15 Mar 2024 17:55:47 +0400 Subject: [PATCH 601/607] Fix infinite retries for orphaned blobs (#9620) * fix: implement finite retries for orphaned blobs * chore: changelog --- CHANGELOG.md | 1 + .../lib/indexer/fetcher/beacon/blob.ex | 26 ++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 508748c8703c..093ffc559a04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - [#9638](https://github.com/blockscout/blockscout/pull/9638) - Do not broadcast coin balance changes with empty value/delta - [#9635](https://github.com/blockscout/blockscout/pull/9635) - Reset missing ranges collector to max number after the cycle is done - [#9629](https://github.com/blockscout/blockscout/pull/9629) - Don't insert pbo for not inserted blocks +- [#9620](https://github.com/blockscout/blockscout/pull/9620) - Fix infinite retries for orphaned blobs - [#9601](https://github.com/blockscout/blockscout/pull/9601) - Fix token instance transform for some unconventional tokens - [#9597](https://github.com/blockscout/blockscout/pull/9597) - Update token transfers block_consensus by block_number - [#9596](https://github.com/blockscout/blockscout/pull/9596) - Fix logging diff --git a/apps/indexer/lib/indexer/fetcher/beacon/blob.ex b/apps/indexer/lib/indexer/fetcher/beacon/blob.ex index ba7282110850..d35a680a7d13 100644 --- a/apps/indexer/lib/indexer/fetcher/beacon/blob.ex +++ b/apps/indexer/lib/indexer/fetcher/beacon/blob.ex @@ -19,6 +19,8 @@ defmodule Indexer.Fetcher.Beacon.Blob do @default_max_batch_size 10 @default_max_concurrency 1 + @default_retries_limit 2 + @default_retry_deadline :timer.minutes(5) @doc """ Asynchronously fetches blobs for given `block_timestamp`. @@ -87,24 +89,40 @@ defmodule Indexer.Fetcher.Beacon.Blob do Logger.debug(fn -> "fetching" end) entries - |> Enum.map(×tamp_to_slot(&1, state)) + |> Enum.map(&entry_to_slot(&1, state)) |> Client.get_blob_sidecars() |> case do {:ok, fetched_blobs, retry_indices} -> run_fetched_blobs(fetched_blobs) - if Enum.empty?(retry_indices) do + retry_entities = + retry_indices + |> Enum.map(&Enum.at(entries, &1)) + |> Enum.filter(&should_retry?/1) + |> Enum.map(&increment_retry_count/1) + + if Enum.empty?(retry_entities) do :ok else - {:retry, retry_indices |> Enum.map(&Enum.at(entries, &1))} + {:retry, retry_entities} end end end defp entry(block_timestamp) do - DateTime.to_unix(block_timestamp) + {DateTime.to_unix(block_timestamp), 0} end + defp increment_retry_count({block_timestamp, retry_count}), do: {block_timestamp, retry_count + 1} + + # Stop retrying after 2 failed retries for slots older than 5 minutes + defp should_retry?({block_timestamp, retry_count}), + do: + retry_count < @default_retries_limit || + block_timestamp + @default_retry_deadline > DateTime.to_unix(DateTime.utc_now()) + + defp entry_to_slot({block_timestamp, _}, state), do: timestamp_to_slot(block_timestamp, state) + @doc """ Converts block timestamp to the slot number. """ From 16797ec1b906e8126b2963f4661c798fcff963a1 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop <105209995+Qwerty5Uiop@users.noreply.github.com> Date: Fri, 15 Mar 2024 18:19:50 +0400 Subject: [PATCH 602/607] Separate errors by type in EndpointAvailabilityObserver (#9511) * Separate errors by type in EndpointAvailabilityObserver * Add eth_call fallback url * Update url unavailable warning message * Update switching back from fallback url message --- CHANGELOG.md | 1 + .../api/v2/smart_contract_controller.ex | 7 +- apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex | 4 +- .../lib/ethereum_jsonrpc/http.ex | 25 ++-- .../utility/endpoint_availability_checker.ex | 27 ++-- .../utility/endpoint_availability_observer.ex | 117 +++++++++++----- apps/explorer/config/dev/arbitrum.exs | 1 + apps/explorer/config/dev/besu.exs | 1 + apps/explorer/config/dev/erigon.exs | 1 + apps/explorer/config/dev/filecoin.exs | 1 + apps/explorer/config/dev/ganache.exs | 1 + apps/explorer/config/dev/geth.exs | 1 + apps/explorer/config/dev/nethermind.exs | 1 + apps/explorer/config/dev/rsk.exs | 1 + apps/explorer/config/prod/arbitrum.exs | 1 + apps/explorer/config/prod/besu.exs | 1 + apps/explorer/config/prod/erigon.exs | 1 + apps/explorer/config/prod/filecoin.exs | 1 + apps/explorer/config/prod/ganache.exs | 1 + apps/explorer/config/prod/geth.exs | 1 + apps/explorer/config/prod/nethermind.exs | 1 + apps/explorer/config/prod/rsk.exs | 1 + .../lib/explorer/smart_contract/reader.ex | 127 +++++++++++++----- apps/indexer/config/dev/arbitrum.exs | 1 + apps/indexer/config/dev/besu.exs | 1 + apps/indexer/config/dev/erigon.exs | 1 + apps/indexer/config/dev/filecoin.exs | 1 + apps/indexer/config/dev/ganache.exs | 1 + apps/indexer/config/dev/geth.exs | 1 + apps/indexer/config/dev/nethermind.exs | 1 + apps/indexer/config/dev/rsk.exs | 1 + apps/indexer/config/prod/arbitrum.exs | 1 + apps/indexer/config/prod/besu.exs | 1 + apps/indexer/config/prod/erigon.exs | 1 + apps/indexer/config/prod/filecoin.exs | 1 + apps/indexer/config/prod/ganache.exs | 1 + apps/indexer/config/prod/geth.exs | 1 + apps/indexer/config/prod/nethermind.exs | 1 + apps/indexer/config/prod/rsk.exs | 1 + docker-compose/envs/common-blockscout.env | 1 + 40 files changed, 249 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 093ffc559a04..d0725a990884 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - [#9631](https://github.com/blockscout/blockscout/pull/9631) - Initial support of zksync chain type - [#9532](https://github.com/blockscout/blockscout/pull/9532) - Add last output root size counter +- [#9511](https://github.com/blockscout/blockscout/pull/9511) - Separate errors by type in EndpointAvailabilityObserver - [#9490](https://github.com/blockscout/blockscout/pull/9490), [#9644](https://github.com/blockscout/blockscout/pull/9644) - Add blob transaction counter and filter in block view - [#9486](https://github.com/blockscout/blockscout/pull/9486) - Massive blocks fetcher - [#9473](https://github.com/blockscout/blockscout/pull/9473) - Add user_op interpretation diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex index 864a7243c5b8..9dbc80236b08 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex @@ -49,7 +49,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do custom_abi <- AddressView.fetch_custom_abi(conn, address_hash_string), {:not_found, true} <- {:not_found, AddressView.check_custom_abi_for_having_read_functions(custom_abi)} do read_only_functions_from_abi = - Reader.read_only_functions_from_abi_with_sender(custom_abi.abi, address_hash, params["from"]) + Reader.read_only_functions_from_abi_with_sender(custom_abi.abi, address_hash, params["from"], @api_true) read_functions_required_wallet_from_abi = Reader.read_functions_required_wallet_from_abi(custom_abi.abi) @@ -61,7 +61,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do def methods_read(conn, %{"address_hash" => address_hash_string} = params) do with {:ok, address_hash, smart_contract} <- validate_smart_contract(params, address_hash_string) do - read_only_functions_from_abi = Reader.read_only_functions(smart_contract, address_hash, params["from"]) + read_only_functions_from_abi = Reader.read_only_functions(smart_contract, address_hash, params["from"], @api_true) read_functions_required_wallet_from_abi = Reader.read_functions_required_wallet(smart_contract) @@ -166,7 +166,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do address_hash, %{method_id: params["method_id"], args: prepare_args(args)}, params["from"], - custom_abi.abi + custom_abi.abi, + @api_true ) else Reader.query_function_with_names( diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex index cd155ed3d231..9cb48cea1732 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex @@ -462,10 +462,10 @@ defmodule EthereumJSONRPC do end defp maybe_replace_url(url, _replace_url, EthereumJSONRPC.HTTP), do: url - defp maybe_replace_url(url, replace_url, _), do: EndpointAvailabilityObserver.maybe_replace_url(url, replace_url) + defp maybe_replace_url(url, replace_url, _), do: EndpointAvailabilityObserver.maybe_replace_url(url, replace_url, :ws) defp maybe_inc_error_count(_url, _arguments, EthereumJSONRPC.HTTP), do: :ok - defp maybe_inc_error_count(url, arguments, _), do: EndpointAvailabilityObserver.inc_error_count(url, arguments) + defp maybe_inc_error_count(url, arguments, _), do: EndpointAvailabilityObserver.inc_error_count(url, arguments, :ws) @doc """ Converts `t:quantity/0` to `t:non_neg_integer/0`. diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex index f7b607e77e8c..e1704d1068a8 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex @@ -23,7 +23,7 @@ defmodule EthereumJSONRPC.HTTP do def json_rpc(%{method: method} = request, options) when is_map(request) do json = encode_json(request) http = Keyword.fetch!(options, :http) - url = url(options, method) + {url_type, url} = url(options, method) http_options = Keyword.fetch!(options, :http_options) with {:ok, %{body: body, status_code: code}} <- http.json_rpc(url, json, headers(), http_options), @@ -33,7 +33,7 @@ defmodule EthereumJSONRPC.HTTP do else error -> named_arguments = [transport: __MODULE__, transport_options: Keyword.delete(options, :method_to_url)] - EndpointAvailabilityObserver.inc_error_count(url, named_arguments) + EndpointAvailabilityObserver.inc_error_count(url, named_arguments, url_type) error end end @@ -65,7 +65,7 @@ defmodule EthereumJSONRPC.HTTP do defp chunked_json_rpc([[%{method: method} | _] = batch | tail] = chunks, options, decoded_response_bodies) when is_list(tail) and is_list(decoded_response_bodies) do http = Keyword.fetch!(options, :http) - url = url(options, method) + {url_type, url} = url(options, method) http_options = Keyword.fetch!(options, :http_options) json = encode_json(batch) @@ -85,7 +85,7 @@ defmodule EthereumJSONRPC.HTTP do {:error, _} = error -> named_arguments = [transport: __MODULE__, transport_options: Keyword.delete(options, :method_to_url)] - EndpointAvailabilityObserver.inc_error_count(url, named_arguments) + EndpointAvailabilityObserver.inc_error_count(url, named_arguments, url_type) error end end @@ -203,12 +203,21 @@ defmodule EthereumJSONRPC.HTTP do with {:ok, method_to_url} <- Keyword.fetch(options, :method_to_url), {:ok, method_atom} <- to_existing_atom(method), {:ok, url} <- Keyword.fetch(method_to_url, method_atom) do - EndpointAvailabilityObserver.maybe_replace_url(url, options[:fallback_trace_url]) + {url_type, fallback_url} = + case method_atom do + :eth_call -> {:eth_call, options[:fallback_eth_call_url]} + _ -> {:trace, options[:fallback_trace_url]} + end + + {url_type, EndpointAvailabilityObserver.maybe_replace_url(url, fallback_url, url_type)} else _ -> - options - |> Keyword.fetch!(:url) - |> EndpointAvailabilityObserver.maybe_replace_url(options[:fallback_url]) + url = + options + |> Keyword.fetch!(:url) + |> EndpointAvailabilityObserver.maybe_replace_url(options[:fallback_url], :http) + + {:http, url} end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/endpoint_availability_checker.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/endpoint_availability_checker.ex index 27cb30921e9a..45f2f687d867 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/endpoint_availability_checker.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/endpoint_availability_checker.ex @@ -4,6 +4,7 @@ defmodule EthereumJSONRPC.Utility.EndpointAvailabilityChecker do """ use GenServer + require Logger alias EthereumJSONRPC.Utility.EndpointAvailabilityObserver @@ -20,26 +21,27 @@ defmodule EthereumJSONRPC.Utility.EndpointAvailabilityChecker do {:ok, %{unavailable_endpoints_arguments: []}} end - def add_endpoint(json_rpc_named_arguments) do - GenServer.cast(__MODULE__, {:add_endpoint, json_rpc_named_arguments}) + def add_endpoint(json_rpc_named_arguments, url_type) do + GenServer.cast(__MODULE__, {:add_endpoint, json_rpc_named_arguments, url_type}) end - def handle_cast({:add_endpoint, named_arguments}, %{unavailable_endpoints_arguments: unavailable} = state) do - {:noreply, %{state | unavailable_endpoints_arguments: [named_arguments | unavailable]}} + def handle_cast({:add_endpoint, named_arguments, url_type}, %{unavailable_endpoints_arguments: unavailable} = state) do + {:noreply, %{state | unavailable_endpoints_arguments: [{named_arguments, url_type} | unavailable]}} end def handle_info(:check, %{unavailable_endpoints_arguments: unavailable_endpoints_arguments} = state) do new_unavailable_endpoints = - Enum.reduce(unavailable_endpoints_arguments, [], fn json_rpc_named_arguments, acc -> + Enum.reduce(unavailable_endpoints_arguments, [], fn {json_rpc_named_arguments, url_type}, acc -> case fetch_latest_block_number(json_rpc_named_arguments) do {:ok, _number} -> url = json_rpc_named_arguments[:transport_options][:url] - EndpointAvailabilityObserver.enable_endpoint(url) - Logger.info("URL #{inspect(url)} is available now, switching back to it") + + EndpointAvailabilityObserver.enable_endpoint(url, url_type) + log_url_available(url, url_type, json_rpc_named_arguments) acc _ -> - [json_rpc_named_arguments | acc] + [{json_rpc_named_arguments, url_type} | acc] end end) @@ -48,6 +50,15 @@ defmodule EthereumJSONRPC.Utility.EndpointAvailabilityChecker do {:noreply, %{state | unavailable_endpoints_arguments: new_unavailable_endpoints}} end + defp log_url_available(url, url_type, json_rpc_named_arguments) do + message_extra = + if EndpointAvailabilityObserver.fallback_url_set?(url_type, json_rpc_named_arguments), + do: ", switching back to it", + else: "" + + Logger.info("URL #{inspect(url)} is available now#{message_extra}") + end + defp fetch_latest_block_number(json_rpc_named_arguments) do {_, arguments_without_fallback} = pop_in(json_rpc_named_arguments, [:transport_options, :fallback_url]) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/endpoint_availability_observer.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/endpoint_availability_observer.ex index 833b2b67d055..fe768190b144 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/endpoint_availability_observer.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/endpoint_availability_observer.ex @@ -20,73 +20,118 @@ defmodule EthereumJSONRPC.Utility.EndpointAvailabilityObserver do def init(_) do schedule_next_cleaning() - {:ok, %{error_counts: %{}, unavailable_endpoints: []}} + {:ok, %{error_counts: %{}, unavailable_endpoints: %{ws: [], trace: [], http: [], eth_call: []}}} end - def inc_error_count(url, json_rpc_named_arguments) do - GenServer.cast(__MODULE__, {:inc_error_count, url, json_rpc_named_arguments}) + def inc_error_count(url, json_rpc_named_arguments, url_type) do + GenServer.cast(__MODULE__, {:inc_error_count, url, json_rpc_named_arguments, url_type}) end - def check_endpoint(url) do - GenServer.call(__MODULE__, {:check_endpoint, url}) + def check_endpoint(url, url_type) do + GenServer.call(__MODULE__, {:check_endpoint, url, url_type}) end - def maybe_replace_url(url, replace_url) do - case check_endpoint(url) do + def maybe_replace_url(url, replace_url, url_type) do + case check_endpoint(url, url_type) do :ok -> url :unavailable -> replace_url || url end end - def enable_endpoint(url) do - GenServer.cast(__MODULE__, {:enable_endpoint, url}) + def enable_endpoint(url, url_type) do + GenServer.cast(__MODULE__, {:enable_endpoint, url, url_type}) end - def handle_call({:check_endpoint, url}, _from, %{unavailable_endpoints: unavailable_endpoints} = state) do - result = if url in unavailable_endpoints, do: :unavailable, else: :ok + def handle_call({:check_endpoint, url, url_type}, _from, %{unavailable_endpoints: unavailable_endpoints} = state) do + result = if url in unavailable_endpoints[url_type], do: :unavailable, else: :ok {:reply, result, state} end - def handle_cast({:inc_error_count, url, json_rpc_named_arguments}, %{error_counts: error_counts} = state) do - current_count = error_counts[url][:count] - unavailable_endpoints = state.unavailable_endpoints - + def handle_cast({:inc_error_count, url, json_rpc_named_arguments, url_type}, state) do new_state = - cond do - url in unavailable_endpoints -> - state - - is_nil(current_count) -> - %{state | error_counts: Map.put(error_counts, url, %{count: 1, last_occasion: now()})} - - current_count + 1 >= @max_error_count -> - EndpointAvailabilityChecker.add_endpoint(put_in(json_rpc_named_arguments[:transport_options][:url], url)) - Logger.warning("URL #{inspect(url)} is unavailable, switching to fallback url") - %{state | error_counts: Map.delete(error_counts, url), unavailable_endpoints: [url | unavailable_endpoints]} - - true -> - %{state | error_counts: Map.put(error_counts, url, %{count: current_count + 1, last_occasion: now()})} - end + if json_rpc_named_arguments[:api?], + do: state, + else: do_increase_error_counts(url, json_rpc_named_arguments, url_type, state) {:noreply, new_state} end - def handle_cast({:enable_endpoint, url}, %{unavailable_endpoints: unavailable_endpoints} = state) do - {:noreply, %{state | unavailable_endpoints: unavailable_endpoints -- [url]}} + def handle_cast({:enable_endpoint, url, url_type}, %{unavailable_endpoints: unavailable_endpoints} = state) do + {:noreply, + %{state | unavailable_endpoints: %{unavailable_endpoints | url_type => unavailable_endpoints[url_type] -- [url]}}} end def handle_info(:clear_old_records, %{error_counts: error_counts} = state) do - new_error_counts = - Enum.reduce(error_counts, %{}, fn {url, %{last_occasion: last_occasion} = record}, acc -> - if now() - last_occasion > @window_duration, do: acc, else: Map.put(acc, url, record) - end) + new_error_counts = Enum.reduce(error_counts, %{}, &do_clear_old_records/2) schedule_next_cleaning() {:noreply, %{state | error_counts: new_error_counts}} end + defp do_clear_old_records({url, counts_by_types}, acc) do + counts_by_types + |> Enum.reduce(%{}, fn {type, %{last_occasion: last_occasion} = record}, acc -> + if now() - last_occasion > @window_duration, do: acc, else: Map.put(acc, type, record) + end) + |> case do + empty_map when empty_map == %{} -> acc + non_empty_map -> Map.put(acc, url, non_empty_map) + end + end + + defp do_increase_error_counts(url, json_rpc_named_arguments, url_type, %{error_counts: error_counts} = state) do + current_count = error_counts[url][url_type][:count] + unavailable_endpoints = state.unavailable_endpoints[url_type] + + cond do + url in unavailable_endpoints -> + state + + is_nil(current_count) -> + %{state | error_counts: Map.put(error_counts, url, %{url_type => %{count: 1, last_occasion: now()}})} + + current_count + 1 >= @max_error_count -> + EndpointAvailabilityChecker.add_endpoint( + put_in(json_rpc_named_arguments[:transport_options][:url], url), + url_type + ) + + log_url_unavailable(url, url_type, json_rpc_named_arguments) + + %{ + state + | error_counts: Map.put(error_counts, url, Map.delete(error_counts[url], url_type)), + unavailable_endpoints: %{state.unavailable_endpoints | url_type => [url | unavailable_endpoints]} + } + + true -> + %{ + state + | error_counts: Map.put(error_counts, url, %{url_type => %{count: current_count + 1, last_occasion: now()}}) + } + end + end + + defp log_url_unavailable(url, url_type, json_rpc_named_arguments) do + fallback_url_message = + if fallback_url_set?(url_type, json_rpc_named_arguments), + do: "switching to fallback #{url_type} url", + else: "and no fallback is set" + + Logger.warning("URL #{inspect(url)} is unavailable, #{fallback_url_message}") + end + + def fallback_url_set?(url_type, json_rpc_named_arguments) do + case url_type do + :http -> not is_nil(json_rpc_named_arguments[:transport_options][:fallback_url]) + :trace -> not is_nil(json_rpc_named_arguments[:transport_options][:fallback_trace_url]) + :eth_call -> not is_nil(json_rpc_named_arguments[:transport_options][:fallback_eth_call_url]) + _ -> false + end + end + defp schedule_next_cleaning do Process.send_after(self(), :clear_old_records, @cleaning_interval) end diff --git a/apps/explorer/config/dev/arbitrum.exs b/apps/explorer/config/dev/arbitrum.exs index dafcd640fe4c..758414fc4d52 100644 --- a/apps/explorer/config/dev/arbitrum.exs +++ b/apps/explorer/config/dev/arbitrum.exs @@ -14,6 +14,7 @@ config :explorer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:8545") ], diff --git a/apps/explorer/config/dev/besu.exs b/apps/explorer/config/dev/besu.exs index 22c0382163e3..9b32da34d7b0 100644 --- a/apps/explorer/config/dev/besu.exs +++ b/apps/explorer/config/dev/besu.exs @@ -15,6 +15,7 @@ config :explorer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/explorer/config/dev/erigon.exs b/apps/explorer/config/dev/erigon.exs index 163f526996f6..5304055919e5 100644 --- a/apps/explorer/config/dev/erigon.exs +++ b/apps/explorer/config/dev/erigon.exs @@ -15,6 +15,7 @@ config :explorer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/explorer/config/dev/filecoin.exs b/apps/explorer/config/dev/filecoin.exs index 68991f7b6910..ea75c71dc462 100644 --- a/apps/explorer/config/dev/filecoin.exs +++ b/apps/explorer/config/dev/filecoin.exs @@ -15,6 +15,7 @@ config :explorer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:1234/rpc/v1", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:1234/rpc/v1"), trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:1234/rpc/v1" diff --git a/apps/explorer/config/dev/ganache.exs b/apps/explorer/config/dev/ganache.exs index f7ddd7cbe37f..ae03df000e59 100644 --- a/apps/explorer/config/dev/ganache.exs +++ b/apps/explorer/config/dev/ganache.exs @@ -14,6 +14,7 @@ config :explorer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:7545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:7545") ], diff --git a/apps/explorer/config/dev/geth.exs b/apps/explorer/config/dev/geth.exs index 97d2ca3afdab..9d9a166f6329 100644 --- a/apps/explorer/config/dev/geth.exs +++ b/apps/explorer/config/dev/geth.exs @@ -15,6 +15,7 @@ config :explorer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/explorer/config/dev/nethermind.exs b/apps/explorer/config/dev/nethermind.exs index 2553a16db492..95ce54a92406 100644 --- a/apps/explorer/config/dev/nethermind.exs +++ b/apps/explorer/config/dev/nethermind.exs @@ -15,6 +15,7 @@ config :explorer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/explorer/config/dev/rsk.exs b/apps/explorer/config/dev/rsk.exs index 699584ea0324..74cb6d98f095 100644 --- a/apps/explorer/config/dev/rsk.exs +++ b/apps/explorer/config/dev/rsk.exs @@ -15,6 +15,7 @@ config :explorer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/explorer/config/prod/arbitrum.exs b/apps/explorer/config/prod/arbitrum.exs index 5f45a1a071db..2ce0af488a63 100644 --- a/apps/explorer/config/prod/arbitrum.exs +++ b/apps/explorer/config/prod/arbitrum.exs @@ -14,6 +14,7 @@ config :explorer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url() ], diff --git a/apps/explorer/config/prod/besu.exs b/apps/explorer/config/prod/besu.exs index a486b5c6dcda..cb641e7244ec 100644 --- a/apps/explorer/config/prod/besu.exs +++ b/apps/explorer/config/prod/besu.exs @@ -15,6 +15,7 @@ config :explorer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/apps/explorer/config/prod/erigon.exs b/apps/explorer/config/prod/erigon.exs index 1ca954c1ef41..e574b5564663 100644 --- a/apps/explorer/config/prod/erigon.exs +++ b/apps/explorer/config/prod/erigon.exs @@ -15,6 +15,7 @@ config :explorer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/apps/explorer/config/prod/filecoin.exs b/apps/explorer/config/prod/filecoin.exs index d48af23462de..00c056dbe6dd 100644 --- a/apps/explorer/config/prod/filecoin.exs +++ b/apps/explorer/config/prod/filecoin.exs @@ -15,6 +15,7 @@ config :explorer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url(), trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") diff --git a/apps/explorer/config/prod/ganache.exs b/apps/explorer/config/prod/ganache.exs index 0956e4133c06..7a0581452b3a 100644 --- a/apps/explorer/config/prod/ganache.exs +++ b/apps/explorer/config/prod/ganache.exs @@ -14,6 +14,7 @@ config :explorer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url() ], diff --git a/apps/explorer/config/prod/geth.exs b/apps/explorer/config/prod/geth.exs index 6080d6f7ca44..90a1a7bb3e80 100644 --- a/apps/explorer/config/prod/geth.exs +++ b/apps/explorer/config/prod/geth.exs @@ -15,6 +15,7 @@ config :explorer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url(), debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/apps/explorer/config/prod/nethermind.exs b/apps/explorer/config/prod/nethermind.exs index bf5a0440865e..a599d352ab41 100644 --- a/apps/explorer/config/prod/nethermind.exs +++ b/apps/explorer/config/prod/nethermind.exs @@ -15,6 +15,7 @@ config :explorer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/apps/explorer/config/prod/rsk.exs b/apps/explorer/config/prod/rsk.exs index 35120eec2c71..ce19c3138079 100644 --- a/apps/explorer/config/prod/rsk.exs +++ b/apps/explorer/config/prod/rsk.exs @@ -15,6 +15,7 @@ config :explorer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/apps/explorer/lib/explorer/smart_contract/reader.ex b/apps/explorer/lib/explorer/smart_contract/reader.ex index c2906986102f..93b80abb9c2c 100644 --- a/apps/explorer/lib/explorer/smart_contract/reader.ex +++ b/apps/explorer/lib/explorer/smart_contract/reader.ex @@ -62,10 +62,17 @@ defmodule Explorer.SmartContract.Reader do ) # => %{"sum" => {:error, "Data overflow encoding int, data `abc` cannot fit in 256 bits"}} """ - @spec query_verified_contract(Hash.Address.t(), functions(), String.t() | nil, true | false, SmartContract.abi()) :: + @spec query_verified_contract( + Hash.Address.t(), + functions(), + String.t() | nil, + true | false, + SmartContract.abi(), + Keyword.t() + ) :: functions_results() - def query_verified_contract(address_hash, functions, from, leave_error_as_map, mabi) do - query_verified_contract_inner(address_hash, functions, mabi, from, leave_error_as_map) + def query_verified_contract(address_hash, functions, from, leave_error_as_map, mabi, options \\ []) do + query_verified_contract_inner(address_hash, functions, mabi, from, leave_error_as_map, options) end @spec query_verified_contract(Hash.Address.t(), functions(), true | false, SmartContract.abi() | nil) :: @@ -79,15 +86,16 @@ defmodule Explorer.SmartContract.Reader do functions(), SmartContract.abi() | nil, String.t() | nil, - true | false + true | false, + Keyword.t() ) :: functions_results() - defp query_verified_contract_inner(address_hash, functions, mabi, from, leave_error_as_map) do + defp query_verified_contract_inner(address_hash, functions, mabi, from, leave_error_as_map, options \\ []) do contract_address = Hash.to_string(address_hash) abi = prepare_abi(mabi, address_hash) - query_contract(contract_address, from, abi, functions, leave_error_as_map) + query_contract(contract_address, from, abi, functions, leave_error_as_map, options) end defp prepare_abi(nil, address_hash) do @@ -114,10 +122,11 @@ defmodule Explorer.SmartContract.Reader do String.t() | nil, term(), functions(), - true | false + true | false, + Keyword.t() ) :: functions_results() - def query_contract(contract_address, from \\ nil, abi, functions, leave_error_as_map) do - query_contract_inner(contract_address, abi, functions, nil, from, leave_error_as_map) + def query_contract(contract_address, from \\ nil, abi, functions, leave_error_as_map, options \\ []) do + query_contract_inner(contract_address, abi, functions, nil, from, leave_error_as_map, options) end @spec query_contract_by_block_number( @@ -130,7 +139,7 @@ defmodule Explorer.SmartContract.Reader do query_contract_inner(contract_address, abi, functions, block_number, nil, leave_error_as_map) end - defp query_contract_inner(contract_address, abi, functions, block_number, from, leave_error_as_map) do + defp query_contract_inner(contract_address, abi, functions, block_number, from, leave_error_as_map, options \\ []) do requests = functions |> Enum.map(fn {method_id, args} -> @@ -144,7 +153,7 @@ defmodule Explorer.SmartContract.Reader do end) requests - |> query_contracts(abi, [], leave_error_as_map) + |> query_contracts(abi, [], leave_error_as_map, options) |> Enum.zip(requests) |> Enum.into(%{}, fn {response, request} -> {request.method_id, response} @@ -170,9 +179,14 @@ defmodule Explorer.SmartContract.Reader do EthereumJSONRPC.execute_contract_functions(requests, abi, json_rpc_named_arguments) end - @spec query_contracts([Contract.call()], term(), true | false) :: [Contract.call_result()] - def query_contracts(requests, abi, [], leave_error_as_map) do - json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + @spec query_contracts([Contract.call()], term(), contract_call_options(), true | false, Keyword.t()) :: [ + Contract.call_result() + ] + def query_contracts(requests, abi, [], leave_error_as_map, options \\ []) do + json_rpc_named_arguments = + :explorer + |> Application.get_env(:json_rpc_named_arguments) + |> Keyword.merge(options) EthereumJSONRPC.execute_contract_functions(requests, abi, json_rpc_named_arguments, leave_error_as_map) end @@ -208,18 +222,20 @@ defmodule Explorer.SmartContract.Reader do } ] """ - @spec read_only_functions(SmartContract.t(), Hash.t(), String.t() | nil) :: [%{}] - def read_only_functions(%SmartContract{abi: abi}, contract_address_hash, from) do + @spec read_only_functions(SmartContract.t(), Hash.t(), String.t() | nil, Keyword.t()) :: [%{}] + def read_only_functions(smart_contract, contract_address_hash, from, options \\ []) + + def read_only_functions(%SmartContract{abi: abi}, contract_address_hash, from, options) do case abi do nil -> [] _ -> - read_only_functions_from_abi_with_sender(abi, contract_address_hash, from) + read_only_functions_from_abi_with_sender(abi, contract_address_hash, from, options) end end - def read_only_functions(nil, _, _), do: [] + def read_only_functions(nil, _, _, _), do: [] def read_only_functions_proxy(contract_address_hash, implementation_address_hash_string, from, options \\ []) do implementation_abi = SmartContract.get_smart_contract_abi(implementation_address_hash_string, options) @@ -229,7 +245,7 @@ defmodule Explorer.SmartContract.Reader do [] _ -> - read_only_functions_from_abi_with_sender(implementation_abi, contract_address_hash, from) + read_only_functions_from_abi_with_sender(implementation_abi, contract_address_hash, from, options) end end @@ -265,15 +281,19 @@ defmodule Explorer.SmartContract.Reader do def read_functions_required_wallet(nil), do: [] - def read_only_functions_from_abi_with_sender([_ | _] = abi, contract_address_hash, from) do + def read_only_functions_from_abi_with_sender(abi, contract_address_hash, from, options \\ []) + + def read_only_functions_from_abi_with_sender([_ | _] = abi, contract_address_hash, from, options) do abi_with_method_id = get_abi_with_method_id(abi) abi_with_method_id |> Enum.filter(&Helper.queriable_method?(&1)) - |> Enum.map(&fetch_current_value_from_blockchain(&1, abi_with_method_id, contract_address_hash, false, [], from)) + |> Enum.map( + &fetch_current_value_from_blockchain(&1, abi_with_method_id, contract_address_hash, false, options, from) + ) end - def read_only_functions_from_abi_with_sender(_, _, _), do: [] + def read_only_functions_from_abi_with_sender(_, _, _, _), do: [] def read_functions_required_wallet_from_abi([_ | _] = abi) do abi_with_method_id = get_abi_with_method_id(abi) @@ -390,7 +410,7 @@ defmodule Explorer.SmartContract.Reader do from, abi, leave_error_as_map, - _options + options ) do outputs = query_function_with_custom_abi_inner( @@ -399,7 +419,8 @@ defmodule Explorer.SmartContract.Reader do args || [], from, leave_error_as_map, - abi + abi, + options ) names = parse_names_from_abi(abi, method_id) @@ -440,11 +461,25 @@ defmodule Explorer.SmartContract.Reader do Hash.t(), %{method_id: String.t(), args: [term()] | nil}, String.t(), - [%{}] + [%{}], + Keyword.t() ) :: %{:names => [any()], :output => [%{}]} - def query_function_with_names_custom_abi(contract_address_hash, %{method_id: method_id, args: args}, from, custom_abi) do + def query_function_with_names_custom_abi( + contract_address_hash, + %{method_id: method_id, args: args}, + from, + custom_abi, + options \\ [] + ) do outputs = - query_function_with_custom_abi(contract_address_hash, %{method_id: method_id, args: args}, from, true, custom_abi) + query_function_with_custom_abi( + contract_address_hash, + %{method_id: method_id, args: args}, + from, + true, + custom_abi, + options + ) names = parse_names_from_abi(custom_abi, method_id) %{output: outputs, names: names} @@ -498,7 +533,8 @@ defmodule Explorer.SmartContract.Reader do %{method_id: String.t(), args: [term()] | nil | []}, String.t() | nil, true | false, - [%{}] + [%{}], + Keyword.t() ) :: [ %{} ] @@ -507,7 +543,8 @@ defmodule Explorer.SmartContract.Reader do %{method_id: method_id, args: args}, from, leave_error_as_map, - custom_abi + custom_abi, + options \\ [] ) do query_function_with_custom_abi_inner( contract_address_hash, @@ -515,11 +552,20 @@ defmodule Explorer.SmartContract.Reader do args || [], from, leave_error_as_map, - custom_abi + custom_abi, + options ) end - @spec query_function_with_custom_abi_inner(Hash.t(), String.t(), [term()], String.t() | nil, true | false, [%{}]) :: [ + @spec query_function_with_custom_abi_inner( + Hash.t(), + String.t(), + [term()], + String.t() | nil, + true | false, + [%{}], + Keyword.t() + ) :: [ %{} ] defp query_function_with_custom_abi_inner( @@ -528,7 +574,8 @@ defmodule Explorer.SmartContract.Reader do args, from, leave_error_as_map, - custom_abi + custom_abi, + options \\ [] ) do parsed_abi = custom_abi @@ -543,7 +590,8 @@ defmodule Explorer.SmartContract.Reader do custom_abi, outputs, method_id, - leave_error_as_map + leave_error_as_map, + options ) {:error, message} -> @@ -566,9 +614,18 @@ defmodule Explorer.SmartContract.Reader do end end - defp query_contract_and_link_outputs(contract_address_hash, args, from, abi, outputs, method_id, leave_error_as_map) do + defp query_contract_and_link_outputs( + contract_address_hash, + args, + from, + abi, + outputs, + method_id, + leave_error_as_map, + options \\ [] + ) do contract_address_hash - |> query_verified_contract(%{method_id => normalize_args(args)}, from, leave_error_as_map, abi) + |> query_verified_contract(%{method_id => normalize_args(args)}, from, leave_error_as_map, abi, options) |> link_outputs_and_values(outputs, method_id) end diff --git a/apps/indexer/config/dev/arbitrum.exs b/apps/indexer/config/dev/arbitrum.exs index e708fed5441b..ee13d8ff9be2 100644 --- a/apps/indexer/config/dev/arbitrum.exs +++ b/apps/indexer/config/dev/arbitrum.exs @@ -19,6 +19,7 @@ config :indexer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:8545") ], diff --git a/apps/indexer/config/dev/besu.exs b/apps/indexer/config/dev/besu.exs index e09c82159481..e1258bff85cb 100644 --- a/apps/indexer/config/dev/besu.exs +++ b/apps/indexer/config/dev/besu.exs @@ -21,6 +21,7 @@ config :indexer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/indexer/config/dev/erigon.exs b/apps/indexer/config/dev/erigon.exs index ef77231c83e5..2a7def995444 100644 --- a/apps/indexer/config/dev/erigon.exs +++ b/apps/indexer/config/dev/erigon.exs @@ -21,6 +21,7 @@ config :indexer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/indexer/config/dev/filecoin.exs b/apps/indexer/config/dev/filecoin.exs index 7bf6f8be2613..65fab05db094 100644 --- a/apps/indexer/config/dev/filecoin.exs +++ b/apps/indexer/config/dev/filecoin.exs @@ -20,6 +20,7 @@ config :indexer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:1234/rpc/v1", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:1234/rpc/v1"), trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:1234/rpc/v1" diff --git a/apps/indexer/config/dev/ganache.exs b/apps/indexer/config/dev/ganache.exs index 95ed0de84d89..fcb3e127e122 100644 --- a/apps/indexer/config/dev/ganache.exs +++ b/apps/indexer/config/dev/ganache.exs @@ -19,6 +19,7 @@ config :indexer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:7545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:7545") ], diff --git a/apps/indexer/config/dev/geth.exs b/apps/indexer/config/dev/geth.exs index 097e48e223f0..c8182d931342 100644 --- a/apps/indexer/config/dev/geth.exs +++ b/apps/indexer/config/dev/geth.exs @@ -20,6 +20,7 @@ config :indexer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/indexer/config/dev/nethermind.exs b/apps/indexer/config/dev/nethermind.exs index d97935eb14da..50c7eec22d2e 100644 --- a/apps/indexer/config/dev/nethermind.exs +++ b/apps/indexer/config/dev/nethermind.exs @@ -21,6 +21,7 @@ config :indexer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/indexer/config/dev/rsk.exs b/apps/indexer/config/dev/rsk.exs index b609f78a224c..d4e4b42630fb 100644 --- a/apps/indexer/config/dev/rsk.exs +++ b/apps/indexer/config/dev/rsk.exs @@ -22,6 +22,7 @@ config :indexer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/indexer/config/prod/arbitrum.exs b/apps/indexer/config/prod/arbitrum.exs index 6cf72e206a26..a4d0667d6384 100644 --- a/apps/indexer/config/prod/arbitrum.exs +++ b/apps/indexer/config/prod/arbitrum.exs @@ -19,6 +19,7 @@ config :indexer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:8545") ], diff --git a/apps/indexer/config/prod/besu.exs b/apps/indexer/config/prod/besu.exs index 576bdcef3bcf..0f98fc1d6775 100644 --- a/apps/indexer/config/prod/besu.exs +++ b/apps/indexer/config/prod/besu.exs @@ -20,6 +20,7 @@ config :indexer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/apps/indexer/config/prod/erigon.exs b/apps/indexer/config/prod/erigon.exs index 84a5c6250390..f01fbc77409b 100644 --- a/apps/indexer/config/prod/erigon.exs +++ b/apps/indexer/config/prod/erigon.exs @@ -20,6 +20,7 @@ config :indexer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/apps/indexer/config/prod/filecoin.exs b/apps/indexer/config/prod/filecoin.exs index 36e0ea0bbba0..fc62d266cc13 100644 --- a/apps/indexer/config/prod/filecoin.exs +++ b/apps/indexer/config/prod/filecoin.exs @@ -20,6 +20,7 @@ config :indexer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url(), trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") diff --git a/apps/indexer/config/prod/ganache.exs b/apps/indexer/config/prod/ganache.exs index 95ed0de84d89..fcb3e127e122 100644 --- a/apps/indexer/config/prod/ganache.exs +++ b/apps/indexer/config/prod/ganache.exs @@ -19,6 +19,7 @@ config :indexer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:7545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url("http://localhost:7545") ], diff --git a/apps/indexer/config/prod/geth.exs b/apps/indexer/config/prod/geth.exs index cfa1c8372436..9e3ccb4e12c3 100644 --- a/apps/indexer/config/prod/geth.exs +++ b/apps/indexer/config/prod/geth.exs @@ -20,6 +20,7 @@ config :indexer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url(), debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/apps/indexer/config/prod/nethermind.exs b/apps/indexer/config/prod/nethermind.exs index 2dc6c0f98e80..4ba53bc7d143 100644 --- a/apps/indexer/config/prod/nethermind.exs +++ b/apps/indexer/config/prod/nethermind.exs @@ -20,6 +20,7 @@ config :indexer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/apps/indexer/config/prod/rsk.exs b/apps/indexer/config/prod/rsk.exs index 444eff26e653..ad4ce94b39d6 100644 --- a/apps/indexer/config/prod/rsk.exs +++ b/apps/indexer/config/prod/rsk.exs @@ -22,6 +22,7 @@ config :indexer, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), + fallback_eth_call_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL"), method_to_url: [ eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 9ba9c686ee0f..abf1c40689a3 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -5,6 +5,7 @@ DATABASE_URL=postgresql://blockscout:ceWb1MeLBEeOIfk65gU8EjF8@db:5432/blockscout # DATABASE_QUEUE_TARGET ETHEREUM_JSONRPC_TRACE_URL=http://host.docker.internal:8545/ # ETHEREUM_JSONRPC_FALLBACK_TRACE_URL= +# ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL= # ETHEREUM_JSONRPC_ETH_CALL_URL= # ETHEREUM_JSONRPC_HTTP_TIMEOUT= # CHAIN_TYPE= From da6c71d9119adab48dd9370683ff0dc26c5b1f4a Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:29:44 +0300 Subject: [PATCH 603/607] Fix zero balances coming via WS (#9510) --- CHANGELOG.md | 1 + .../api/v2/address_controller_test.exs | 291 +++++++++++++++++- apps/explorer/config/test.exs | 1 - .../chain/address/current_token_balance.ex | 4 +- .../fetcher/token_balance_on_demand.ex | 61 +++- 5 files changed, 336 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0725a990884..9bff8b0f3777 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ - [#9529](https://github.com/blockscout/blockscout/pull/9529) - Fix `MAX_SAFE_INTEGER` frontend bug - [#9518](https://github.com/blockscout/blockscout/pull/9518), [#9628](https://github.com/blockscout/blockscout/pull/9628) - Fix MultipleResultsError in `smart_contract_creation_tx_bytecode/1` - [#9514](https://github.com/blockscout/blockscout/pull/9514) - Fix missing `0x` prefix for `blockNumber`, `logIndex`, `transactionIndex` and remove `transactionLogIndex` in `eth_getLogs` response. +- [#9510](https://github.com/blockscout/blockscout/pull/9510) - Fix WS false 0 token balances - [#9512](https://github.com/blockscout/blockscout/pull/9512) - Docker-compose 2.24.6 compatibility - [#9262](https://github.com/blockscout/blockscout/pull/9262) - Fix withdrawal status - [#9123](https://github.com/blockscout/blockscout/pull/9123) - Fixes in Optimism due to changed log topics type diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index 4189d1a1129f..3cb778e7e9d9 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -1,7 +1,9 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do use BlockScoutWeb.ConnCase use EthereumJSONRPC.Case, async: false + use BlockScoutWeb.ChannelCase + alias ABI.{TypeDecoder, TypeEncoder} alias BlockScoutWeb.Models.UserFromAuth alias Explorer.{Chain, Repo} alias Explorer.Chain.Address.Counters @@ -1835,7 +1837,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do ) |> Repo.preload([:token]) end - |> Enum.sort_by(fn x -> x.value end, :asc) + |> Enum.sort_by(fn x -> Decimal.to_integer(x.value) end, :asc) ctbs_erc_1155 = for _ <- 0..50 do @@ -1846,7 +1848,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do ) |> Repo.preload([:token]) end - |> Enum.sort_by(fn x -> x.value end, :asc) + |> Enum.sort_by(fn x -> Decimal.to_integer(x.value) end, :asc) filter = %{"type" => "ERC-20"} request = get(conn, "/api/v2/addresses/#{address.hash}/tokens", filter) @@ -1883,6 +1885,291 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do end end + describe "checks TokenBalanceOnDemand" do + setup do + Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.BlockNumber.child_id()) + Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.BlockNumber.child_id()) + old_env = Application.get_env(:indexer, Indexer.Fetcher.TokenBalanceOnDemand) + + Application.put_env( + :indexer, + Indexer.Fetcher.TokenBalanceOnDemand, + Keyword.put(old_env, :fallback_threshold_in_blocks, 0) + ) + + on_exit(fn -> + Application.put_env(:indexer, Indexer.Fetcher.TokenBalanceOnDemand, old_env) + end) + end + + test "Indexer.Fetcher.TokenBalanceOnDemand broadcasts only updated balances", %{conn: conn} do + address = insert(:address) + + ctbs_erc_20 = + for i <- 0..1 do + ctb = + insert(:address_current_token_balance_with_token_id_and_fixed_token_type, + address: address, + token_type: "ERC-20", + token_id: nil + ) + + {to_string(ctb.token_contract_address_hash), + Decimal.to_integer(ctb.value) + if(rem(i, 2) == 0, do: 1, else: 0)} + end + |> Enum.into(%{}) + + ctbs_erc_721 = + for i <- 0..1 do + ctb = + insert(:address_current_token_balance_with_token_id_and_fixed_token_type, + address: address, + token_type: "ERC-721", + token_id: nil + ) + + {to_string(ctb.token_contract_address_hash), + Decimal.to_integer(ctb.value) + if(rem(i, 2) == 0, do: 1, else: 0)} + end + |> Enum.into(%{}) + + other_balances = Map.merge(ctbs_erc_20, ctbs_erc_721) + + balances_erc_1155 = + for i <- 0..1 do + ctb = + insert(:address_current_token_balance_with_token_id_and_fixed_token_type, + address: address, + token_type: "ERC-1155", + token_id: Enum.random(1..100_000) + ) + + {{to_string(ctb.token_contract_address_hash), to_string(ctb.token_id)}, + Decimal.to_integer(ctb.value) + if(rem(i, 2) == 0, do: 1, else: 0)} + end + |> Enum.into(%{}) + + block_number_hex = "0x" <> (Integer.to_string(insert(:block).number, 16) |> String.upcase()) + + expect(EthereumJSONRPC.Mox, :json_rpc, fn [ + %{ + id: id_1, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: "0x00fdd58e" <> request_1, + to: contract_address_1 + }, + ^block_number_hex + ] + }, + %{ + id: id_2, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: "0x00fdd58e" <> request_2, + to: contract_address_2 + }, + ^block_number_hex + ] + } + ], + _options -> + types_list = [:address, {:uint, 256}] + + [address_1, token_id_1] = request_1 |> Base.decode16!(case: :lower) |> TypeDecoder.decode_raw(types_list) + + assert address_1 == address.hash.bytes + + result_1 = + balances_erc_1155[{contract_address_1 |> String.downcase(), to_string(token_id_1)}] + |> List.wrap() + |> TypeEncoder.encode_raw([{:uint, 256}], :standard) + |> Base.encode16(case: :lower) + + [address_2, token_id_2] = request_2 |> Base.decode16!(case: :lower) |> TypeDecoder.decode_raw(types_list) + + assert address_2 == address.hash.bytes + + result_2 = + balances_erc_1155[{contract_address_2 |> String.downcase(), to_string(token_id_2)}] + |> List.wrap() + |> TypeEncoder.encode_raw([{:uint, 256}], :standard) + |> Base.encode16(case: :lower) + + {:ok, + [ + %{ + id: id_1, + jsonrpc: "2.0", + result: "0x" <> result_1 + }, + %{ + id: id_2, + jsonrpc: "2.0", + result: "0x" <> result_2 + } + ]} + end) + + expect(EthereumJSONRPC.Mox, :json_rpc, fn [ + %{ + id: id_1, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: "0x70a08231" <> request_1, + to: contract_address_1 + }, + ^block_number_hex + ] + }, + %{ + id: id_2, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: "0x70a08231" <> request_2, + to: contract_address_2 + }, + ^block_number_hex + ] + }, + %{ + id: id_3, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: "0x70a08231" <> request_3, + to: contract_address_3 + }, + ^block_number_hex + ] + }, + %{ + id: id_4, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: "0x70a08231" <> request_4, + to: contract_address_4 + }, + ^block_number_hex + ] + } + ], + _options -> + types_list = [:address] + + assert request_1 |> Base.decode16!(case: :lower) |> TypeDecoder.decode_raw(types_list) == [address.hash.bytes] + + assert request_2 |> Base.decode16!(case: :lower) |> TypeDecoder.decode_raw(types_list) == [address.hash.bytes] + + assert request_3 |> Base.decode16!(case: :lower) |> TypeDecoder.decode_raw(types_list) == [address.hash.bytes] + + assert request_4 |> Base.decode16!(case: :lower) |> TypeDecoder.decode_raw(types_list) == [address.hash.bytes] + + result_1 = + other_balances[contract_address_1 |> String.downcase()] + |> List.wrap() + |> TypeEncoder.encode_raw([{:uint, 256}], :standard) + |> Base.encode16(case: :lower) + + result_2 = + other_balances[contract_address_2 |> String.downcase()] + |> List.wrap() + |> TypeEncoder.encode_raw([{:uint, 256}], :standard) + |> Base.encode16(case: :lower) + + result_3 = + other_balances[contract_address_3 |> String.downcase()] + |> List.wrap() + |> TypeEncoder.encode_raw([{:uint, 256}], :standard) + |> Base.encode16(case: :lower) + + result_4 = + other_balances[contract_address_4 |> String.downcase()] + |> List.wrap() + |> TypeEncoder.encode_raw([{:uint, 256}], :standard) + |> Base.encode16(case: :lower) + + {:ok, + [ + %{ + id: id_1, + jsonrpc: "2.0", + result: "0x" <> result_1 + }, + %{ + id: id_2, + jsonrpc: "2.0", + result: "0x" <> result_2 + }, + %{ + id: id_3, + jsonrpc: "2.0", + result: "0x" <> result_3 + }, + %{ + id: id_4, + jsonrpc: "2.0", + result: "0x" <> result_4 + } + ]} + end) + + topic = "addresses:#{address.hash}" + + {:ok, _reply, _socket} = + BlockScoutWeb.UserSocketV2 + |> socket("no_id", %{}) + |> subscribe_and_join(topic) + + request = get(conn, "/api/v2/addresses/#{address.hash}/tokens") + assert _response = json_response(request, 200) + overflow = false + + assert_receive %Phoenix.Socket.Message{ + payload: %{token_balances: [ctb_erc_20], overflow: ^overflow}, + event: "updated_token_balances_erc_20", + topic: ^topic + }, + :timer.seconds(1) + + assert_receive %Phoenix.Socket.Message{ + payload: %{token_balances: [ctb_erc_721], overflow: ^overflow}, + event: "updated_token_balances_erc_721", + topic: ^topic + }, + :timer.seconds(1) + + assert_receive %Phoenix.Socket.Message{ + payload: %{token_balances: [ctb_erc_1155], overflow: ^overflow}, + event: "updated_token_balances_erc_1155", + topic: ^topic + }, + :timer.seconds(1) + + assert Decimal.to_integer(ctb_erc_20["value"]) == + other_balances[ctb_erc_20["token"]["address"] |> String.downcase()] + + assert Decimal.to_integer(ctb_erc_721["value"]) == + other_balances[ctb_erc_721["token"]["address"] |> String.downcase()] + + assert Decimal.to_integer(ctb_erc_1155["value"]) == + balances_erc_1155[ + {ctb_erc_1155["token"]["address"] |> String.downcase(), to_string(ctb_erc_1155["token_id"])} + ] + end + end + describe "/addresses/{address_hash}/withdrawals" do test "get empty list on non existing address", %{conn: conn} do address = build(:address) diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index 6241f35eebd7..9ace0f12c261 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -86,4 +86,3 @@ config :explorer, Explorer.ExchangeRates.Source.TransactionAndLog, config :explorer, Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand, enabled: false config :explorer, Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand, enabled: false -config :explorer, Explorer.Tags.AddressTag.Cataloger, enabled: false diff --git a/apps/explorer/lib/explorer/chain/address/current_token_balance.ex b/apps/explorer/lib/explorer/chain/address/current_token_balance.ex index 30dbe76f56f8..43e1a57099b8 100644 --- a/apps/explorer/lib/explorer/chain/address/current_token_balance.ex +++ b/apps/explorer/lib/explorer/chain/address/current_token_balance.ex @@ -160,9 +160,7 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do on: ctb.token_contract_address_hash == t.contract_address_hash, preload: [token: t], select: ctb, - select_merge: ^%{fiat_value: fiat_balance}, - order_by: ^[desc_nulls_last: fiat_balance], - order_by: [desc: ctb.value, desc: ctb.id] + select_merge: ^%{fiat_value: fiat_balance} ) end diff --git a/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex b/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex index 8b8336c906db..7020cfd9d74d 100644 --- a/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex +++ b/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex @@ -71,10 +71,17 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do end defp fetch_and_update(block_number, address_hash, stale_current_token_balances) do - %{erc_1155: erc_1155_ctbs, other: other_ctbs, tokens: tokens} = + %{ + erc_1155: erc_1155_ctbs, + other: other_ctbs, + tokens: tokens, + balances_map: balances_map + } = stale_current_token_balances - |> Enum.reduce(%{erc_1155: [], other: [], tokens: %{}}, fn %{token_id: token_id} = stale_current_token_balance, - acc -> + |> Enum.reduce(%{erc_1155: [], other: [], tokens: %{}, balances_map: %{}}, fn %{ + token_id: token_id + } = stale_current_token_balance, + acc -> prepared_ctb = %{ token_contract_address_hash: "0x" <> Base.encode16(stale_current_token_balance.token.contract_address_hash.bytes), @@ -98,35 +105,40 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do Map.put(acc, :other, [prepared_ctb | acc[:other]]) end - Map.put(result, :tokens, updated_tokens) - end) + updated_balances_map = + Map.put( + acc[:balances_map], + ctb_to_key(stale_current_token_balance), + stale_current_token_balance.value + ) - erc_1155_ctbs_reversed = Enum.reverse(erc_1155_ctbs) - other_ctbs_reversed = Enum.reverse(other_ctbs) + result + |> Map.put(:tokens, updated_tokens) + |> Map.put(:balances_map, updated_balances_map) + end) updated_erc_1155_ctbs = - if Enum.count(erc_1155_ctbs_reversed) > 0 do - erc_1155_ctbs_reversed + if Enum.count(erc_1155_ctbs) > 0 do + erc_1155_ctbs |> BalanceReader.get_balances_of_erc_1155() - |> Enum.zip(erc_1155_ctbs_reversed) + |> Enum.zip(erc_1155_ctbs) |> Enum.map(&prepare_updated_balance(&1, block_number)) else [] end updated_other_ctbs = - if Enum.count(other_ctbs_reversed) > 0 do - other_ctbs_reversed + if Enum.count(other_ctbs) > 0 do + other_ctbs |> BalanceReader.get_balances_of() - |> Enum.zip(other_ctbs_reversed) + |> Enum.zip(other_ctbs) |> Enum.map(&prepare_updated_balance(&1, block_number)) else [] end filtered_current_token_balances_update_params = - (updated_erc_1155_ctbs ++ updated_other_ctbs) - |> Enum.filter(&(!is_nil(&1))) + (updated_erc_1155_ctbs ++ updated_other_ctbs) |> Enum.filter(&(!is_nil(&1))) if Enum.count(filtered_current_token_balances_update_params) > 0 do {:ok, @@ -140,12 +152,14 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do broadcast: false }) + filtered_imported_ctbs = filter_imported_ctbs(imported_ctbs, balances_map) + Publisher.broadcast( %{ address_current_token_balances: %{ address_hash: to_string(address_hash), address_current_token_balances: - imported_ctbs + filtered_imported_ctbs |> Enum.map(fn ctb -> %CurrentTokenBalance{ctb | token: tokens[ctb.token_contract_address_hash.bytes]} end) } }, @@ -154,6 +168,21 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do end end + defp filter_imported_ctbs(imported_ctbs, balances_map) do + Enum.filter(imported_ctbs, fn ctb -> + if balance = balances_map[ctb_to_key(ctb)] do + Decimal.compare(balance, ctb.value) != :eq + else + Logger.error("Imported unknown balance") + true + end + end) + end + + defp ctb_to_key(ctb) do + {ctb.token_contract_address_hash.bytes, ctb.token_type, ctb.token_id && Decimal.to_integer(ctb.token_id)} + end + defp prepare_updated_balance({{:ok, updated_balance}, stale_current_token_balance}, block_number) do %{} |> Map.put(:address_hash, stale_current_token_balance.address_hash) From dea361d56fac753d0695eee52be7563add104ce1 Mon Sep 17 00:00:00 2001 From: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:32:50 +0300 Subject: [PATCH 604/607] Add secondary coin and transaction stats (#9483) * Add volume_24h * Add secondary coin and transactions stats * Process review comments * Allow different source for secondary coin * Fix exchange_rates_secondary_coin_price_source --------- Co-authored-by: Nikita Pozdniakov --- CHANGELOG.md | 1 + .../lib/block_scout_web/api_router.ex | 2 + .../controllers/api/v2/stats_controller.ex | 12 ++ .../api/v2/transaction_controller.ex | 20 +++ .../views/api/v2/token_view.ex | 1 + .../views/api/v2/transaction_view.ex | 14 ++ apps/explorer/config/runtime/test.exs | 2 + apps/explorer/lib/explorer/application.ex | 2 + apps/explorer/lib/explorer/chain/token.ex | 3 +- .../fresh_pending_transactions_counter.ex | 95 ++++++++++++ .../counters/transactions_24h_stats.ex | 143 ++++++++++++++++++ .../exchange_rates/source/coin_gecko.ex | 20 ++- .../exchange_rates/source/coin_market_cap.ex | 6 + .../lib/explorer/market/history/cataloger.ex | 65 ++++++-- .../explorer/market/history/source/price.ex | 6 +- .../market/history/source/price/coin_gecko.ex | 15 +- .../history/source/price/coin_market_cap.ex | 18 ++- .../history/source/price/crypto_compare.ex | 26 ++-- apps/explorer/lib/explorer/market/market.ex | 8 +- .../lib/explorer/market/market_history.ex | 1 + .../explorer/market/market_history_cache.ex | 7 +- ...0240219143204_add_volume_24h_to_tokens.exs | 9 ++ ...1331_add_secondary_coin_market_history.exs | 12 ++ ...resh_pending_transactions_counter_test.exs | 27 ++++ .../counters/transactions_24h_stats_test.exs | 61 ++++++++ .../exchange_rates/source/coin_gecko_test.exs | 2 +- .../token_exchange_rates_test.exs | 12 +- .../market/history/cataloger_test.exs | 100 ++++++++++-- .../source/price/crypto_compare_test.exs | 22 ++- .../test/support/fakes/no_op_price_source.ex | 2 +- config/config_helper.exs | 14 ++ config/runtime.exs | 32 +++- config/runtime/test.exs | 4 + cspell.json | 7 + docker-compose/envs/common-blockscout.env | 5 + 35 files changed, 692 insertions(+), 84 deletions(-) create mode 100644 apps/explorer/lib/explorer/counters/fresh_pending_transactions_counter.ex create mode 100644 apps/explorer/lib/explorer/counters/transactions_24h_stats.ex create mode 100644 apps/explorer/priv/repo/migrations/20240219143204_add_volume_24h_to_tokens.exs create mode 100644 apps/explorer/priv/repo/migrations/20240226151331_add_secondary_coin_market_history.exs create mode 100644 apps/explorer/test/explorer/counters/fresh_pending_transactions_counter_test.exs create mode 100644 apps/explorer/test/explorer/counters/transactions_24h_stats_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bff8b0f3777..1d6710ed8d20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - [#9511](https://github.com/blockscout/blockscout/pull/9511) - Separate errors by type in EndpointAvailabilityObserver - [#9490](https://github.com/blockscout/blockscout/pull/9490), [#9644](https://github.com/blockscout/blockscout/pull/9644) - Add blob transaction counter and filter in block view - [#9486](https://github.com/blockscout/blockscout/pull/9486) - Massive blocks fetcher +- [#9483](https://github.com/blockscout/blockscout/pull/9483) - Add secondary coin and transaction stats - [#9473](https://github.com/blockscout/blockscout/pull/9473) - Add user_op interpretation - [#9461](https://github.com/blockscout/blockscout/pull/9461) - Fetch blocks without internal transactions backwards - [#9460](https://github.com/blockscout/blockscout/pull/9460) - Optimism chain type diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index b377a60fd9a9..f733afcc59c7 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -202,6 +202,7 @@ defmodule BlockScoutWeb.ApiRouter do scope "/transactions" do get("/", V2.TransactionController, :transactions) get("/watchlist", V2.TransactionController, :watchlist_transactions) + get("/stats", V2.TransactionController, :stats) if Application.compile_env(:explorer, :chain_type) == "polygon_zkevm" do get("/zkevm-batch/:batch_number", V2.TransactionController, :polygon_zkevm_batch) @@ -298,6 +299,7 @@ defmodule BlockScoutWeb.ApiRouter do scope "/charts" do get("/transactions", V2.StatsController, :transactions_chart) get("/market", V2.StatsController, :market_chart) + get("/secondary-coin-market", V2.StatsController, :secondary_coin_market_chart) end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex index f4bb234f2494..8cf9adbe5d8d 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex @@ -147,6 +147,18 @@ defmodule BlockScoutWeb.API.V2.StatsController do }) end + def secondary_coin_market_chart(conn, _params) do + recent_market_history = Market.fetch_recent_history(true) + + chart_data = + recent_market_history + |> Enum.map(fn day -> Map.take(day, [:closing_price, :date]) end) + + json(conn, %{ + chart_data: chart_data + }) + end + defp backward_compatibility(response, conn) do case Conn.get_req_header(conn, "updated-gas-oracle") do ["true"] -> diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex index be5c58beddb4..05039db280e6 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -33,6 +33,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do alias Explorer.Chain.{Hash, Transaction} alias Explorer.Chain.PolygonZkevm.Reader alias Explorer.Chain.ZkSync.Reader + alias Explorer.Counters.{FreshPendingTransactionsCounter, Transactions24hStats} alias Indexer.Fetcher.FirstTraceOnDemand action_fallback(BlockScoutWeb.API.V2.FallbackController) @@ -446,6 +447,25 @@ defmodule BlockScoutWeb.API.V2.TransactionController do end end + def stats(conn, _params) do + transactions_count = Transactions24hStats.fetch_count(@api_true) + pending_transactions_count = FreshPendingTransactionsCounter.fetch(@api_true) + transaction_fees_sum = Transactions24hStats.fetch_fee_sum(@api_true) + transaction_fees_avg = Transactions24hStats.fetch_fee_average(@api_true) + + conn + |> put_status(200) + |> render( + :stats, + %{ + transactions_count_24h: transactions_count, + pending_transactions_count: pending_transactions_count, + transaction_fees_sum_24h: transaction_fees_sum, + transaction_fees_avg_24h: transaction_fees_avg + } + ) + end + @doc """ Checks if this valid transaction hash string, and this transaction doesn't belong to prohibited address """ diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex index a6b5fc99f72b..f1ed2d99537c 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex @@ -36,6 +36,7 @@ defmodule BlockScoutWeb.API.V2.TokenView do "type" => token.type, "holders" => prepare_holders_count(token.holder_count), "exchange_rate" => exchange_rate(token), + "volume_24h" => token.volume_24h, "total_supply" => token.total_supply, "icon_url" => token.icon_url, "circulating_market_cap" => token.circulating_market_cap diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index cd8d57e3c30f..2c4c2fc47811 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -184,6 +184,20 @@ defmodule BlockScoutWeb.API.V2.TransactionView do } end + def render("stats.json", %{ + transactions_count_24h: transactions_count, + pending_transactions_count: pending_transactions_count, + transaction_fees_sum_24h: transaction_fees_sum, + transaction_fees_avg_24h: transaction_fees_avg + }) do + %{ + "transactions_count_24h" => transactions_count, + "pending_transactions_count" => pending_transactions_count, + "transaction_fees_sum_24h" => transaction_fees_sum, + "transaction_fees_avg_24h" => transaction_fees_avg + } + end + @doc """ Decodes list of logs """ diff --git a/apps/explorer/config/runtime/test.exs b/apps/explorer/config/runtime/test.exs index c54b7129378f..d9cfc1a821b6 100644 --- a/apps/explorer/config/runtime/test.exs +++ b/apps/explorer/config/runtime/test.exs @@ -19,6 +19,8 @@ config :explorer, Explorer.Market.History.Historian, enabled: false config :explorer, Explorer.Counters.AddressesCounter, enabled: false, enable_consolidation: false config :explorer, Explorer.Counters.LastOutputRootSizeCounter, enabled: false, enable_consolidation: false +config :explorer, Explorer.Counters.Transactions24hStats, enabled: false, enable_consolidation: false +config :explorer, Explorer.Counters.FreshPendingTransactionsCounter, enabled: false, enable_consolidation: false config :explorer, Explorer.Chain.Cache.ContractsCounter, enabled: false, enable_consolidation: false config :explorer, Explorer.Chain.Cache.NewContractsCounter, enabled: false, enable_consolidation: false config :explorer, Explorer.Chain.Cache.VerifiedContractsCounter, enabled: false, enable_consolidation: false diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index b5d33647d200..96876b008b9b 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -119,6 +119,8 @@ defmodule Explorer.Application do configure(Explorer.Counters.BlockPriorityFeeCounter), configure(Explorer.Counters.AverageBlockTime), configure(Explorer.Counters.LastOutputRootSizeCounter), + configure(Explorer.Counters.FreshPendingTransactionsCounter), + configure(Explorer.Counters.Transactions24hStats), configure(Explorer.Validator.MetadataProcessor), configure(Explorer.Tags.AddressTag.Cataloger), configure(MinMissingBlockNumber), diff --git a/apps/explorer/lib/explorer/chain/token.ex b/apps/explorer/lib/explorer/chain/token.ex index 9800f462f078..6578a1bdcae4 100644 --- a/apps/explorer/lib/explorer/chain/token.ex +++ b/apps/explorer/lib/explorer/chain/token.ex @@ -30,6 +30,7 @@ defmodule Explorer.Chain.Token.Schema do field(:circulating_market_cap, :decimal) field(:icon_url, :string) field(:is_verified_via_admin_panel, :boolean) + field(:volume_24h, :decimal) belongs_to( :contract_address, @@ -123,7 +124,7 @@ defmodule Explorer.Chain.Token do Explorer.Chain.Token.Schema.generate() @required_attrs ~w(contract_address_hash type)a - @optional_attrs ~w(cataloged decimals name symbol total_supply skip_metadata total_supply_updated_at_block updated_at fiat_value circulating_market_cap icon_url is_verified_via_admin_panel)a + @optional_attrs ~w(cataloged decimals name symbol total_supply skip_metadata total_supply_updated_at_block updated_at fiat_value circulating_market_cap icon_url is_verified_via_admin_panel volume_24h)a @doc false def changeset(%Token{} = token, params \\ %{}) do diff --git a/apps/explorer/lib/explorer/counters/fresh_pending_transactions_counter.ex b/apps/explorer/lib/explorer/counters/fresh_pending_transactions_counter.ex new file mode 100644 index 000000000000..3a4b1548ff53 --- /dev/null +++ b/apps/explorer/lib/explorer/counters/fresh_pending_transactions_counter.ex @@ -0,0 +1,95 @@ +defmodule Explorer.Counters.FreshPendingTransactionsCounter do + @moduledoc """ + Caches number of pending transactions for last 30 minutes. + + It loads the sum asynchronously and in a time interval of :cache_period (default to 5 minutes). + """ + + use GenServer + + import Ecto.Query + + alias Explorer.{Chain, Repo} + alias Explorer.Chain.Transaction + + @counter_type "pending_transaction_count_30min" + + @doc """ + Starts a process to periodically update the counter. + """ + @spec start_link(term()) :: GenServer.on_start() + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + @impl true + def init(_args) do + {:ok, %{consolidate?: enable_consolidation?()}, {:continue, :ok}} + end + + defp schedule_next_consolidation do + Process.send_after(self(), :consolidate, cache_interval()) + end + + @impl true + def handle_continue(:ok, %{consolidate?: true} = state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @impl true + def handle_continue(:ok, state) do + {:noreply, state} + end + + @impl true + def handle_info(:consolidate, state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @doc """ + Fetches the value for a `#{@counter_type}` counter type from the `last_fetched_counters` table. + """ + def fetch(options) do + Chain.get_last_fetched_counter(@counter_type, options) + end + + @doc """ + Consolidates the info by populating the `last_fetched_counters` table with the current database information. + """ + def consolidate do + query = + from(transaction in Transaction, + where: is_nil(transaction.block_hash) and transaction.inserted_at >= ago(30, "minute"), + select: count(transaction.hash) + ) + + count = Repo.one!(query, timeout: :infinity) + + Chain.upsert_last_fetched_counter(%{ + counter_type: @counter_type, + value: count + }) + end + + @doc """ + Returns a boolean that indicates whether consolidation is enabled + + In order to choose whether or not to enable the scheduler and the initial + consolidation, change the following Explorer config: + + `config :explorer, #{__MODULE__}, enable_consolidation: true` + + to: + + `config :explorer, #{__MODULE__}, enable_consolidation: false` + """ + def enable_consolidation?, do: Application.get_env(:explorer, __MODULE__)[:enable_consolidation] + + defp cache_interval, do: Application.get_env(:explorer, __MODULE__)[:cache_period] +end diff --git a/apps/explorer/lib/explorer/counters/transactions_24h_stats.ex b/apps/explorer/lib/explorer/counters/transactions_24h_stats.ex new file mode 100644 index 000000000000..80bef49ec046 --- /dev/null +++ b/apps/explorer/lib/explorer/counters/transactions_24h_stats.ex @@ -0,0 +1,143 @@ +defmodule Explorer.Counters.Transactions24hStats do + @moduledoc """ + Caches number of transactions for last 24 hours, sum of transaction fees for last 24 hours and average transaction fee for last 24 hours counters. + + It loads the counters asynchronously and in a time interval of :cache_period (default to 1 hour). + """ + + use GenServer + + import Ecto.Query + + alias Explorer.{Chain, Repo} + alias Explorer.Chain.Transaction + + @tx_count_name "transaction_count_24h" + @tx_fee_sum_name "transaction_fee_sum_24h" + @tx_fee_average_name "transaction_fee_average_24h" + + @doc """ + Starts a process to periodically update the counters. + """ + @spec start_link(term()) :: GenServer.on_start() + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + @impl true + def init(_args) do + {:ok, %{consolidate?: enable_consolidation?()}, {:continue, :ok}} + end + + defp schedule_next_consolidation do + Process.send_after(self(), :consolidate, cache_interval()) + end + + @impl true + def handle_continue(:ok, %{consolidate?: true} = state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @impl true + def handle_continue(:ok, state) do + {:noreply, state} + end + + @impl true + def handle_info(:consolidate, state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @doc """ + Fetches the value for a `#{@tx_count_name}` counter type from the `last_fetched_counters` table. + """ + def fetch_count(options) do + Chain.get_last_fetched_counter(@tx_count_name, options) + end + + @doc """ + Fetches the value for a `#{@tx_fee_sum_name}` counter type from the `last_fetched_counters` table. + """ + def fetch_fee_sum(options) do + Chain.get_last_fetched_counter(@tx_fee_sum_name, options) + end + + @doc """ + Fetches the value for a `#{@tx_fee_average_name}` counter type from the `last_fetched_counters` table. + """ + def fetch_fee_average(options) do + Chain.get_last_fetched_counter(@tx_fee_average_name, options) + end + + @doc """ + Consolidates the info by populating the `last_fetched_counters` table with the current database information. + """ + def consolidate do + fee_query = + dynamic( + [transaction, block], + fragment( + "COALESCE(?, ? + LEAST(?, ?))", + transaction.gas_price, + block.base_fee_per_gas, + transaction.max_priority_fee_per_gas, + transaction.max_fee_per_gas - block.base_fee_per_gas + ) * transaction.gas_used + ) + + sum_query = dynamic([_, _], sum(^fee_query)) + avg_query = dynamic([_, _], avg(^fee_query)) + + query = + from(transaction in Transaction, + join: block in assoc(transaction, :block), + where: block.timestamp >= ago(24, "hour"), + select: %{count: count(transaction.hash)}, + select_merge: ^%{fee_sum: sum_query}, + select_merge: ^%{fee_average: avg_query} + ) + + %{ + count: count, + fee_sum: fee_sum, + fee_average: fee_average + } = Repo.one!(query, timeout: :infinity) + + Chain.upsert_last_fetched_counter(%{ + counter_type: @tx_count_name, + value: count + }) + + Chain.upsert_last_fetched_counter(%{ + counter_type: @tx_fee_sum_name, + value: fee_sum + }) + + Chain.upsert_last_fetched_counter(%{ + counter_type: @tx_fee_average_name, + value: fee_average + }) + end + + @doc """ + Returns a boolean that indicates whether consolidation is enabled + + In order to choose whether or not to enable the scheduler and the initial + consolidation, change the following Explorer config: + + `config :explorer, #{__MODULE__}, enable_consolidation: true` + + to: + + `config :explorer, #{__MODULE__}, enable_consolidation: false` + """ + def enable_consolidation?, do: Application.get_env(:explorer, __MODULE__)[:enable_consolidation] + + defp cache_interval, do: Application.get_env(:explorer, __MODULE__)[:cache_period] +end diff --git a/apps/explorer/lib/explorer/exchange_rates/source/coin_gecko.ex b/apps/explorer/lib/explorer/exchange_rates/source/coin_gecko.ex index a1282f525d65..40076e6f6364 100644 --- a/apps/explorer/lib/explorer/exchange_rates/source/coin_gecko.ex +++ b/apps/explorer/lib/explorer/exchange_rates/source/coin_gecko.ex @@ -51,6 +51,7 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do def format_data(%{} = market_data_for_tokens) do currency = currency() market_cap = currency <> "_market_cap" + volume_24h = currency <> "_24h_vol" market_data_for_tokens |> Enum.reduce(%{}, fn @@ -60,7 +61,8 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do acc |> Map.put(address_hash, %{ fiat_value: Map.get(market_data, currency), - circulating_market_cap: Map.get(market_data, market_cap) + circulating_market_cap: Map.get(market_data, market_cap), + volume_24h: Map.get(market_data, volume_24h) }) _ -> @@ -92,14 +94,22 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do @impl Source def format_data(_), do: [] - @spec history_url(non_neg_integer()) :: String.t() - def history_url(previous_days) do + @spec history_url(non_neg_integer(), boolean()) :: String.t() + def history_url(previous_days, secondary_coin? \\ false) do query_params = %{ "days" => previous_days, "vs_currency" => "usd" } - "#{source_url()}/market_chart?#{URI.encode_query(query_params)}" + source_url = if secondary_coin?, do: secondary_source_url(), else: source_url() + + "#{source_url}/market_chart?#{URI.encode_query(query_params)}" + end + + def secondary_source_url do + id = config(:secondary_coin_id) + + if id, do: "#{base_url()}/coins/#{id}", else: nil end @impl Source @@ -131,7 +141,7 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do def source_url(token_addresses) when is_list(token_addresses) do joined_addresses = token_addresses |> Enum.map_join(",", &to_string/1) - "#{base_url()}/simple/token_price/#{platform()}?vs_currencies=#{currency()}&include_market_cap=true&contract_addresses=#{joined_addresses}" + "#{base_url()}/simple/token_price/#{platform()}?vs_currencies=#{currency()}&include_market_cap=true&include_24hr_vol=true&contract_addresses=#{joined_addresses}" end @impl Source diff --git a/apps/explorer/lib/explorer/exchange_rates/source/coin_market_cap.ex b/apps/explorer/lib/explorer/exchange_rates/source/coin_market_cap.ex index 2e153e4dee40..739d1c0425fc 100644 --- a/apps/explorer/lib/explorer/exchange_rates/source/coin_market_cap.ex +++ b/apps/explorer/lib/explorer/exchange_rates/source/coin_market_cap.ex @@ -71,6 +71,12 @@ defmodule Explorer.ExchangeRates.Source.CoinMarketCap do end end + @impl Source + def source_url(:secondary_coin) do + coin_id = config(:secondary_coin_id) + if coin_id, do: "#{api_quotes_latest_url()}?id=#{coin_id}&CMC_PRO_API_KEY=#{api_key()}", else: nil + end + @impl Source def source_url(input) do case Chain.Hash.Address.cast(input) do diff --git a/apps/explorer/lib/explorer/market/history/cataloger.ex b/apps/explorer/lib/explorer/market/history/cataloger.ex index e52052a711d0..19b0c7bc4f1b 100644 --- a/apps/explorer/lib/explorer/market/history/cataloger.ex +++ b/apps/explorer/lib/explorer/market/history/cataloger.ex @@ -40,13 +40,33 @@ defmodule Explorer.Market.History.Cataloger do @impl GenServer # Record fetch successful. - def handle_info({_ref, {:price_history, {_, _, {:ok, records}}}}, state) do - Process.send(self(), {:fetch_market_cap_history, 365}, []) + def handle_info({_ref, {:price_history, {day_count, _, false, {:ok, records}}}}, state) do + if config_or_default(:secondary_coin_enabled, false) do + Process.send(self(), {:fetch_price_history_for_secondary_coin, day_count}, []) + else + Process.send(self(), {:fetch_market_cap_history, day_count}, []) + end + state = state |> Map.put_new(:price_records, records) {:noreply, state} end + # Secondary coin. + def handle_info({_ref, {:price_history, {day_count, _, true, {:ok, records}}}}, state) do + Process.send(self(), {:fetch_market_cap_history, day_count}, []) + state = state |> Map.put_new(:secondary_coin_price_records, records) + + {:noreply, state} + end + + @impl GenServer + def handle_info({:fetch_price_history_for_secondary_coin, day_count}, state) do + fetch_price_history(day_count, true) + + {:noreply, state} + end + @impl GenServer def handle_info({:fetch_market_cap_history, day_count}, state) do fetch_market_cap_history(day_count) @@ -98,10 +118,10 @@ defmodule Explorer.Market.History.Cataloger do # Failed to get records. Try again. @impl GenServer - def handle_info({_ref, {:price_history, {day_count, failed_attempts, :error}}}, state) do + def handle_info({_ref, {:price_history, {day_count, failed_attempts, secondary_coin?, :error}}}, state) do Logger.warn(fn -> "Failed to fetch price history. Trying again." end) - fetch_price_history(day_count, failed_attempts + 1) + fetch_price_history(day_count, secondary_coin?, failed_attempts + 1) {:noreply, state} end @@ -150,19 +170,31 @@ defmodule Explorer.Market.History.Cataloger do Application.get_env(:explorer, __MODULE__)[key] || default end - defp market_cap_history(records, state) do + defp market_cap_history(records, _state) do Market.bulk_insert_history(records) # Schedule next check for history fetch_after = config_or_default(:history_fetch_interval, :timer.minutes(60)) Process.send_after(self(), {:fetch_price_history, 1}, fetch_after) - {:noreply, state} + {:noreply, %{}} end - @spec source_price() :: module() - defp source_price do - config_or_default(:price_source, Explorer.ExchangeRates.Source, Explorer.Market.History.Source.Price.CryptoCompare) + @spec source_price(boolean()) :: module() + defp source_price(secondary_coin?) do + if secondary_coin? do + config_or_default( + :secondary_coin_price_source, + Explorer.ExchangeRates.Source, + Explorer.Market.History.Source.Price.CryptoCompare + ) + else + config_or_default( + :price_source, + Explorer.ExchangeRates.Source, + Explorer.Market.History.Source.Price.CryptoCompare + ) + end end @spec source_market_cap() :: module() @@ -183,15 +215,17 @@ defmodule Explorer.Market.History.Cataloger do ) end - @spec fetch_price_history(non_neg_integer(), non_neg_integer()) :: Task.t() - defp fetch_price_history(day_count, failed_attempts \\ 0) do + @spec fetch_price_history(non_neg_integer(), boolean(), non_neg_integer()) :: Task.t() + defp fetch_price_history(day_count, secondary_coin? \\ false, failed_attempts \\ 0) do Task.Supervisor.async_nolink(Explorer.MarketTaskSupervisor, fn -> Process.sleep(HistoryProcess.delay(failed_attempts)) if failed_attempts < @price_failed_attempts do - {:price_history, {day_count, failed_attempts, source_price().fetch_price_history(day_count)}} + {:price_history, + {day_count, failed_attempts, secondary_coin?, + source_price(secondary_coin?).fetch_price_history(day_count, secondary_coin?)}} else - {:price_history, {day_count, failed_attempts, {:ok, []}}} + {:price_history, {day_count, failed_attempts, secondary_coin?, {:ok, []}}} end end) end @@ -224,13 +258,14 @@ defmodule Explorer.Market.History.Cataloger do defp compile_records(state) do price_records = state.price_records + secondary_coin_price_records = state |> Map.get(:secondary_coin_price_records, []) market_cap_records = state.market_cap_records tvl_records = state.tvl_records - all_records = price_records ++ market_cap_records ++ tvl_records + all_records = price_records ++ market_cap_records ++ tvl_records ++ secondary_coin_price_records all_records - |> Enum.group_by(fn %{date: date} -> date end) + |> Enum.group_by(fn %{date: date} = value -> {date, Map.get(value, :secondary_coin, false)} end) |> Map.values() |> Enum.map(fn a -> Enum.reduce(a, %{}, fn x, acc -> Map.merge(x, acc) end) diff --git a/apps/explorer/lib/explorer/market/history/source/price.ex b/apps/explorer/lib/explorer/market/history/source/price.ex index 07924c1fca3a..c8d697fa471a 100644 --- a/apps/explorer/lib/explorer/market/history/source/price.ex +++ b/apps/explorer/lib/explorer/market/history/source/price.ex @@ -9,11 +9,13 @@ defmodule Explorer.Market.History.Source.Price do @type record :: %{ closing_price: Decimal.t(), date: Date.t(), - opening_price: Decimal.t() + opening_price: Decimal.t(), + secondary_coin: boolean() } @doc """ Fetch history for a specified amount of days in the past. """ - @callback fetch_price_history(previous_days :: non_neg_integer()) :: {:ok, [record()]} | :error + @callback fetch_price_history(previous_days :: non_neg_integer(), secondary_coin :: boolean()) :: + {:ok, [record()]} | :error end diff --git a/apps/explorer/lib/explorer/market/history/source/price/coin_gecko.ex b/apps/explorer/lib/explorer/market/history/source/price/coin_gecko.ex index ef49d1f19cdf..8cd7ca220341 100644 --- a/apps/explorer/lib/explorer/market/history/source/price/coin_gecko.ex +++ b/apps/explorer/lib/explorer/market/history/source/price/coin_gecko.ex @@ -11,14 +11,14 @@ defmodule Explorer.Market.History.Source.Price.CoinGecko do @behaviour SourcePrice @impl SourcePrice - def fetch_price_history(previous_days) do - url = ExchangeRatesSourceCoinGecko.history_url(previous_days) + def fetch_price_history(previous_days, secondary_coin? \\ false) do + url = ExchangeRatesSourceCoinGecko.history_url(previous_days, secondary_coin?) case Source.http_request(url, ExchangeRatesSourceCoinGecko.headers()) do {:ok, data} -> result = data - |> format_data() + |> format_data(secondary_coin?) {:ok, result} @@ -27,10 +27,10 @@ defmodule Explorer.Market.History.Source.Price.CoinGecko do end end - @spec format_data(term()) :: SourcePrice.record() | nil - defp format_data(nil), do: nil + @spec format_data(term(), boolean()) :: SourcePrice.record() | nil + defp format_data(nil, _), do: nil - defp format_data(data) do + defp format_data(data, secondary_coin?) do prices = data["prices"] for [date, price] <- prices do @@ -39,7 +39,8 @@ defmodule Explorer.Market.History.Source.Price.CoinGecko do %{ closing_price: Decimal.new(to_string(price)), date: CryptoCompare.date(date), - opening_price: Decimal.new(to_string(price)) + opening_price: Decimal.new(to_string(price)), + secondary_coin: secondary_coin? } end end diff --git a/apps/explorer/lib/explorer/market/history/source/price/coin_market_cap.ex b/apps/explorer/lib/explorer/market/history/source/price/coin_market_cap.ex index 0a8c4bf28dae..c226edfcdd3b 100644 --- a/apps/explorer/lib/explorer/market/history/source/price/coin_market_cap.ex +++ b/apps/explorer/lib/explorer/market/history/source/price/coin_market_cap.ex @@ -10,15 +10,18 @@ defmodule Explorer.Market.History.Source.Price.CoinMarketCap do @behaviour SourcePrice @impl SourcePrice - def fetch_price_history(_previous_days \\ nil) do - url = ExchangeRatesSourceCoinMarketCap.source_url() + def fetch_price_history(_previous_days \\ nil, secondary_coin? \\ false) do + url = + if secondary_coin?, + do: ExchangeRatesSourceCoinMarketCap.source_url(:secondary_coin), + else: ExchangeRatesSourceCoinMarketCap.source_url() if url do case Source.http_request(url, ExchangeRatesSourceCoinMarketCap.headers()) do {:ok, data} -> result = data - |> format_data() + |> format_data(secondary_coin?) {:ok, result} @@ -30,10 +33,10 @@ defmodule Explorer.Market.History.Source.Price.CoinMarketCap do end end - @spec format_data(term()) :: SourcePrice.record() | nil - defp format_data(nil), do: nil + @spec format_data(term(), boolean()) :: SourcePrice.record() | nil + defp format_data(nil, _), do: nil - defp format_data(%{"data" => _} = json_data) do + defp format_data(%{"data" => _} = json_data, secondary_coin?) do market_data = json_data["data"] token_properties = ExchangeRatesSourceCoinMarketCap.get_token_properties(market_data) @@ -48,7 +51,8 @@ defmodule Explorer.Market.History.Source.Price.CoinMarketCap do %{ closing_price: current_price_usd, date: last_updated, - opening_price: current_price_usd + opening_price: current_price_usd, + secondary_coin: secondary_coin? } ] end diff --git a/apps/explorer/lib/explorer/market/history/source/price/crypto_compare.ex b/apps/explorer/lib/explorer/market/history/source/price/crypto_compare.ex index e1b31f03cc2f..297f72348000 100644 --- a/apps/explorer/lib/explorer/market/history/source/price/crypto_compare.ex +++ b/apps/explorer/lib/explorer/market/history/source/price/crypto_compare.ex @@ -18,15 +18,15 @@ defmodule Explorer.Market.History.Source.Price.CryptoCompare do @typep unix_timestamp :: non_neg_integer() @impl SourcePrice - def fetch_price_history(previous_days) do - url = history_url(previous_days) + def fetch_price_history(previous_days, secondary_coin?) do + url = history_url(previous_days, secondary_coin?) headers = [{"Content-Type", "application/json"}] case HTTPoison.get(url, headers) do {:ok, %Response{body: body, status_code: 200}} -> result = body - |> format_data() + |> format_data(secondary_coin?) |> reject_zeros() {:ok, result} @@ -49,23 +49,26 @@ defmodule Explorer.Market.History.Source.Price.CryptoCompare do |> DateTime.to_date() end - @spec format_data(String.t()) :: [SourcePrice.record()] - defp format_data(data) do + @spec format_data(String.t(), boolean()) :: [SourcePrice.record()] + defp format_data(data, secondary_coin?) do json = Jason.decode!(data) for item <- json["Data"] do %{ closing_price: Decimal.new(to_string(item["close"])), date: date(item["time"]), - opening_price: Decimal.new(to_string(item["open"])) + opening_price: Decimal.new(to_string(item["open"])), + secondary_coin: secondary_coin? } end end - @spec history_url(non_neg_integer()) :: String.t() - defp history_url(previous_days) do + @spec history_url(non_neg_integer(), boolean()) :: String.t() + defp history_url(previous_days, secondary_coin?) do + fsym = if secondary_coin?, do: config(:secondary_coin_symbol), else: Explorer.coin() + query_params = %{ - "fsym" => Explorer.coin(), + "fsym" => fsym, "limit" => previous_days, "tsym" => "USD" } @@ -78,4 +81,9 @@ defmodule Explorer.Market.History.Source.Price.CryptoCompare do Decimal.equal?(item.closing_price, 0) && Decimal.equal?(item.opening_price, 0) end) end + + @spec config(atom()) :: term + defp config(key) do + Application.get_env(:explorer, __MODULE__, [])[key] + end end diff --git a/apps/explorer/lib/explorer/market/market.ex b/apps/explorer/lib/explorer/market/market.ex index a65b643fa5cb..11edb9bc3931 100644 --- a/apps/explorer/lib/explorer/market/market.ex +++ b/apps/explorer/lib/explorer/market/market.ex @@ -14,9 +14,9 @@ defmodule Explorer.Market do Today's date is include as part of the day count """ - @spec fetch_recent_history() :: [MarketHistory.t()] - def fetch_recent_history do - MarketHistoryCache.fetch() + @spec fetch_recent_history(boolean()) :: [MarketHistory.t()] + def fetch_recent_history(secondary_coin? \\ false) do + MarketHistoryCache.fetch(secondary_coin?) end @doc """ @@ -72,7 +72,7 @@ defmodule Explorer.Market do Repo.insert_all(MarketHistory, records_without_zeroes, on_conflict: market_history_on_conflict(), - conflict_target: [:date] + conflict_target: [:date, :secondary_coin] ) end diff --git a/apps/explorer/lib/explorer/market/market_history.ex b/apps/explorer/lib/explorer/market/market_history.ex index 6aa24f2f6253..d8ddc3ad5a33 100644 --- a/apps/explorer/lib/explorer/market/market_history.ex +++ b/apps/explorer/lib/explorer/market/market_history.ex @@ -20,5 +20,6 @@ defmodule Explorer.Market.MarketHistory do field(:opening_price, :decimal) field(:market_cap, :decimal) field(:tvl, :decimal) + field(:secondary_coin, :boolean) end end diff --git a/apps/explorer/lib/explorer/market/market_history_cache.ex b/apps/explorer/lib/explorer/market/market_history_cache.ex index 81307bc34fa3..8d095ab35923 100644 --- a/apps/explorer/lib/explorer/market/market_history_cache.ex +++ b/apps/explorer/lib/explorer/market/market_history_cache.ex @@ -15,12 +15,15 @@ defmodule Explorer.Market.MarketHistoryCache do # 6 hours @recent_days 30 - def fetch do - if cache_expired?(@last_update_key) do + def fetch(secondary_coin? \\ false) do + @last_update_key + |> cache_expired?() + |> if do update_cache() else fetch_from_cache(@history_key) end + |> Enum.filter(&(&1.secondary_coin == secondary_coin?)) end def cache_name, do: @cache_name diff --git a/apps/explorer/priv/repo/migrations/20240219143204_add_volume_24h_to_tokens.exs b/apps/explorer/priv/repo/migrations/20240219143204_add_volume_24h_to_tokens.exs new file mode 100644 index 000000000000..cf3c9ad7b3b8 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240219143204_add_volume_24h_to_tokens.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.AddVolume24hToTokens do + use Ecto.Migration + + def change do + alter table(:tokens) do + add(:volume_24h, :decimal) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20240226151331_add_secondary_coin_market_history.exs b/apps/explorer/priv/repo/migrations/20240226151331_add_secondary_coin_market_history.exs new file mode 100644 index 000000000000..799cf62ab01c --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240226151331_add_secondary_coin_market_history.exs @@ -0,0 +1,12 @@ +defmodule Explorer.Repo.Migrations.AddSecondaryCoinMarketHistory do + use Ecto.Migration + + def change do + alter table(:market_history) do + add(:secondary_coin, :boolean, default: false) + end + + drop_if_exists(unique_index(:market_history, [:date])) + create(unique_index(:market_history, [:date, :secondary_coin])) + end +end diff --git a/apps/explorer/test/explorer/counters/fresh_pending_transactions_counter_test.exs b/apps/explorer/test/explorer/counters/fresh_pending_transactions_counter_test.exs new file mode 100644 index 000000000000..6a93612e0485 --- /dev/null +++ b/apps/explorer/test/explorer/counters/fresh_pending_transactions_counter_test.exs @@ -0,0 +1,27 @@ +defmodule Explorer.Counters.FreshPendingTransactionsCounterTest do + use Explorer.DataCase + + alias Explorer.Counters.FreshPendingTransactionsCounter + + test "populates the cache with the number of pending transactions addresses" do + insert(:transaction) + insert(:transaction) + insert(:transaction) + + start_supervised!(FreshPendingTransactionsCounter) + FreshPendingTransactionsCounter.consolidate() + + assert FreshPendingTransactionsCounter.fetch([]) == Decimal.new("3") + end + + test "count only fresh transactions" do + insert(:transaction, inserted_at: Timex.shift(Timex.now(), hours: -2)) + insert(:transaction) + insert(:transaction) + + start_supervised!(FreshPendingTransactionsCounter) + FreshPendingTransactionsCounter.consolidate() + + assert FreshPendingTransactionsCounter.fetch([]) == Decimal.new("2") + end +end diff --git a/apps/explorer/test/explorer/counters/transactions_24h_stats_test.exs b/apps/explorer/test/explorer/counters/transactions_24h_stats_test.exs new file mode 100644 index 000000000000..96a5fccaffe3 --- /dev/null +++ b/apps/explorer/test/explorer/counters/transactions_24h_stats_test.exs @@ -0,0 +1,61 @@ +defmodule Explorer.Counters.Transactions24hStatsTest do + use Explorer.DataCase + + alias Explorer.Counters.Transactions24hStats + + test "populates the cache with transaction counters" do + block = insert(:block, base_fee_per_gas: 50) + address = insert(:address) + + # fee = 10000 + + insert(:transaction, + from_address: address, + block: block, + block_number: block.number, + cumulative_gas_used: 0, + index: 0, + gas_price: 100, + gas_used: 100 + ) + + # fee = 15000 + + insert(:transaction, + from_address: address, + block: block, + block_number: block.number, + cumulative_gas_used: 100, + index: 1, + gas_price: 150, + gas_used: 100, + max_priority_fee_per_gas: 100, + max_fee_per_gas: 200 + ) + + # fee = 10000 + + insert(:transaction, + from_address: address, + block: block, + block_number: block.number, + cumulative_gas_used: 200, + index: 2, + gas_price: 100, + gas_used: 100, + max_priority_fee_per_gas: 70, + max_fee_per_gas: 100 + ) + + start_supervised!(Transactions24hStats) + Transactions24hStats.consolidate() + + transaction_count = Transactions24hStats.fetch_count([]) + transaction_fee_sum = Transactions24hStats.fetch_fee_sum([]) + transaction_fee_average = Transactions24hStats.fetch_fee_average([]) + + assert transaction_count == Decimal.new("3") + assert transaction_fee_sum == Decimal.new("35000") + assert transaction_fee_average == Decimal.new("11667") + end +end diff --git a/apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs b/apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs index e16d056422c2..aa17f904019f 100644 --- a/apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs +++ b/apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs @@ -71,7 +71,7 @@ defmodule Explorer.ExchangeRates.Source.CoinGeckoTest do end test "composes cg url to list of contract address hashes" do - assert "https://api.coingecko.com/api/v3/simple/token_price/ethereum?vs_currencies=usd&include_market_cap=true&contract_addresses=0xdAC17F958D2ee523a2206206994597C13D831ec7" == + assert "https://api.coingecko.com/api/v3/simple/token_price/ethereum?vs_currencies=usd&include_market_cap=true&include_24hr_vol=true&contract_addresses=0xdAC17F958D2ee523a2206206994597C13D831ec7" == CoinGecko.source_url(["0xdAC17F958D2ee523a2206206994597C13D831ec7"]) end diff --git a/apps/explorer/test/explorer/exchange_rates/token_exchange_rates_test.exs b/apps/explorer/test/explorer/exchange_rates/token_exchange_rates_test.exs index 2e5f9ee1a6c6..a6d3255a8047 100644 --- a/apps/explorer/test/explorer/exchange_rates/token_exchange_rates_test.exs +++ b/apps/explorer/test/explorer/exchange_rates/token_exchange_rates_test.exs @@ -77,7 +77,9 @@ defmodule Explorer.TokenExchangeRatesTest do "GET", "/simple/token_price/ethereum", fn conn -> - assert conn.query_string == "vs_currencies=usd&include_market_cap=true&contract_addresses=#{joined_addresses}" + assert conn.query_string == + "vs_currencies=usd&include_market_cap=true&include_24hr_vol=true&contract_addresses=#{joined_addresses}" + Conn.resp(conn, 200, Jason.encode!(token_exchange_rates)) end ) @@ -159,7 +161,9 @@ defmodule Explorer.TokenExchangeRatesTest do "GET", "/simple/token_price/ethereum", fn conn -> - assert conn.query_string == "vs_currencies=usd&include_market_cap=true&contract_addresses=#{joined_addresses}" + assert conn.query_string == + "vs_currencies=usd&include_market_cap=true&include_24hr_vol=true&contract_addresses=#{joined_addresses}" + Conn.resp(conn, 200, "{}") end ) @@ -239,7 +243,9 @@ defmodule Explorer.TokenExchangeRatesTest do "GET", "/simple/token_price/ethereum", fn conn -> - assert conn.query_string == "vs_currencies=usd&include_market_cap=true&contract_addresses=#{joined_addresses}" + assert conn.query_string == + "vs_currencies=usd&include_market_cap=true&include_24hr_vol=true&contract_addresses=#{joined_addresses}" + Conn.resp(conn, 429, "Too many requests") end ) diff --git a/apps/explorer/test/explorer/market/history/cataloger_test.exs b/apps/explorer/test/explorer/market/history/cataloger_test.exs index f7a86a960e2c..8e784ed0dc0c 100644 --- a/apps/explorer/test/explorer/market/history/cataloger_test.exs +++ b/apps/explorer/test/explorer/market/history/cataloger_test.exs @@ -54,13 +54,17 @@ defmodule Explorer.Market.History.CatalogerTest do """ Bypass.expect(bypass, fn conn -> Conn.resp(conn, 200, resp) end) - records = [%{date: ~D[2018-04-01], closing_price: Decimal.new(10), opening_price: Decimal.new(5)}] - expect(TestSource, :fetch_price_history, fn 1 -> {:ok, records} end) + + records = [ + %{date: ~D[2018-04-01], closing_price: Decimal.new(10), opening_price: Decimal.new(5), secondary_coin: false} + ] + + expect(TestSource, :fetch_price_history, fn 1, _ -> {:ok, records} end) set_mox_global() state = %{} assert {:noreply, state} == Cataloger.handle_info({:fetch_price_history, 1}, state) - assert_receive {_ref, {:price_history, {1, 0, {:ok, ^records}}}} + assert_receive {_ref, {:price_history, {1, 0, false, {:ok, ^records}}}} end test "handle_info with successful tasks (price, market cap and tvl)" do @@ -80,15 +84,15 @@ defmodule Explorer.Market.History.CatalogerTest do state2 = Map.put(state, :market_cap_records, market_cap_records) - state3 = Map.put(state2, :tvl_records, tvl_records) + assert {:noreply, state} == + Cataloger.handle_info({nil, {:price_history, {1, 0, false, {:ok, price_records}}}}, state) - assert {:noreply, state} == Cataloger.handle_info({nil, {:price_history, {1, 0, {:ok, price_records}}}}, state) - assert_receive {:fetch_market_cap_history, 365} + assert_receive {:fetch_market_cap_history, 1} assert {:noreply, state2} == Cataloger.handle_info({nil, {:market_cap_history, {0, 3, {:ok, market_cap_records}}}}, state) - assert {:noreply, state3} == + assert {:noreply, %{}} == Cataloger.handle_info({nil, {:tvl_history, {0, 3, {:ok, tvl_records}}}}, state2) assert record2 = Repo.get_by(MarketHistory, date: Enum.at(price_records, 1).date) @@ -113,15 +117,15 @@ defmodule Explorer.Market.History.CatalogerTest do state2 = Map.put(state, :market_cap_records, market_cap_records) - state3 = Map.put(state2, :tvl_records, []) + assert {:noreply, state} == + Cataloger.handle_info({nil, {:price_history, {1, 0, false, {:ok, price_records}}}}, state) - assert {:noreply, state} == Cataloger.handle_info({nil, {:price_history, {1, 0, {:ok, price_records}}}}, state) - assert_receive {:fetch_market_cap_history, 365} + assert_receive {:fetch_market_cap_history, 1} assert {:noreply, state2} == Cataloger.handle_info({nil, {:market_cap_history, {0, 3, {:ok, market_cap_records}}}}, state) - assert {:noreply, state3} == + assert {:noreply, %{}} == Cataloger.handle_info({nil, {:tvl_history, {0, 3, {:ok, tvl_records}}}}, state2) assert record = Repo.get_by(MarketHistory, date: Enum.at(price_records, 0).date) @@ -142,15 +146,15 @@ defmodule Explorer.Market.History.CatalogerTest do state2 = Map.put(state, :market_cap_records, market_cap_records) - state3 = Map.put(state2, :tvl_records, tvl_records) + assert {:noreply, state} == + Cataloger.handle_info({nil, {:price_history, {1, 0, false, {:ok, price_records}}}}, state) - assert {:noreply, state} == Cataloger.handle_info({nil, {:price_history, {1, 0, {:ok, price_records}}}}, state) - assert_receive {:fetch_market_cap_history, 365} + assert_receive {:fetch_market_cap_history, 1} assert {:noreply, state2} == Cataloger.handle_info({nil, {:market_cap_history, {0, 3, {:ok, market_cap_records}}}}, state) - assert {:noreply, state3} == + assert {:noreply, %{}} == Cataloger.handle_info({nil, {:tvl_history, {0, 3, {:ok, tvl_records}}}}, state2) assert record = Repo.get_by(MarketHistory, date: Enum.at(price_records, 0).date) @@ -159,6 +163,72 @@ defmodule Explorer.Market.History.CatalogerTest do assert record.tvl == nil end + test "current day values are saved in state" do + bypass = Bypass.open() + Application.put_env(:explorer, CryptoCompare, base_url: "http://localhost:#{bypass.port}") + old_env = Application.get_all_env(:explorer) + + Application.put_env(:explorer, Explorer.History.Process, base_backoff: 0) + + resp = + &""" + { + "Response": "Success", + "Type": 100, + "Aggregated": false, + "TimeTo": 1522569618, + "TimeFrom": 1522566018, + "FirstValueInArray": true, + "ConversionType": { + "type": "multiply", + "conversionSymbol": "ETH" + }, + "Data": [{ + "time": #{&1}, + "high": 10, + "low": 5, + "open": 5, + "volumefrom": 0, + "volumeto": 0, + "close": #{&2}, + "conversionType": "multiply", + "conversionSymbol": "ETH" + }], + "RateLimit": {}, + "HasWarning": false + } + """ + + Bypass.expect(bypass, fn conn -> + case conn.params["limit"] do + "365" -> Conn.resp(conn, 200, resp.(1_522_566_018, 10)) + _ -> Conn.resp(conn, 200, resp.(1_522_633_818, 20)) + end + end) + + {:ok, pid} = Cataloger.start_link([]) + + :timer.sleep(4000) + + Process.send(pid, {:fetch_price_history, 1}, []) + + :timer.sleep(4000) + + assert [ + %Explorer.Market.MarketHistory{ + date: ~D[2018-04-01] + } = first_entry, + %Explorer.Market.MarketHistory{ + date: ~D[2018-04-02] + } = second_entry + ] = MarketHistory |> Repo.all() + + assert Decimal.eq?(first_entry.closing_price, Decimal.new(10)) + assert Decimal.eq?(second_entry.closing_price, Decimal.new(20)) + + Application.put_all_env(explorer: old_env) + end + test "handle info for DOWN message" do assert {:noreply, %{}} == Cataloger.handle_info({:DOWN, nil, :process, nil, nil}, %{}) end diff --git a/apps/explorer/test/explorer/market/history/source/price/crypto_compare_test.exs b/apps/explorer/test/explorer/market/history/source/price/crypto_compare_test.exs index 2d8311b0ae1f..82f60c12d92a 100644 --- a/apps/explorer/test/explorer/market/history/source/price/crypto_compare_test.exs +++ b/apps/explorer/test/explorer/market/history/source/price/crypto_compare_test.exs @@ -63,28 +63,31 @@ defmodule Explorer.Market.History.Source.Price.CryptoCompareTest do %{ closing_price: Decimal.from_float(9655.77), date: ~D[2018-04-24], - opening_price: Decimal.from_float(8967.86) + opening_price: Decimal.from_float(8967.86), + secondary_coin: false }, %{ closing_price: Decimal.from_float(8873.62), date: ~D[2018-04-25], - opening_price: Decimal.from_float(9657.69) + opening_price: Decimal.from_float(9657.69), + secondary_coin: false }, %{ closing_price: Decimal.from_float(8804.32), date: ~D[2018-04-26], - opening_price: Decimal.from_float(8873.57) + opening_price: Decimal.from_float(8873.57), + secondary_coin: false } ] - assert {:ok, expected} == CryptoCompare.fetch_price_history(3) + assert {:ok, expected} == CryptoCompare.fetch_price_history(3, false) end test "with errored request", %{bypass: bypass} do error_text = ~S({"error": "server error"}) Bypass.expect(bypass, fn conn -> Conn.resp(conn, 500, error_text) end) - assert :error == CryptoCompare.fetch_price_history(3) + assert :error == CryptoCompare.fetch_price_history(3, false) end test "rejects empty prices", %{bypass: bypass} do @@ -135,10 +138,15 @@ defmodule Explorer.Market.History.Source.Price.CryptoCompareTest do Bypass.expect(bypass, fn conn -> Conn.resp(conn, 200, json) end) expected = [ - %{closing_price: Decimal.from_float(8804.32), date: ~D[2018-04-26], opening_price: Decimal.from_float(8873.57)} + %{ + closing_price: Decimal.from_float(8804.32), + date: ~D[2018-04-26], + opening_price: Decimal.from_float(8873.57), + secondary_coin: false + } ] - assert {:ok, expected} == CryptoCompare.fetch_price_history(3) + assert {:ok, expected} == CryptoCompare.fetch_price_history(3, false) end end end diff --git a/apps/explorer/test/support/fakes/no_op_price_source.ex b/apps/explorer/test/support/fakes/no_op_price_source.ex index b9a460ac8876..fa896d90032a 100644 --- a/apps/explorer/test/support/fakes/no_op_price_source.ex +++ b/apps/explorer/test/support/fakes/no_op_price_source.ex @@ -6,7 +6,7 @@ defmodule Explorer.ExchangeRates.Source.NoOpPriceSource do @behaviour SourcePrice @impl SourcePrice - def fetch_price_history(_previous_days) do + def fetch_price_history(_previous_days, _secondary_coin?) do {:ok, []} end end diff --git a/config/config_helper.exs b/config/config_helper.exs index b67e23e0e495..a703ac522bd1 100644 --- a/config/config_helper.exs +++ b/config/config_helper.exs @@ -170,6 +170,20 @@ defmodule ConfigHelper do end end + @spec exchange_rates_secondary_coin_price_source() :: Price.CoinGecko | Price.CoinMarketCap | Price.CryptoCompare + def exchange_rates_secondary_coin_price_source do + cmc_secondary_coin_id = System.get_env("EXCHANGE_RATES_COINMARKETCAP_SECONDARY_COIN_ID") + cg_secondary_coin_id = System.get_env("EXCHANGE_RATES_COINGECKO_SECONDARY_COIN_ID") + cc_secondary_coin_symbol = System.get_env("EXCHANGE_RATES_CRYPTOCOMPARE_SECONDARY_COIN_SYMBOL") + + cond do + cg_secondary_coin_id && cg_secondary_coin_id !== "" -> Price.CoinGecko + cmc_secondary_coin_id && cmc_secondary_coin_id !== "" -> Price.CoinMarketCap + cc_secondary_coin_symbol && cc_secondary_coin_symbol !== "" -> Price.CryptoCompare + true -> Price.CryptoCompare + end + end + def block_transformer do block_transformers = %{ "clique" => Blocks.Clique, diff --git a/config/runtime.exs b/config/runtime.exs index 96e2938e4499..b1f384bdba1b 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -292,10 +292,20 @@ config :explorer, Explorer.Counters.AddressTokenTransfersCounter, cache_period: ConfigHelper.parse_time_env_var("CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD", "1h") config :explorer, Explorer.Counters.LastOutputRootSizeCounter, - enabled: true, - enable_consolidation: true, + enabled: ConfigHelper.chain_type() == "optimism", + enable_consolidation: ConfigHelper.chain_type() == "optimism", cache_period: ConfigHelper.parse_time_env_var("CACHE_OPTIMISM_LAST_OUTPUT_ROOT_SIZE_COUNTER_PERIOD", "5m") +config :explorer, Explorer.Counters.Transactions24hStats, + enabled: true, + cache_period: ConfigHelper.parse_time_env_var("CACHE_TRANSACTIONS_24H_STATS_PERIOD", "1h"), + enable_consolidation: true + +config :explorer, Explorer.Counters.FreshPendingTransactionsCounter, + enabled: true, + cache_period: ConfigHelper.parse_time_env_var("CACHE_FRESH_PENDING_TRANSACTIONS_COUNTER_PERIOD", "5m"), + enable_consolidation: true + config :explorer, Explorer.ExchangeRates, store: :ets, enabled: !disable_exchange_rates?, @@ -304,20 +314,31 @@ config :explorer, Explorer.ExchangeRates, config :explorer, Explorer.ExchangeRates.Source, source: ConfigHelper.exchange_rates_source(), price_source: ConfigHelper.exchange_rates_price_source(), + secondary_coin_price_source: ConfigHelper.exchange_rates_secondary_coin_price_source(), market_cap_source: ConfigHelper.exchange_rates_market_cap_source(), tvl_source: ConfigHelper.exchange_rates_tvl_source() +cmc_secondary_coin_id = System.get_env("EXCHANGE_RATES_COINMARKETCAP_SECONDARY_COIN_ID") + config :explorer, Explorer.ExchangeRates.Source.CoinMarketCap, api_key: System.get_env("EXCHANGE_RATES_COINMARKETCAP_API_KEY"), - coin_id: System.get_env("EXCHANGE_RATES_COINMARKETCAP_COIN_ID") + coin_id: System.get_env("EXCHANGE_RATES_COINMARKETCAP_COIN_ID"), + secondary_coin_id: cmc_secondary_coin_id + +cg_secondary_coin_id = System.get_env("EXCHANGE_RATES_COINGECKO_SECONDARY_COIN_ID") config :explorer, Explorer.ExchangeRates.Source.CoinGecko, platform: System.get_env("EXCHANGE_RATES_COINGECKO_PLATFORM_ID"), api_key: System.get_env("EXCHANGE_RATES_COINGECKO_API_KEY"), - coin_id: System.get_env("EXCHANGE_RATES_COINGECKO_COIN_ID") + coin_id: System.get_env("EXCHANGE_RATES_COINGECKO_COIN_ID"), + secondary_coin_id: cg_secondary_coin_id config :explorer, Explorer.ExchangeRates.Source.DefiLlama, coin_id: System.get_env("EXCHANGE_RATES_DEFILLAMA_COIN_ID") +cc_secondary_coin_symbol = System.get_env("EXCHANGE_RATES_CRYPTOCOMPARE_SECONDARY_COIN_SYMBOL") + +config :explorer, Explorer.Market.History.Source.Price.CryptoCompare, secondary_coin_symbol: cc_secondary_coin_symbol + config :explorer, Explorer.ExchangeRates.TokenExchangeRates, enabled: !ConfigHelper.parse_bool_env_var("DISABLE_TOKEN_EXCHANGE_RATE", "true"), interval: ConfigHelper.parse_time_env_var("TOKEN_EXCHANGE_RATE_INTERVAL", "5s"), @@ -326,7 +347,8 @@ config :explorer, Explorer.ExchangeRates.TokenExchangeRates, config :explorer, Explorer.Market.History.Cataloger, enabled: !disable_indexer? && !disable_exchange_rates?, - history_fetch_interval: ConfigHelper.parse_time_env_var("MARKET_HISTORY_FETCH_INTERVAL", "1h") + history_fetch_interval: ConfigHelper.parse_time_env_var("MARKET_HISTORY_FETCH_INTERVAL", "1h"), + secondary_coin_enabled: cmc_secondary_coin_id || cg_secondary_coin_id || cc_secondary_coin_symbol config :explorer, Explorer.Chain.Transaction, suave_bid_contracts: System.get_env("SUAVE_BID_CONTRACTS", "") diff --git a/config/runtime/test.exs b/config/runtime/test.exs index ca3eed98e10c..786755f81298 100644 --- a/config/runtime/test.exs +++ b/config/runtime/test.exs @@ -16,6 +16,10 @@ config :block_scout_web, BlockScoutWeb.API.V2, enabled: true ### Explorer ### ################ +config :explorer, Explorer.Counters.Transactions24hStats, + cache_period: ConfigHelper.parse_time_env_var("CACHE_TRANSACTIONS_24H_STATS_PERIOD", "1h"), + enable_consolidation: false + variant = Variant.get() Code.require_file("#{variant}.exs", "apps/explorer/config/test") diff --git a/cspell.json b/cspell.json index cad6b4ad11e7..ec008f2588e1 100644 --- a/cspell.json +++ b/cspell.json @@ -189,6 +189,7 @@ "cooldown", "cooltesthost", "crossorigin", + "CRYPTOCOMPARE", "ctbs", "ctid", "cumalative", @@ -244,6 +245,8 @@ "exvcr", "falala", "FEVM", + "filecoin", + "Filecoin", "Filesize", "Filecoin", "fkey", @@ -378,6 +381,7 @@ "noproc", "noreferrer", "noreply", + "NOTOK", "noves", "nowarn", "nowrap", @@ -501,6 +505,7 @@ "successa", "successb", "supernet", + "sushiswap", "swal", "sweetalert", "tabindex", @@ -562,6 +567,7 @@ "valuemin", "valuenow", "varint", + "verifyproxycontract", "verifysourcecode", "viewerjs", "volumefrom", @@ -588,6 +594,7 @@ "yellowgreen", "zaphod", "zeppelinos", + "zetachain", "zftv", "ziczr", "zindex", diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index abf1c40689a3..11d5e7872571 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -41,9 +41,12 @@ EXCHANGE_RATES_COIN= # EXCHANGE_RATES_TVL_SOURCE= # EXCHANGE_RATES_PRICE_SOURCE= # EXCHANGE_RATES_COINGECKO_COIN_ID= +# EXCHANGE_RATES_COINGECKO_SECONDARY_COIN_ID= # EXCHANGE_RATES_COINGECKO_API_KEY= # EXCHANGE_RATES_COINMARKETCAP_API_KEY= # EXCHANGE_RATES_COINMARKETCAP_COIN_ID= +# EXCHANGE_RATES_COINMARKETCAP_SECONDARY_COIN_ID= +# EXCHANGE_RATES_CRYPTOCOMPARE_SECONDARY_COIN_SYMBOL= POOL_SIZE=80 # EXCHANGE_RATES_COINGECKO_PLATFORM_ID= # TOKEN_EXCHANGE_RATE_INTERVAL= @@ -91,6 +94,8 @@ CACHE_MARKET_HISTORY_PERIOD=21600 CACHE_ADDRESS_TRANSACTIONS_COUNTER_PERIOD=1800 CACHE_ADDRESS_TOKENS_USD_SUM_PERIOD=3600 CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD=1800 +# CACHE_TRANSACTIONS_24H_STATS_PERIOD= +# CACHE_FRESH_PENDING_TRANSACTIONS_COUNTER_PERIOD= TOKEN_METADATA_UPDATE_INTERVAL=172800 CONTRACT_VERIFICATION_ALLOWED_SOLIDITY_EVM_VERSIONS=homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,paris,shanghai,cancun,default CONTRACT_VERIFICATION_ALLOWED_VYPER_EVM_VERSIONS=byzantium,constantinople,petersburg,istanbul,berlin,paris,shanghai,cancun,default From ee3b9c2c28743e39c341a4110a80b70b57d61e60 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Fri, 15 Mar 2024 17:40:29 +0300 Subject: [PATCH 605/607] ERC-404 basic support (#9407) * ERC-404 basic support * rename nft_token_ to nft_ * ERC-404 support additions * Cover with token transfer parsing tests * Cover ERC-404 with token balance tests * Cover ERC-404 with current token balance tests * Notification summary tests * Some more tests * Update apps/block_scout_web/lib/block_scout_web/views/tokens/helper.ex Co-authored-by: Qwerty5Uiop <105209995+Qwerty5Uiop@users.noreply.github.com> * Process review comments * Process review comment * Format changes --------- Co-authored-by: Qwerty5Uiop <105209995+Qwerty5Uiop@users.noreply.github.com> --- CHANGELOG.md | 1 + .../channels/address_channel.ex | 1 + .../account/api/v1/user_controller.ex | 22 ++- .../controllers/api/rpc/address_controller.ex | 10 +- .../controllers/api/v2/address_controller.ex | 6 +- .../controllers/api/v2/token_controller.ex | 2 +- .../tokens/instance/holder_controller.ex | 2 +- .../tokens/instance/metadata_controller.ex | 2 +- .../tokens/instance/transfer_controller.ex | 2 +- .../lib/block_scout_web/paging_helper.ex | 12 +- .../views/account/api/v1/user_view.ex | 9 +- .../views/api/rpc/address_view.ex | 11 +- .../views/api/v2/address_view.ex | 2 +- .../block_scout_web/views/tokens/helper.ex | 31 ++-- .../views/tokens/holder_view.ex | 10 ++ .../views/tokens/overview_view.ex | 1 + .../block_scout_web/views/transaction_view.ex | 1 + apps/block_scout_web/priv/gettext/default.pot | 125 ++++++++------- .../priv/gettext/en/LC_MESSAGES/default.po | 142 +++++++++--------- .../account/api/v1/user_controller_test.exs | 4 + .../address_token_controller_test.exs | 31 ++++ .../api/v2/address_controller_test.exs | 36 +++++ .../api/v2/token_controller_test.exs | 85 ++++++++++- .../lib/explorer/account/notifier/email.ex | 3 + .../lib/explorer/account/notifier/notify.ex | 3 + .../lib/explorer/account/notifier/summary.ex | 16 ++ .../lib/explorer/account/watchlist_address.ex | 4 +- apps/explorer/lib/explorer/chain.ex | 39 +++-- .../explorer/chain/address/token_balance.ex | 6 +- .../runner/address/current_token_balances.ex | 3 +- .../import/runner/address/token_balances.ex | 9 +- .../lib/explorer/chain/shibarium/bridge.ex | 2 +- apps/explorer/lib/explorer/chain/token.ex | 2 + .../lib/explorer/chain/token/instance.ex | 83 +++++++++- .../lib/explorer/chain/token_transfer.ex | 6 + apps/explorer/lib/explorer/etherscan.ex | 14 +- .../lib/explorer/token/balance_reader.ex | 6 +- ...unt_watchlist_addresses_erc_404_fields.exs | 10 ++ .../account/notifier/summary_test.exs | 108 ++++++++++++- .../address/current_token_balances_test.exs | 45 +++++- apps/explorer/test/explorer/chain_test.exs | 6 +- apps/explorer/test/support/factory.ex | 17 ++- .../fetcher/token_balance_on_demand.ex | 14 +- .../token_instance/metadata_retriever.ex | 2 +- apps/indexer/lib/indexer/token_balances.ex | 36 ++--- .../transform/address_token_balances.ex | 5 +- .../lib/indexer/transform/token_instances.ex | 17 +++ .../lib/indexer/transform/token_transfers.ex | 130 +++++++++++++--- .../indexer/fetcher/token_balance_test.exs | 28 +++- .../test/indexer/token_balances_test.exs | 118 +++++++++++++++ .../transform/token_transfers_test.exs | 86 ++++++++++- 51 files changed, 1108 insertions(+), 258 deletions(-) create mode 100644 apps/explorer/priv/account/migrations/20240219152220_add_account_watchlist_addresses_erc_404_fields.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d6710ed8d20..4458b99401d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,7 @@ - [#9441](https://github.com/blockscout/blockscout/pull/9441) - Update BENS integration: change endpoint for resolving address in search - [#9437](https://github.com/blockscout/blockscout/pull/9437) - Add Enum.uniq before sanitizing token transfers +- [#9407](https://github.com/blockscout/blockscout/pull/9407) - ERC-404 basic support - [#9403](https://github.com/blockscout/blockscout/pull/9403) - Null round handling - [#9401](https://github.com/blockscout/blockscout/pull/9401) - Eliminate incorrect token transfers with empty token_ids - [#9396](https://github.com/blockscout/blockscout/pull/9396) - More-Minimal Proxy support diff --git a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex index 378e09e02dd2..8f95108bb2be 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex @@ -241,6 +241,7 @@ defmodule BlockScoutWeb.AddressChannel do push_current_token_balances(socket, address_current_token_balances, "erc_20", "ERC-20") push_current_token_balances(socket, address_current_token_balances, "erc_721", "ERC-721") push_current_token_balances(socket, address_current_token_balances, "erc_1155", "ERC-1155") + push_current_token_balances(socket, address_current_token_balances, "erc_404", "ERC-404") {:noreply, socket} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex index 724378cc69d6..af723cacf13c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex @@ -146,12 +146,15 @@ defmodule BlockScoutWeb.Account.Api.V1.UserController do "ERC-721" => %{ "incoming" => watch_erc_721_input, "outcoming" => watch_erc_721_output - } - # , + }, # "ERC-1155" => %{ # "incoming" => watch_erc_1155_input, # "outcoming" => watch_erc_1155_output - # } + # }, + "ERC-404" => %{ + "incoming" => watch_erc_404_input, + "outcoming" => watch_erc_404_output + } }, "notification_methods" => %{ "email" => notify_email @@ -167,6 +170,8 @@ defmodule BlockScoutWeb.Account.Api.V1.UserController do watch_erc_721_output: watch_erc_721_output, watch_erc_1155_input: watch_erc_721_input, watch_erc_1155_output: watch_erc_721_output, + watch_erc_404_input: watch_erc_404_input, + watch_erc_404_output: watch_erc_404_output, notify_email: notify_email, address_hash: address_hash } @@ -202,12 +207,15 @@ defmodule BlockScoutWeb.Account.Api.V1.UserController do "ERC-721" => %{ "incoming" => watch_erc_721_input, "outcoming" => watch_erc_721_output - } - # , + }, # "ERC-1155" => %{ # "incoming" => watch_erc_1155_input, # "outcoming" => watch_erc_1155_output - # } + # }, + "ERC-404" => %{ + "incoming" => watch_erc_404_input, + "outcoming" => watch_erc_404_output + } }, "notification_methods" => %{ "email" => notify_email @@ -224,6 +232,8 @@ defmodule BlockScoutWeb.Account.Api.V1.UserController do watch_erc_721_output: watch_erc_721_output, watch_erc_1155_input: watch_erc_721_input, watch_erc_1155_output: watch_erc_721_output, + watch_erc_404_input: watch_erc_404_input, + watch_erc_404_output: watch_erc_404_output, notify_email: notify_email, address_hash: address_hash } diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex index ff81d7fc2da0..140dea3512c6 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex @@ -200,7 +200,7 @@ defmodule BlockScoutWeb.API.RPC.AddressController do {:contract_address, to_address_hash_optional(params["contractaddress"])}, true <- !is_nil(address_hash) or !is_nil(contract_address_hash), {:ok, token_transfers, max_block_number} <- - list_nft_token_transfers(address_hash, contract_address_hash, options) do + list_nft_transfers(address_hash, contract_address_hash, options) do render(conn, :tokennfttx, %{token_transfers: token_transfers, max_block_number: max_block_number}) else false -> @@ -531,10 +531,10 @@ defmodule BlockScoutWeb.API.RPC.AddressController do end end - defp list_nft_token_transfers(nil, contract_address_hash, options) do + defp list_nft_transfers(nil, contract_address_hash, options) do with {:ok, max_block_number} <- Chain.max_consensus_block_number(), token_transfers when token_transfers != [] <- - Etherscan.list_nft_token_transfers_by_token(contract_address_hash, options) do + Etherscan.list_nft_transfers_by_token(contract_address_hash, options) do {:ok, token_transfers, max_block_number} else _ -> @@ -542,11 +542,11 @@ defmodule BlockScoutWeb.API.RPC.AddressController do end end - defp list_nft_token_transfers(address_hash, contract_address_hash, options) do + defp list_nft_transfers(address_hash, contract_address_hash, options) do with {:address, :ok} <- {:address, Address.check_address_exists(address_hash, @api_true)}, {:ok, max_block_number} <- Chain.max_consensus_block_number(), token_transfers when token_transfers != [] <- - Etherscan.list_nft_token_transfers(address_hash, contract_address_hash, options) do + Etherscan.list_nft_transfers(address_hash, contract_address_hash, options) do {:ok, token_transfers, max_block_number} else _ -> diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex index 6a02b832d96b..509e395dd1de 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -17,7 +17,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do delete_parameters_from_next_page_params: 1, token_transfers_types_options: 1, address_transactions_sorting: 1, - nft_token_types_options: 1 + nft_types_options: 1 ] import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1, maybe_preload_ens_to_address: 1] @@ -449,7 +449,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do address_hash, params |> paging_options() - |> Keyword.merge(nft_token_types_options(params)) + |> Keyword.merge(nft_types_options(params)) |> Keyword.merge(@api_true) |> Keyword.merge(@nft_necessity_by_association) ) @@ -477,7 +477,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do address_hash, params |> paging_options() - |> Keyword.merge(nft_token_types_options(params)) + |> Keyword.merge(nft_types_options(params)) |> Keyword.merge(@api_true) |> Keyword.merge(@nft_necessity_by_association) ) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex index 3a62f1de48e7..0be915d1e4ac 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex @@ -189,7 +189,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do {:not_found, false} <- {:not_found, Chain.erc_20_token?(token)}, {:format, {token_id, ""}} <- {:format, Integer.parse(token_id_str)} do token_instance = - case Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, address_hash, @api_true) do + case Chain.nft_instance_from_token_id_and_token_address(token_id, address_hash, @api_true) do {:ok, token_instance} -> token_instance |> Chain.select_repo(@api_true).preload(:owner) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/holder_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/holder_controller.ex index 4794e14723b4..2b2eab99b7ef 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/holder_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/holder_controller.ex @@ -60,7 +60,7 @@ defmodule BlockScoutWeb.Tokens.Instance.HolderController do {:ok, token} <- Chain.token_from_address_hash(hash, options), false <- Chain.erc_20_token?(token), {token_id, ""} <- Integer.parse(token_id_str) do - case Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, hash) do + case Chain.nft_instance_from_token_id_and_token_address(token_id, hash) do {:ok, token_instance} -> Helper.render(conn, token_instance, hash, token_id, token) {:error, :not_found} -> Helper.render(conn, nil, hash, token_id, token) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/metadata_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/metadata_controller.ex index fe691d770e17..0036a95563ca 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/metadata_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/metadata_controller.ex @@ -12,7 +12,7 @@ defmodule BlockScoutWeb.Tokens.Instance.MetadataController do false <- Chain.erc_20_token?(token), {token_id, ""} <- Integer.parse(token_id_str), {:ok, token_instance} <- - Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, hash) do + Chain.nft_instance_from_token_id_and_token_address(token_id, hash) do if token_instance.metadata do Helper.render(conn, token_instance, hash, token_id, token) else diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex index bd52bdba24f0..30a8212a75f9 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex @@ -63,7 +63,7 @@ defmodule BlockScoutWeb.Tokens.Instance.TransferController do {:ok, token} <- Chain.token_from_address_hash(hash, options), false <- Chain.erc_20_token?(token), {token_id, ""} <- Integer.parse(token_id_str) do - case Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, hash) do + case Chain.nft_instance_from_token_id_and_token_address(token_id, hash) do {:ok, token_instance} -> Helper.render(conn, token_instance, hash, token_id, token) {:error, :not_found} -> Helper.render(conn, nil, hash, token_id, token) end diff --git a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex index 72914f1f5d34..0626de4025fe 100644 --- a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex @@ -32,8 +32,8 @@ defmodule BlockScoutWeb.PagingHelper do ] end - @allowed_token_transfer_type_labels ["ERC-20", "ERC-721", "ERC-1155"] - @allowed_nft_token_type_labels ["ERC-721", "ERC-1155"] + @allowed_token_transfer_type_labels ["ERC-20", "ERC-721", "ERC-1155", "ERC-404"] + @allowed_nft_type_labels ["ERC-721", "ERC-1155", "ERC-404"] @allowed_chain_id [1, 56, 99] @allowed_stability_validators_states ["active", "probation", "inactive"] @@ -80,14 +80,14 @@ defmodule BlockScoutWeb.PagingHelper do @doc """ Parse 'type' query parameter from request option map """ - @spec nft_token_types_options(map()) :: [{:token_type, list}] - def nft_token_types_options(%{"type" => filters}) do + @spec nft_types_options(map()) :: [{:token_type, list}] + def nft_types_options(%{"type" => filters}) do [ - token_type: filters_to_list(filters, @allowed_nft_token_type_labels) + token_type: filters_to_list(filters, @allowed_nft_type_labels) ] end - def nft_token_types_options(_), do: [token_type: []] + def nft_types_options(_), do: [token_type: []] defp filters_to_list(filters, allowed, variant \\ :upcase) defp filters_to_list(filters, allowed, :downcase), do: filters |> String.downcase() |> parse_filter(allowed) diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex index 26c8d7c8295b..553d01206ad9 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/account/api/v1/user_view.ex @@ -112,12 +112,15 @@ defmodule BlockScoutWeb.Account.Api.V1.UserView do "ERC-721" => %{ "incoming" => watchlist.watch_erc_721_input, "outcoming" => watchlist.watch_erc_721_output - } - # , + }, # "ERC-1155" => %{ # "incoming" => watchlist.watch_erc_1155_input, # "outcoming" => watchlist.watch_erc_1155_output - # } + # }, + "ERC-404" => %{ + "incoming" => watchlist.watch_erc_404_input, + "outcoming" => watchlist.watch_erc_404_output + } }, "notification_methods" => %{ "email" => watchlist.notify_email diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex index 25ecdb2fc961..eca2f51a14e0 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex @@ -44,7 +44,7 @@ defmodule BlockScoutWeb.API.RPC.AddressView do end def render("tokennfttx.json", %{token_transfers: token_transfers, max_block_number: max_block_number}) do - data = Enum.map(token_transfers, &prepare_nft_token_transfer(&1, max_block_number)) + data = Enum.map(token_transfers, &prepare_nft_transfer(&1, max_block_number)) RPCView.render("show.json", data: data) end @@ -192,6 +192,13 @@ defmodule BlockScoutWeb.API.RPC.AddressView do |> Map.put_new(:values, token_transfer.amounts) end + defp prepare_token_transfer(%{token_type: "ERC-404"} = token_transfer) do + token_transfer + |> prepare_common_token_transfer() + |> Map.put_new(:tokenIDs, token_transfer.token_ids) + |> Map.put_new(:values, token_transfer.amounts) + end + defp prepare_token_transfer(%{token_type: "ERC-20"} = token_transfer) do token_transfer |> prepare_common_token_transfer() @@ -202,7 +209,7 @@ defmodule BlockScoutWeb.API.RPC.AddressView do prepare_common_token_transfer(token_transfer) end - defp prepare_nft_token_transfer(token_transfer, max_block_number) do + defp prepare_nft_transfer(token_transfer, max_block_number) do %{ "blockNumber" => to_string(token_transfer.block_number), "timeStamp" => to_string(DateTime.to_unix(token_transfer.block.timestamp)), diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex index aafe02d9a90b..0c177f3c1957 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex @@ -230,7 +230,7 @@ defmodule BlockScoutWeb.API.V2.AddressView do ) :: map() def fetch_and_render_token_instance(token_id, token, address_hash, token_balance) do token_instance = - case Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address( + case Chain.nft_instance_from_token_id_and_token_address( token_id, token.contract_address_hash, @api_true diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/helper.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/helper.ex index 309732bd85f2..d7103f0314d4 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/helper.ex @@ -31,29 +31,33 @@ defmodule BlockScoutWeb.Tokens.Helper do end # TODO: remove this clause along with token transfer denormalization - defp do_token_transfer_amount(%Token{type: "ERC-20"}, nil, nil, nil, _token_ids) do + defp do_token_transfer_amount(%Token{type: type}, nil, nil, nil, _token_ids) when type in ["ERC-20", "ERC-404"] do {:ok, "--"} end - defp do_token_transfer_amount(_token, "ERC-20", nil, nil, _token_ids) do + defp do_token_transfer_amount(_token, type, nil, nil, _token_ids) when type in ["ERC-20", "ERC-404"] do {:ok, "--"} end # TODO: remove this clause along with token transfer denormalization - defp do_token_transfer_amount(%Token{type: "ERC-20", decimals: nil}, nil, amount, _amounts, _token_ids) do + defp do_token_transfer_amount(%Token{type: type, decimals: nil}, nil, amount, _amounts, _token_ids) + when type in ["ERC-20", "ERC-404"] do {:ok, CurrencyHelper.format_according_to_decimals(amount, Decimal.new(0))} end - defp do_token_transfer_amount(%Token{decimals: nil}, "ERC-20", amount, _amounts, _token_ids) do + defp do_token_transfer_amount(%Token{decimals: nil}, type, amount, _amounts, _token_ids) + when type in ["ERC-20", "ERC-404"] do {:ok, CurrencyHelper.format_according_to_decimals(amount, Decimal.new(0))} end # TODO: remove this clause along with token transfer denormalization - defp do_token_transfer_amount(%Token{type: "ERC-20", decimals: decimals}, nil, amount, _amounts, _token_ids) do + defp do_token_transfer_amount(%Token{type: type, decimals: decimals}, nil, amount, _amounts, _token_ids) + when type in ["ERC-20", "ERC-404"] do {:ok, CurrencyHelper.format_according_to_decimals(amount, decimals)} end - defp do_token_transfer_amount(%Token{decimals: decimals}, "ERC-20", amount, _amounts, _token_ids) do + defp do_token_transfer_amount(%Token{decimals: decimals}, type, amount, _amounts, _token_ids) + when type in ["ERC-20", "ERC-404"] do {:ok, CurrencyHelper.format_according_to_decimals(amount, decimals)} end @@ -102,32 +106,35 @@ defmodule BlockScoutWeb.Tokens.Helper do end # TODO: remove this clause along with token transfer denormalization - defp do_token_transfer_amount_for_api(%Token{type: "ERC-20"}, nil, nil, nil, _token_ids) do + defp do_token_transfer_amount_for_api(%Token{type: type}, nil, nil, nil, _token_ids) + when type in ["ERC-20", "ERC-404"] do {:ok, nil} end - defp do_token_transfer_amount_for_api(_token, "ERC-20", nil, nil, _token_ids) do + defp do_token_transfer_amount_for_api(_token, type, nil, nil, _token_ids) when type in ["ERC-20", "ERC-404"] do {:ok, nil} end # TODO: remove this clause along with token transfer denormalization defp do_token_transfer_amount_for_api( - %Token{type: "ERC-20", decimals: decimals}, + %Token{type: type, decimals: decimals}, nil, amount, _amounts, _token_ids - ) do + ) + when type in ["ERC-20", "ERC-404"] do {:ok, amount, decimals} end defp do_token_transfer_amount_for_api( %Token{decimals: decimals}, - "ERC-20", + type, amount, _amounts, _token_ids - ) do + ) + when type in ["ERC-20", "ERC-404"] do {:ok, amount, decimals} end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex index 2edfd9398119..745b041ddd64 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex @@ -70,6 +70,16 @@ defmodule BlockScoutWeb.Tokens.HolderView do to_string(format_according_to_decimals(value, decimals)) <> " TokenID " <> to_string(id) end + def format_token_balance_value(value, id, %Token{type: "ERC-404", decimals: decimals}) do + base = to_string(format_according_to_decimals(value, decimals)) + + if id do + base <> " TokenID " <> to_string(id) + else + base + end + end + def format_token_balance_value(value, _id, _token) do value end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex index 441ac1a0a4f1..497186464c0c 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex @@ -44,6 +44,7 @@ defmodule BlockScoutWeb.Tokens.OverviewView do def display_inventory?(%Token{type: "ERC-721"}), do: true def display_inventory?(%Token{type: "ERC-1155"}), do: true + def display_inventory?(%Token{type: "ERC-404"}), do: true def display_inventory?(_), do: false def smart_contract_with_read_only_functions?( diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex index c3d741741211..587c57cf062f 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex @@ -223,6 +223,7 @@ defmodule BlockScoutWeb.TransactionView do :erc20 -> gettext("ERC-20 ") :erc721 -> gettext("ERC-721 ") :erc1155 -> gettext("ERC-1155 ") + :erc404 -> gettext("ERC-404 ") _ -> "" end end diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index a36415acff7a..d56fadb7cfb6 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -1,16 +1,15 @@ -#: lib/block_scout_web/views/address_token_balance_view.ex:10 -#, elixir-autogen, elixir-format -msgid "%{count} token" -msgid_plural "%{count} tokens" -msgstr[0] "" -msgstr[1] "" - -#: lib/block_scout_web/templates/block/_tile.html.eex:29 -#, elixir-autogen, elixir-format -msgid "%{count} transaction" -msgid_plural "%{count} transactions" -msgstr[0] "" -msgstr[1] "" +## This file is a PO Template file. +## +## "msgid"s here are often extracted from source code. +## Add new messages manually only if they're dynamic +## messages that can't be statically extracted. +## +## Run "mix gettext.extract" to bring this file up to +## date. Leave "msgstr"s empty as changing them here has no +## effect: edit them in PO (.po) files instead. +# +msgid "" +msgstr "" #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:9 #, elixir-autogen, elixir-format @@ -53,6 +52,20 @@ msgstr "" msgid "%{count} Transactions" msgstr "" +#: lib/block_scout_web/views/address_token_balance_view.ex:10 +#, elixir-autogen, elixir-format +msgid "%{count} token" +msgid_plural "%{count} tokens" +msgstr[0] "" +msgstr[1] "" + +#: lib/block_scout_web/templates/block/_tile.html.eex:29 +#, elixir-autogen, elixir-format +msgid "%{count} transaction" +msgid_plural "%{count} transactions" +msgstr[0] "" +msgstr[1] "" + #: lib/block_scout_web/templates/transaction/_actions.html.eex:101 #, elixir-autogen, elixir-format msgid "%{qty} of Token ID [%{link_to_id}]" @@ -68,7 +81,7 @@ msgstr "" msgid "%{subnetwork} Explorer - BlockScout" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:374 +#: lib/block_scout_web/views/transaction_view.ex:375 #, elixir-autogen, elixir-format msgid "(Awaiting internal transactions for status)" msgstr "" @@ -671,7 +684,7 @@ msgstr "" msgid "Compiler version" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:367 +#: lib/block_scout_web/views/transaction_view.ex:368 #, elixir-autogen, elixir-format msgid "Confirmed" msgstr "" @@ -1265,12 +1278,12 @@ msgstr "" msgid "Error trying to fetch balances." msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:378 +#: lib/block_scout_web/views/transaction_view.ex:379 #, elixir-autogen, elixir-format msgid "Error: %{reason}" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:376 +#: lib/block_scout_web/views/transaction_view.ex:377 #, elixir-autogen, elixir-format msgid "Error: (Awaiting internal transactions for reason)" msgstr "" @@ -1302,6 +1315,11 @@ msgstr "" msgid "Expand" msgstr "" +#: lib/block_scout_web/templates/csv_export/index.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Export" +msgstr "" + #: lib/block_scout_web/templates/csv_export/index.html.eex:10 #, elixir-autogen, elixir-format msgid "Export Data" @@ -1708,7 +1726,7 @@ msgstr "" msgid "Max Priority Fee per Gas" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:330 +#: lib/block_scout_web/views/transaction_view.ex:331 #, elixir-autogen, elixir-format msgid "Max of" msgstr "" @@ -1855,6 +1873,16 @@ msgstr "" msgid "New Smart Contract Verification" msgstr "" +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:9 +#, elixir-autogen, elixir-format +msgid "New Smart Contract Verification via Standard input JSON" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:5 +#, elixir-autogen, elixir-format +msgid "New Smart Contract Verification via metadata JSON" +msgstr "" + #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 #, elixir-autogen, elixir-format @@ -2487,7 +2515,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:375 +#: lib/block_scout_web/views/transaction_view.ex:376 #, elixir-autogen, elixir-format msgid "Success" msgstr "" @@ -3114,7 +3142,7 @@ msgstr "" msgid "Uncles" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:366 +#: lib/block_scout_web/views/transaction_view.ex:367 #, elixir-autogen, elixir-format msgid "Unconfirmed" msgstr "" @@ -3461,6 +3489,12 @@ msgstr "" msgid "Your request contained an error, perhaps a mistyped tx/block/address hash. Try again, and check the developer tools console for more info." msgstr "" +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:38 +#: lib/block_scout_web/views/verified_contracts_view.ex:12 +#, elixir-autogen, elixir-format +msgid "Yul" +msgstr "" + #: lib/block_scout_web/templates/address/overview.html.eex:111 #, elixir-autogen, elixir-format msgid "at" @@ -3471,6 +3505,16 @@ msgstr "" msgid "balance of the address" msgstr "" +#: lib/block_scout_web/templates/transaction/overview.html.eex:437 +#, elixir-autogen, elixir-format +msgid "burnt for this transaction. Equals Block Base Fee per Gas * Gas Used." +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:215 +#, elixir-autogen, elixir-format +msgid "burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used)." +msgstr "" + #: lib/block_scout_web/templates/address_contract/index.html.eex:27 #, elixir-autogen, elixir-format msgid "button" @@ -3506,6 +3550,11 @@ msgstr "" msgid "false" msgstr "" +#: lib/block_scout_web/templates/csv_export/index.html.eex:14 +#, elixir-autogen, elixir-format +msgid "for address" +msgstr "" + #: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 @@ -3557,6 +3606,11 @@ msgstr "" msgid "string" msgstr "" +#: lib/block_scout_web/templates/csv_export/index.html.eex:17 +#, elixir-autogen, elixir-format +msgid "to CSV file" +msgstr "" + #: lib/block_scout_web/views/address_contract_view.ex:29 #, elixir-autogen, elixir-format msgid "true" @@ -3681,37 +3735,6 @@ msgstr "" msgid "%{withdrawals_count} withdrawals processed and %{withdrawals_sum} withdrawn." msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:14 -#, elixir-autogen, elixir-format -msgid "Export" -msgstr "" - -#: lib/block_scout_web/templates/csv_export/index.html.eex:14 -#, elixir-autogen, elixir-format -msgid "for address" -msgstr "" - -#: lib/block_scout_web/templates/csv_export/index.html.eex:17 -#, elixir-autogen, elixir-format -msgid "to CSV file" -msgstr "" - -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:38 -#: lib/block_scout_web/views/verified_contracts_view.ex:12 -#, elixir-autogen, elixir-format -msgid "Yul" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:469 -#, elixir-autogen, elixir-format -msgid "burnt for this transaction. Equals Block Base Fee per Gas * Gas Used." -msgstr "" - -#: lib/block_scout_web/templates/block/overview.html.eex:215 -#, elixir-autogen, elixir-format -msgid "burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used)." -msgstr "" - #: lib/block_scout_web/templates/transaction/overview.html.eex:488 #, elixir-autogen, elixir-format msgid "Actual gas amount used by the transaction." diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 47059226f782..b6e1f7825b65 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -1,16 +1,15 @@ -#: lib/block_scout_web/views/address_token_balance_view.ex:10 -#, elixir-autogen, elixir-format -msgid "%{count} token" -msgid_plural "%{count} tokens" -msgstr[0] "" -msgstr[1] "" - -#: lib/block_scout_web/templates/block/_tile.html.eex:29 -#, elixir-autogen, elixir-format -msgid "%{count} transaction" -msgid_plural "%{count} transactions" -msgstr[0] "" -msgstr[1] "" +## "msgid"s in this file come from POT (.pot) files. +### +### Do not add, change, or remove "msgid"s manually here as +### they're tied to the ones in the corresponding POT file +### (with the same domain). +### +### Use "mix gettext.extract --merge" or "mix gettext.merge" +### to merge POT files into PO files. +msgid "" +msgstr "" +"Language: en\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:9 #, elixir-autogen, elixir-format @@ -53,6 +52,20 @@ msgstr "" msgid "%{count} Transactions" msgstr "" +#: lib/block_scout_web/views/address_token_balance_view.ex:10 +#, elixir-autogen, elixir-format +msgid "%{count} token" +msgid_plural "%{count} tokens" +msgstr[0] "" +msgstr[1] "" + +#: lib/block_scout_web/templates/block/_tile.html.eex:29 +#, elixir-autogen, elixir-format +msgid "%{count} transaction" +msgid_plural "%{count} transactions" +msgstr[0] "" +msgstr[1] "" + #: lib/block_scout_web/templates/transaction/_actions.html.eex:101 #, elixir-autogen, elixir-format msgid "%{qty} of Token ID [%{link_to_id}]" @@ -68,7 +81,7 @@ msgstr "" msgid "%{subnetwork} Explorer - BlockScout" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:374 +#: lib/block_scout_web/views/transaction_view.ex:375 #, elixir-autogen, elixir-format msgid "(Awaiting internal transactions for status)" msgstr "" @@ -671,7 +684,7 @@ msgstr "" msgid "Compiler version" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:367 +#: lib/block_scout_web/views/transaction_view.ex:368 #, elixir-autogen, elixir-format msgid "Confirmed" msgstr "" @@ -1265,12 +1278,12 @@ msgstr "" msgid "Error trying to fetch balances." msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:378 +#: lib/block_scout_web/views/transaction_view.ex:379 #, elixir-autogen, elixir-format msgid "Error: %{reason}" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:376 +#: lib/block_scout_web/views/transaction_view.ex:377 #, elixir-autogen, elixir-format msgid "Error: (Awaiting internal transactions for reason)" msgstr "" @@ -1302,6 +1315,11 @@ msgstr "" msgid "Expand" msgstr "" +#: lib/block_scout_web/templates/csv_export/index.html.eex:14 +#, elixir-autogen, elixir-format +msgid "Export" +msgstr "" + #: lib/block_scout_web/templates/csv_export/index.html.eex:10 #, elixir-autogen, elixir-format msgid "Export Data" @@ -1708,7 +1726,7 @@ msgstr "" msgid "Max Priority Fee per Gas" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:330 +#: lib/block_scout_web/views/transaction_view.ex:331 #, elixir-autogen, elixir-format msgid "Max of" msgstr "" @@ -1855,6 +1873,16 @@ msgstr "" msgid "New Smart Contract Verification" msgstr "" +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:9 +#, elixir-autogen, elixir-format +msgid "New Smart Contract Verification via Standard input JSON" +msgstr "" + +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:5 +#, elixir-autogen, elixir-format +msgid "New Smart Contract Verification via metadata JSON" +msgstr "" + #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 #, elixir-autogen, elixir-format @@ -2487,7 +2515,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:375 +#: lib/block_scout_web/views/transaction_view.ex:376 #, elixir-autogen, elixir-format msgid "Success" msgstr "" @@ -3114,7 +3142,7 @@ msgstr "" msgid "Uncles" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:366 +#: lib/block_scout_web/views/transaction_view.ex:367 #, elixir-autogen, elixir-format msgid "Unconfirmed" msgstr "" @@ -3461,6 +3489,12 @@ msgstr "" msgid "Your request contained an error, perhaps a mistyped tx/block/address hash. Try again, and check the developer tools console for more info." msgstr "" +#: lib/block_scout_web/templates/verified_contracts/index.html.eex:38 +#: lib/block_scout_web/views/verified_contracts_view.ex:12 +#, elixir-autogen, elixir-format +msgid "Yul" +msgstr "" + #: lib/block_scout_web/templates/address/overview.html.eex:111 #, elixir-autogen, elixir-format msgid "at" @@ -3471,6 +3505,16 @@ msgstr "" msgid "balance of the address" msgstr "" +#: lib/block_scout_web/templates/transaction/overview.html.eex:437 +#, elixir-autogen, elixir-format +msgid "burnt for this transaction. Equals Block Base Fee per Gas * Gas Used." +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:215 +#, elixir-autogen, elixir-format +msgid "burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used)." +msgstr "" + #: lib/block_scout_web/templates/address_contract/index.html.eex:27 #, elixir-autogen, elixir-format msgid "button" @@ -3506,6 +3550,11 @@ msgstr "" msgid "false" msgstr "" +#: lib/block_scout_web/templates/csv_export/index.html.eex:14 +#, elixir-autogen, elixir-format +msgid "for address" +msgstr "" + #: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 @@ -3557,6 +3606,11 @@ msgstr "" msgid "string" msgstr "" +#: lib/block_scout_web/templates/csv_export/index.html.eex:17 +#, elixir-autogen, elixir-format +msgid "to CSV file" +msgstr "" + #: lib/block_scout_web/views/address_contract_view.ex:29 #, elixir-autogen, elixir-format msgid "true" @@ -3642,23 +3696,6 @@ msgstr "" msgid "Beacon chain, Withdrawals, %{subnetwork}, %{coin}" msgstr "" -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:29 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:23 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:23 -#, elixir-autogen, elixir-format, fuzzy -msgid "Index" -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:9 -#, elixir-autogen, elixir-format, fuzzy -msgid "New Smart Contract Verification via Standard input JSON" -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:5 -#, elixir-autogen, elixir-format, fuzzy -msgid "New Smart Contract Verification via metadata JSON" -msgstr "" - #: lib/block_scout_web/templates/layout/_footer.html.eex:31 #, elixir-autogen, elixir-format msgid "Telegram" @@ -3681,37 +3718,6 @@ msgstr "" msgid "%{withdrawals_count} withdrawals processed and %{withdrawals_sum} withdrawn." msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:14 -#, elixir-autogen, elixir-format, fuzzy -msgid "Export" -msgstr "" - -#: lib/block_scout_web/templates/csv_export/index.html.eex:14 -#, elixir-autogen, elixir-format, fuzzy -msgid "for address" -msgstr "" - -#: lib/block_scout_web/templates/csv_export/index.html.eex:17 -#, elixir-autogen, elixir-format -msgid "to CSV file" -msgstr "" - -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:38 -#: lib/block_scout_web/views/verified_contracts_view.ex:12 -#, elixir-autogen, elixir-format -msgid "Yul" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:469 -#, elixir-autogen, elixir-format, fuzzy -msgid "burnt for this transaction. Equals Block Base Fee per Gas * Gas Used." -msgstr "" - -#: lib/block_scout_web/templates/block/overview.html.eex:215 -#, elixir-autogen, elixir-format, fuzzy -msgid "burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used)." -msgstr "" - #: lib/block_scout_web/templates/transaction/overview.html.eex:488 #, elixir-autogen, elixir-format, fuzzy msgid "Actual gas amount used by the transaction." diff --git a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs index cb8359f003f5..378336b35fc4 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs @@ -1218,6 +1218,10 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do "ERC-721" => %{ "incoming" => watchlist.watch_erc_721_input, "outcoming" => watchlist.watch_erc_721_output + }, + "ERC-404" => %{ + "incoming" => watchlist.watch_erc_404_input, + "outcoming" => watchlist.watch_erc_404_output } } diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_token_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_token_controller_test.exs index b96ede11e8db..475987d235a1 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_token_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_token_controller_test.exs @@ -161,6 +161,37 @@ defmodule BlockScoutWeb.AddressTokenControllerTest do assert 1 = length(response_2nd_page["items"]) end + test "returns next page of results based on last seen token for erc-404", %{conn: conn} do + address = insert(:address) + + 1..51 + |> Enum.reduce([], fn _i, acc -> + token = insert(:token, name: "FN2 Token", type: "ERC-404") + + insert( + :address_current_token_balance, + token_contract_address_hash: token.contract_address_hash, + address: address, + value: 3 + ) + + acc ++ [token.name] + end) + + conn = + get(conn, address_token_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash)), %{ + "type" => "JSON" + }) + + assert response = json_response(conn, 200) + + request_2nd_page = get(conn, response["next_page_path"], %{"type" => "JSON"}) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + assert 1 = length(response_2nd_page["items"]) + end + test "next_page_params exists if not on last page", %{conn: conn} do address = insert(:address) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index 3cb778e7e9d9..15fe4de50b7f 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -2565,6 +2565,42 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do check_paginated_response(response, response_2nd_page, token_instances) end + test "get paginated ERC-404 nft", %{conn: conn, endpoint: endpoint} do + address = insert(:address) + + insert_list(51, :address_current_token_balance_with_token_id) + + token_instances = + for _ <- 0..50 do + token = insert(:token, type: "ERC-404") + + ti = + insert(:token_instance, + token_contract_address_hash: token.contract_address_hash + ) + |> Repo.preload([:token]) + + current_token_balance = + insert(:address_current_token_balance_with_token_id_and_fixed_token_type, + address: address, + token_type: "ERC-404", + token_id: ti.token_id, + token_contract_address_hash: token.contract_address_hash + ) + + %Instance{ti | current_token_balance: current_token_balance} + end + |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id}, :desc) + + request = get(conn, endpoint.(address.hash)) + assert response = json_response(request, 200) + + request_2nd_page = get(conn, endpoint.(address.hash), response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, token_instances) + end + test "test filters", %{conn: conn, endpoint: endpoint} do address = insert(:address) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs index 18c30bfd19e6..9136d8053b78 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs @@ -532,6 +532,8 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do tokens_ordered_by_holders_asc ) + :timer.sleep(200) + # by circulating_market_cap tokens_ordered_by_circulating_market_cap = Enum.sort(tokens, &(&1.circulating_market_cap <= &2.circulating_market_cap)) @@ -634,9 +636,15 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do insert(:token, type: "ERC-1155") end + erc_404_tokens = + for _i <- 0..50 do + insert(:token, type: "ERC-404") + end + check_tokens_pagination(erc_20_tokens, conn, %{"type" => "ERC-20"}) check_tokens_pagination(erc_721_tokens |> Enum.reverse(), conn, %{"type" => "ERC-721"}) check_tokens_pagination(erc_1155_tokens |> Enum.reverse(), conn, %{"type" => "ERC-1155"}) + check_tokens_pagination(erc_404_tokens |> Enum.reverse(), conn, %{"type" => "ERC-404"}) end test "tokens are filtered by multiple type", %{conn: conn} do @@ -655,6 +663,11 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do insert(:token, type: "ERC-1155") end + erc_404_tokens = + for _i <- 0..24 do + insert(:token, type: "ERC-404") + end + check_tokens_pagination( erc_721_tokens |> Kernel.++(erc_1155_tokens) |> Enum.reverse(), conn, @@ -670,6 +683,14 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do "type" => "[erc-20,ERC-1155]" } ) + + check_tokens_pagination( + erc_404_tokens |> Enum.reverse() |> Kernel.++(erc_20_tokens), + conn, + %{ + "type" => "[erc-20,ERC-404]" + } + ) end test "sorting by fiat_value", %{conn: conn} do @@ -1001,7 +1022,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do instance = insert(:token_instance, token_id: 0, token_contract_address_hash: token.contract_address_hash) - transfer = + _transfer = insert(:token_transfer, token_contract_address: token.contract_address, transaction: transaction, @@ -1118,6 +1139,68 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do check_paginated_response(response, response_2nd_page, transfers_0 ++ transfers_1) end + test "check that pagination works for 404 tokens", %{conn: conn} do + token = insert(:token, type: "ERC-404") + + for _ <- 0..50 do + insert(:token_instance, token_id: 0) + end + + id = :rand.uniform(1_000_000) + + transaction = + :transaction + |> insert(input: "0xabcd010203040506") + |> with_block() + + insert(:token_instance, token_id: id, token_contract_address_hash: token.contract_address_hash) + + insert_list(100, :token_transfer, + token_contract_address: token.contract_address, + transaction: transaction, + token_ids: [id + 1], + token_type: "ERC-404", + amounts: [1] + ) + + transfers_0 = + insert_list(26, :token_transfer, + token_contract_address: token.contract_address, + transaction: transaction, + token_ids: [id, id + 1], + token_type: "ERC-404", + amounts: [1, 2] + ) + + transfers_1 = + for _ <- 26..50 do + transaction = + :transaction + |> insert(input: "0xabcd010203040506") + |> with_block() + + insert(:token_transfer, + token_contract_address: token.contract_address, + transaction: transaction, + token_ids: [id], + token_type: "ERC-404" + ) + end + + request = get(conn, "/api/v2/tokens/#{token.contract_address_hash}/instances/#{id}/transfers") + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/tokens/#{token.contract_address_hash}/instances/#{id}/transfers", + response["next_page_params"] + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + check_paginated_response(response, response_2nd_page, transfers_0 ++ transfers_1) + end + test "check that pagination works for 721 tokens", %{conn: conn} do token = insert(:token, type: "ERC-721") id = 0 diff --git a/apps/explorer/lib/explorer/account/notifier/email.ex b/apps/explorer/lib/explorer/account/notifier/email.ex index 713e5c3f1beb..6e8639e058e2 100644 --- a/apps/explorer/lib/explorer/account/notifier/email.ex +++ b/apps/explorer/lib/explorer/account/notifier/email.ex @@ -59,6 +59,9 @@ defmodule Explorer.Account.Notifier.Email do "ERC-1155" -> "Token ID: " <> subject <> " of " + + "ERC-404" -> + "Token ID: " <> subject <> " of " end end diff --git a/apps/explorer/lib/explorer/account/notifier/notify.ex b/apps/explorer/lib/explorer/account/notifier/notify.ex index 47adfc1b2c0c..3ad9e041c297 100644 --- a/apps/explorer/lib/explorer/account/notifier/notify.ex +++ b/apps/explorer/lib/explorer/account/notifier/notify.ex @@ -140,6 +140,7 @@ defmodule Explorer.Account.Notifier.Notify do end end + # credo:disable-for-next-line defp watched?(%WatchlistAddress{} = address, %{type: type}, direction) do case {type, direction} do {"COIN", :incoming} -> address.watch_coin_input @@ -150,6 +151,8 @@ defmodule Explorer.Account.Notifier.Notify do {"ERC-721", :outgoing} -> address.watch_erc_721_output {"ERC-1155", :incoming} -> address.watch_erc_1155_input {"ERC-1155", :outgoing} -> address.watch_erc_1155_output + {"ERC-404", :incoming} -> address.watch_erc_404_input + {"ERC-404", :outgoing} -> address.watch_erc_404_output end end diff --git a/apps/explorer/lib/explorer/account/notifier/summary.ex b/apps/explorer/lib/explorer/account/notifier/summary.ex index c38e40107c52..3364dfbb3744 100644 --- a/apps/explorer/lib/explorer/account/notifier/summary.ex +++ b/apps/explorer/lib/explorer/account/notifier/summary.ex @@ -151,6 +151,22 @@ defmodule Explorer.Account.Notifier.Summary do name: transfer.token.name, type: transfer.token.type } + + "ERC-404" -> + token_ids_string = token_ids(transfer) + + %Summary{ + amount: amount(transfer), + transaction_hash: transaction.hash, + method: method(transfer), + from_address_hash: transfer.from_address_hash, + to_address_hash: transfer.to_address_hash, + block_number: transfer.block_number, + subject: if(token_ids_string == "", do: transfer.token.type, else: token_ids_string), + tx_fee: fee(transaction), + name: transfer.token.name, + type: transfer.token.type + } end end diff --git a/apps/explorer/lib/explorer/account/watchlist_address.ex b/apps/explorer/lib/explorer/account/watchlist_address.ex index 86c0923704b4..7c5131609abe 100644 --- a/apps/explorer/lib/explorer/account/watchlist_address.ex +++ b/apps/explorer/lib/explorer/account/watchlist_address.ex @@ -30,6 +30,8 @@ defmodule Explorer.Account.WatchlistAddress do field(:watch_erc_721_output, :boolean, default: true, null: false) field(:watch_erc_1155_input, :boolean, default: true, null: false) field(:watch_erc_1155_output, :boolean, default: true, null: false) + field(:watch_erc_404_input, :boolean, default: true, null: false) + field(:watch_erc_404_output, :boolean, default: true, null: false) field(:notify_email, :boolean, default: true, null: false) field(:notify_epns, :boolean) field(:notify_feed, :boolean) @@ -43,7 +45,7 @@ defmodule Explorer.Account.WatchlistAddress do timestamps() end - @attrs ~w(name address_hash watch_coin_input watch_coin_output watch_erc_20_input watch_erc_20_output watch_erc_721_input watch_erc_721_output watch_erc_1155_input watch_erc_1155_output notify_email notify_epns notify_feed notify_inapp watchlist_id)a + @attrs ~w(name address_hash watch_coin_input watch_coin_output watch_erc_20_input watch_erc_20_output watch_erc_721_input watch_erc_721_output watch_erc_1155_input watch_erc_1155_output watch_erc_404_input watch_erc_404_output notify_email notify_epns notify_feed notify_inapp watchlist_id)a def changeset do %__MODULE__{} diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index fb9ee353dccd..4085da40f5e6 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -3818,13 +3818,13 @@ defmodule Explorer.Chain do |> select_repo(options).all() end - @spec erc721_or_erc1155_token_instance_from_token_id_and_token_address( + @spec nft_instance_from_token_id_and_token_address( Decimal.t() | non_neg_integer(), Hash.Address.t(), [api?] ) :: {:ok, Instance.t()} | {:error, :not_found} - def erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, token_contract_address, options \\ []) do + def nft_instance_from_token_id_and_token_address(token_id, token_contract_address, options \\ []) do query = Instance.token_instance_query(token_id, token_contract_address) case select_repo(options).one(query) do @@ -4129,9 +4129,10 @@ defmodule Explorer.Chain do def put_owner_to_token_instance( %Instance{owner: nil, is_unique: true} = token_instance, - %Token{type: "ERC-1155"}, + %Token{type: type}, options - ) do + ) + when type in ["ERC-1155", "ERC-404"] do owner_address_hash = token_instance |> Instance.owner_query() @@ -4146,7 +4147,7 @@ defmodule Explorer.Chain do def data, do: DataloaderEcto.new(Repo) @spec transaction_token_transfer_type(Transaction.t()) :: - :erc20 | :erc721 | :erc1155 | :token_transfer | nil + :erc20 | :erc721 | :erc1155 | :erc404 | :token_transfer | nil def transaction_token_transfer_type( %Transaction{ status: :ok, @@ -4207,10 +4208,7 @@ defmodule Explorer.Chain do find_erc1155_token_transfer(transaction.token_transfers, {from_address, to_address}) - {"0xf907fc5b" <> _params, ^zero_wei} -> - :erc20 - - # check for ERC-20 or for old ERC-721, ERC-1155 token versions + # check for ERC-20 or for old ERC-721, ERC-1155, ERC-404 token versions {unquote(TokenTransfer.transfer_function_signature()) <> params, ^zero_wei} -> types = [:address, {:uint, 256}] @@ -4218,7 +4216,7 @@ defmodule Explorer.Chain do decimal_value = Decimal.new(value) - find_erc721_or_erc20_or_erc1155_token_transfer(transaction.token_transfers, {address, decimal_value}) + find_known_token_transfer(transaction.token_transfers, {address, decimal_value}) _ -> nil @@ -4243,7 +4241,7 @@ defmodule Explorer.Chain do if token_transfer, do: :erc1155 end - defp find_erc721_or_erc20_or_erc1155_token_transfer(token_transfers, {address, decimal_value}) do + defp find_known_token_transfer(token_transfers, {address, decimal_value}) do token_transfer = Enum.find(token_transfers, fn token_transfer -> token_transfer.to_address_hash.bytes == address && token_transfer.amount == decimal_value @@ -4254,6 +4252,7 @@ defmodule Explorer.Chain do %Token{type: "ERC-20"} -> :erc20 %Token{type: "ERC-721"} -> :erc721 %Token{type: "ERC-1155"} -> :erc1155 + %Token{type: "ERC-404"} -> :erc404 _ -> nil end else @@ -4504,20 +4503,20 @@ defmodule Explorer.Chain do ...> token_contract_address_hash: token.contract_address_hash, ...> token_id: token_id ...> ) - iex> Explorer.Chain.check_erc721_or_erc1155_token_instance_exists(token_id, token.contract_address_hash) + iex> Explorer.Chain.check_nft_instance_exists(token_id, token.contract_address_hash) :ok Returns `:not_found` if not found iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") - iex> Explorer.Chain.check_erc721_or_erc1155_token_instance_exists(10, hash) + iex> Explorer.Chain.check_nft_instance_exists(10, hash) :not_found """ - @spec check_erc721_or_erc1155_token_instance_exists(binary() | non_neg_integer(), Hash.Address.t()) :: + @spec check_nft_instance_exists(binary() | non_neg_integer(), Hash.Address.t()) :: :ok | :not_found - def check_erc721_or_erc1155_token_instance_exists(token_id, hash) do + def check_nft_instance_exists(token_id, hash) do token_id - |> erc721_or_erc1155_token_instance_exist?(hash) + |> nft_instance_exist?(hash) |> boolean_to_check_result() end @@ -4532,17 +4531,17 @@ defmodule Explorer.Chain do ...> token_contract_address_hash: token.contract_address_hash, ...> token_id: token_id ...> ) - iex> Explorer.Chain.erc721_or_erc1155_token_instance_exist?(token_id, token.contract_address_hash) + iex> Explorer.Chain.nft_instance_exist?(token_id, token.contract_address_hash) true Returns `false` if not found iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") - iex> Explorer.Chain.erc721_or_erc1155_token_instance_exist?(10, hash) + iex> Explorer.Chain.nft_instance_exist?(10, hash) false """ - @spec erc721_or_erc1155_token_instance_exist?(binary() | non_neg_integer(), Hash.Address.t()) :: boolean() - def erc721_or_erc1155_token_instance_exist?(token_id, hash) do + @spec nft_instance_exist?(binary() | non_neg_integer(), Hash.Address.t()) :: boolean() + def nft_instance_exist?(token_id, hash) do query = from(i in Instance, where: i.token_contract_address_hash == ^hash and i.token_id == ^Decimal.new(token_id) diff --git a/apps/explorer/lib/explorer/chain/address/token_balance.ex b/apps/explorer/lib/explorer/chain/address/token_balance.ex index 99eac8779f29..e50462b1e96c 100644 --- a/apps/explorer/lib/explorer/chain/address/token_balance.ex +++ b/apps/explorer/lib/explorer/chain/address/token_balance.ex @@ -23,7 +23,7 @@ defmodule Explorer.Chain.Address.TokenBalance do * `token_contract_address_hash` - The contract address hash foreign key. * `block_number` - The block's number that the transfer took place. * `value` - The value that's represents the balance. - * `token_id` - The token_id of the transferred token (applicable for ERC-1155 and ERC-721 tokens) + * `token_id` - The token_id of the transferred token (applicable for ERC-1155, ERC-721 and ERC-404 tokens) * `token_type` - The type of the token """ typed_schema "address_token_balances" do @@ -76,7 +76,7 @@ defmodule Explorer.Chain.Address.TokenBalance do 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 + tb.token_type == "ERC-1155" or tb.token_type == "ERC-404") and (is_nil(tb.value_fetched_at) or is_nil(tb.value)) ) else @@ -86,7 +86,7 @@ defmodule Explorer.Chain.Address.TokenBalance do 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 + t.type == "ERC-1155" or t.type == "ERC-404") and (is_nil(tb.value_fetched_at) or is_nil(tb.value)) ) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex b/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex index cc51fb87376c..b7420aec8b24 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex @@ -219,7 +219,8 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do ordered_changes_list = changes_list |> Enum.map(fn change -> - if Map.has_key?(change, :token_id) and Map.get(change, :token_type) == "ERC-1155" do + if Map.has_key?(change, :token_id) and + (Map.get(change, :token_type) == "ERC-1155" || Map.get(change, :token_type) == "ERC-404") do change else Map.put(change, :token_id, nil) diff --git a/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex b/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex index 618c8a920de2..16989c2822f6 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex @@ -69,10 +69,11 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalances do ordered_changes_list = changes_list |> Enum.map(fn change -> - if Map.has_key?(change, :token_id) and Map.get(change, :token_type) == "ERC-1155" do - change - else - Map.put(change, :token_id, nil) + cond do + Map.has_key?(change, :token_id) and Map.get(change, :token_type) == "ERC-1155" -> change + Map.get(change, :token_type) == "ERC-404" and Map.has_key?(change, :token_id) -> Map.put(change, :value, nil) + Map.get(change, :token_type) == "ERC-404" and Map.has_key?(change, :value) -> Map.put(change, :token_id, nil) + true -> Map.put(change, :token_id, nil) end end) |> Enum.group_by(fn %{ diff --git a/apps/explorer/lib/explorer/chain/shibarium/bridge.ex b/apps/explorer/lib/explorer/chain/shibarium/bridge.ex index ee03ebcd2503..374bf2ae8d37 100644 --- a/apps/explorer/lib/explorer/chain/shibarium/bridge.ex +++ b/apps/explorer/lib/explorer/chain/shibarium/bridge.ex @@ -19,7 +19,7 @@ defmodule Explorer.Chain.Shibarium.Bridge do @typedoc """ * `user_address` - address of the user that initiated operation * `user` - foreign key of `user_address` - * `amount_or_id` - amount of the operation or NTF id (in case of ERC-721 token) + * `amount_or_id` - amount of the operation or NFT id (in case of ERC-721 token) * `erc1155_ids` - an array of ERC-1155 token ids (when batch ERC-1155 token transfer) * `erc1155_amounts` - an array of corresponding ERC-1155 token amounts (when batch ERC-1155 token transfer) * `l1_transaction_hash` - transaction hash for L1 side diff --git a/apps/explorer/lib/explorer/chain/token.ex b/apps/explorer/lib/explorer/chain/token.ex index 6578a1bdcae4..13bcb12d3ccf 100644 --- a/apps/explorer/lib/explorer/chain/token.ex +++ b/apps/explorer/lib/explorer/chain/token.ex @@ -61,6 +61,7 @@ defmodule Explorer.Chain.Token do * ERC-20 * ERC-721 * ERC-1155 + * ERC-404 ## Token Specifications @@ -68,6 +69,7 @@ defmodule Explorer.Chain.Token do * [ERC-721](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md) * [ERC-777](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-777.md) * [ERC-1155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md) + * [ERC-404](https://github.com/Pandora-Labs-Org/erc404) """ use Explorer.Schema diff --git a/apps/explorer/lib/explorer/chain/token/instance.ex b/apps/explorer/lib/explorer/chain/token/instance.ex index d1ce8a181558..3807d3b88bf7 100644 --- a/apps/explorer/lib/explorer/chain/token/instance.ex +++ b/apps/explorer/lib/explorer/chain/token/instance.ex @@ -1,6 +1,6 @@ defmodule Explorer.Chain.Token.Instance do @moduledoc """ - Represents an ERC-721/ERC-1155 token instance and stores metadata defined in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md. + Represents an ERC-721/ERC-1155/ERC-404 token instance and stores metadata defined in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md. """ use Explorer.Schema @@ -113,6 +113,10 @@ defmodule Explorer.Chain.Token.Instance do erc_1155_token_instances_by_address_hash(address_hash, options) end + defp nft_list(address_hash, ["ERC-404"], options) do + erc_404_token_instances_by_address_hash(address_hash, options) + end + defp nft_list(address_hash, _, options) do paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) @@ -120,6 +124,9 @@ defmodule Explorer.Chain.Token.Instance do %PagingOptions{key: {_contract_address_hash, _token_id, "ERC-1155"}} -> erc_1155_token_instances_by_address_hash(address_hash, options) + %PagingOptions{key: {_contract_address_hash, _token_id, "ERC-404"}} -> + erc_404_token_instances_by_address_hash(address_hash, options) + _ -> erc_721 = erc_721_token_instances_by_owner_address_hash(address_hash, options) @@ -127,8 +134,9 @@ defmodule Explorer.Chain.Token.Instance do erc_721 else erc_1155 = erc_1155_token_instances_by_address_hash(address_hash, options) + erc_404 = erc_404_token_instances_by_address_hash(address_hash, options) - (erc_721 ++ erc_1155) |> Enum.take(paging_options.page_size) + (erc_721 ++ erc_1155 ++ erc_404) |> Enum.take(paging_options.page_size) end end end @@ -183,6 +191,33 @@ defmodule Explorer.Chain.Token.Instance do defp page_erc_1155_token_instances(query, _), do: query + @spec erc_404_token_instances_by_address_hash(binary() | Hash.Address.t(), keyword) :: [Instance.t()] + def erc_404_token_instances_by_address_hash(address_hash, options \\ []) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + + __MODULE__ + |> join(:inner, [ti], ctb in CurrentTokenBalance, + as: :ctb, + on: + ctb.token_contract_address_hash == ti.token_contract_address_hash and ctb.token_id == ti.token_id and + ctb.address_hash == ^address_hash + ) + |> where([ctb: ctb], ctb.value > 0 and ctb.token_type == "ERC-404") + |> order_by([ti], asc: ti.token_contract_address_hash, desc: ti.token_id) + |> limit(^paging_options.page_size) + |> page_erc_404_token_instances(paging_options) + |> select_merge([ctb: ctb], %{current_token_balance: ctb}) + |> Chain.join_associations(necessity_by_association) + |> Chain.select_repo(options).all() + end + + defp page_erc_404_token_instances(query, %PagingOptions{key: {contract_address_hash, token_id, "ERC-404"}}) do + page_token_instance(query, contract_address_hash, token_id) + end + + defp page_erc_404_token_instances(query, _), do: query + defp page_token_instance(query, contract_address_hash, token_id) do query |> where( @@ -199,9 +234,10 @@ defmodule Explorer.Chain.Token.Instance do def nft_list_next_page_params(%__MODULE__{ current_token_balance: %CurrentTokenBalance{}, token_contract_address_hash: token_contract_address_hash, - token_id: token_id + token_id: token_id, + token: token }) do - %{"token_contract_address_hash" => token_contract_address_hash, "token_id" => token_id, "token_type" => "ERC-1155"} + %{"token_contract_address_hash" => token_contract_address_hash, "token_id" => token_id, "token_type" => token.type} end def nft_list_next_page_params(%__MODULE__{ @@ -228,6 +264,10 @@ defmodule Explorer.Chain.Token.Instance do erc_1155_collections_by_address_hash(address_hash, options) end + defp nft_collections(address_hash, ["ERC-404"], options) do + erc_404_collections_by_address_hash(address_hash, options) + end + defp nft_collections(address_hash, _, options) do paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) @@ -242,8 +282,9 @@ defmodule Explorer.Chain.Token.Instance do erc_721 else erc_1155 = erc_1155_collections_by_address_hash(address_hash, options) + erc_404 = erc_404_collections_by_address_hash(address_hash, options) - (erc_721 ++ erc_1155) |> Enum.take(paging_options.page_size) + (erc_721 ++ erc_1155 ++ erc_404) |> Enum.take(paging_options.page_size) end end end @@ -301,6 +342,38 @@ defmodule Explorer.Chain.Token.Instance do defp page_erc_1155_nft_collections(query, _), do: query + @spec erc_404_collections_by_address_hash(binary() | Hash.Address.t(), keyword) :: [ + %{ + token_contract_address_hash: Hash.Address.t(), + distinct_token_instances_count: integer(), + token_ids: [integer()] + } + ] + def erc_404_collections_by_address_hash(address_hash, options) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + + CurrentTokenBalance + |> where([ctb], ctb.address_hash == ^address_hash and ctb.value > 0 and ctb.token_type == "ERC-404") + |> group_by([ctb], ctb.token_contract_address_hash) + |> order_by([ctb], asc: ctb.token_contract_address_hash) + |> select([ctb], %{ + token_contract_address_hash: ctb.token_contract_address_hash, + distinct_token_instances_count: fragment("COUNT(*)"), + token_ids: fragment("array_agg(?)", ctb.token_id) + }) + |> page_erc_404_nft_collections(paging_options) + |> limit(^paging_options.page_size) + |> Chain.select_repo(options).all() + |> Enum.map(&erc_1155_preload_nft(&1, address_hash, options)) + |> Helper.custom_preload(options, Token, :token_contract_address_hash, :contract_address_hash, :token) + end + + defp page_erc_404_nft_collections(query, %PagingOptions{key: {contract_address_hash, "ERC-404"}}) do + page_nft_collections(query, contract_address_hash) + end + + defp page_erc_404_nft_collections(query, _), do: query + defp page_nft_collections(query, token_contract_address_hash) do query |> where([ctb], ctb.token_contract_address_hash > ^token_contract_address_hash) diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index a185e43b49a5..4f1e4495decc 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -41,6 +41,8 @@ defmodule Explorer.Chain.TokenTransfer do @weth_withdrawal_signature "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" @erc1155_single_transfer_signature "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62" @erc1155_batch_transfer_signature "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb" + @erc404_erc20_transfer_event "0xe59fdd36d0d223c0c7d996db7ad796880f45e1936cb0bb7ac102e7082e031487" + @erc404_erc721_transfer_event "0xe5f815dc84b8cecdfd4beedfc3f91ab5be7af100eca4e8fb11552b867995394f" @transfer_function_signature "0xa9059cbb" @@ -143,6 +145,10 @@ defmodule Explorer.Chain.TokenTransfer do def erc1155_batch_transfer_signature, do: @erc1155_batch_transfer_signature + def erc404_erc20_transfer_event, do: @erc404_erc20_transfer_event + + def erc404_erc721_transfer_event, do: @erc404_erc721_transfer_event + @doc """ ERC 20's transfer(address,uint256) function signature """ diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index 29ed0425a92f..2395fd95aa9f 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -293,14 +293,14 @@ defmodule Explorer.Etherscan do @doc """ Gets a list of ERC-721 token transfers for a given address_hash. If contract_address_hash is not nil, transfers will be filtered by contract. """ - @spec list_nft_token_transfers(Hash.Address.t(), Hash.Address.t() | nil, map()) :: [TokenTransfer.t()] - def list_nft_token_transfers( + @spec list_nft_transfers(Hash.Address.t(), Hash.Address.t() | nil, map()) :: [TokenTransfer.t()] + def list_nft_transfers( %Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash, contract_address_hash, options \\ @default_options ) do options - |> base_nft_token_transfers_query(contract_address_hash) + |> base_nft_transfers_query(contract_address_hash) |> where([tt], tt.from_address_hash == ^address_hash or tt.to_address_hash == ^address_hash) |> Repo.replica().all() end @@ -308,17 +308,17 @@ defmodule Explorer.Etherscan do @doc """ Gets a list of ERC-721 token transfers for a given token contract_address_hash. """ - @spec list_nft_token_transfers_by_token(Hash.Address.t(), map()) :: [TokenTransfer.t()] - def list_nft_token_transfers_by_token( + @spec list_nft_transfers_by_token(Hash.Address.t(), map()) :: [TokenTransfer.t()] + def list_nft_transfers_by_token( %Hash{byte_count: unquote(Hash.Address.byte_count())} = contract_address_hash, options \\ @default_options ) do options - |> base_nft_token_transfers_query(contract_address_hash) + |> base_nft_transfers_query(contract_address_hash) |> Repo.replica().all() end - defp base_nft_token_transfers_query(options, contract_address_hash) do + defp base_nft_transfers_query(options, contract_address_hash) do options = Map.merge(@default_options, options) TokenTransfer.erc_721_token_transfers_query() diff --git a/apps/explorer/lib/explorer/token/balance_reader.ex b/apps/explorer/lib/explorer/token/balance_reader.ex index c8577c457839..2617cfb02992 100644 --- a/apps/explorer/lib/explorer/token/balance_reader.ex +++ b/apps/explorer/lib/explorer/token/balance_reader.ex @@ -27,7 +27,7 @@ defmodule Explorer.Token.BalanceReader do } ] - @erc1155_balance_function_abi [ + @nft_balance_function_abi [ %{ "constant" => true, "inputs" => [%{"name" => "_owner", "type" => "address"}, %{"name" => "_id", "type" => "uint256"}], @@ -67,7 +67,7 @@ defmodule Explorer.Token.BalanceReader do ) :: [{:ok, non_neg_integer()} | {:error, String.t()}] def get_balances_of_with_abi(token_balance_requests, abi) do formatted_balances_requests = - if abi == @erc1155_balance_function_abi do + if abi == @nft_balance_function_abi do token_balance_requests |> Enum.map(&format_erc_1155_balance_request/1) else @@ -93,7 +93,7 @@ defmodule Explorer.Token.BalanceReader do } ]) :: [{:ok, non_neg_integer()} | {:error, String.t()}] def get_balances_of_erc_1155(token_balance_requests) do - get_balances_of_with_abi(token_balance_requests, @erc1155_balance_function_abi) + get_balances_of_with_abi(token_balance_requests, @nft_balance_function_abi) end defp format_balance_request(%{ diff --git a/apps/explorer/priv/account/migrations/20240219152220_add_account_watchlist_addresses_erc_404_fields.exs b/apps/explorer/priv/account/migrations/20240219152220_add_account_watchlist_addresses_erc_404_fields.exs new file mode 100644 index 000000000000..7fbb08a48cd4 --- /dev/null +++ b/apps/explorer/priv/account/migrations/20240219152220_add_account_watchlist_addresses_erc_404_fields.exs @@ -0,0 +1,10 @@ +defmodule Explorer.Repo.Account.Migrations.AddAccountWatchlistAddressesErc404Fields do + use Ecto.Migration + + def change do + alter table(:account_watchlist_addresses) do + add(:watch_erc_404_input, :boolean, default: true) + add(:watch_erc_404_output, :boolean, default: true) + end + end +end diff --git a/apps/explorer/test/explorer/account/notifier/summary_test.exs b/apps/explorer/test/explorer/account/notifier/summary_test.exs index 54fc0da10925..37d18f95dc34 100644 --- a/apps/explorer/test/explorer/account/notifier/summary_test.exs +++ b/apps/explorer/test/explorer/account/notifier/summary_test.exs @@ -4,7 +4,6 @@ defmodule Explorer.Account.Notifier.SummaryTest do import Explorer.Factory alias Explorer.Account.Notifier.Summary - alias Explorer.Chain alias Explorer.Chain.{TokenTransfer, Transaction, Wei} alias Explorer.Repo @@ -268,5 +267,112 @@ defmodule Explorer.Account.Notifier.SummaryTest do } ] end + + test "ERC-404 Token transfer with token id" do + token = insert(:token, type: "ERC-404") + + tx = + %Transaction{ + from_address: _from_address, + to_address: _to_address, + block_number: _block_number, + hash: _tx_hash + } = with_block(insert(:transaction)) + + transfer = + %TokenTransfer{ + amount: _amount, + block_number: block_number, + from_address: from_address, + to_address: to_address + } = + :token_transfer + |> insert( + transaction: tx, + token_ids: [42], + token_contract_address: token.contract_address + ) + |> Repo.preload([ + :token + ]) + + {_, fee} = Transaction.fee(tx, :gwei) + + token_decimals = Decimal.to_integer(token.decimals) + + decimals = Decimal.new(Integer.pow(10, token_decimals)) + + amount = Decimal.div(transfer.amount, decimals) + + assert Summary.process(transfer) == [ + %Summary{ + amount: amount, + block_number: block_number, + from_address_hash: from_address.hash, + method: "transfer", + name: "Infinite Token", + subject: "42", + to_address_hash: to_address.hash, + transaction_hash: tx.hash, + tx_fee: fee, + type: "ERC-404" + } + ] + end + + test "ERC-404 Token transfer without token id" do + token = insert(:token, type: "ERC-404") + + tx = + %Transaction{ + from_address: _from_address, + to_address: _to_address, + block_number: _block_number, + hash: _tx_hash + } = with_block(insert(:transaction)) + + transfer = + %TokenTransfer{ + amount: _amount, + block_number: block_number, + from_address: from_address, + to_address: to_address + } = + :token_transfer + |> insert( + transaction: tx, + token_ids: [], + token_contract_address: token.contract_address + ) + |> Repo.preload([ + :token + ]) + + {_, fee} = Transaction.fee(tx, :gwei) + + token_decimals = Decimal.to_integer(token.decimals) + + decimals = Decimal.new(Integer.pow(10, token_decimals)) + + amount = Decimal.div(transfer.amount, decimals) + + IO.inspect("Gimme") + IO.inspect(Summary.process(transfer)) + + assert Summary.process(transfer) == [ + %Summary{ + amount: amount, + block_number: block_number, + from_address_hash: from_address.hash, + method: "transfer", + name: "Infinite Token", + subject: "ERC-404", + to_address_hash: to_address.hash, + transaction_hash: tx.hash, + tx_fee: fee, + type: "ERC-404" + } + ] + end end end diff --git a/apps/explorer/test/explorer/chain/import/runner/address/current_token_balances_test.exs b/apps/explorer/test/explorer/chain/import/runner/address/current_token_balances_test.exs index fa8d97e0a5c2..6789d37b5a8e 100644 --- a/apps/explorer/test/explorer/chain/import/runner/address/current_token_balances_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/address/current_token_balances_test.exs @@ -89,6 +89,13 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalancesTest do value_5 = Decimal.new(2) token_id_5 = Decimal.new(555) + token_erc_404 = insert(:token, holder_count: 0) + token_erc_404_contract_address_hash = token_erc_404.contract_address_hash + value_6 = Decimal.new(10) + token_id_6 = Decimal.new(333) + + value_7 = Decimal.new(25) + block_number = 1 assert {:ok, @@ -121,6 +128,20 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalancesTest do token_contract_address_hash: ^token_erc_721_contract_address_hash, value: ^value_5, token_id: nil + }, + %Explorer.Chain.Address.CurrentTokenBalance{ + address_hash: ^address_hash, + block_number: ^block_number, + token_contract_address_hash: ^token_erc_404_contract_address_hash, + value: ^value_7, + token_id: nil + }, + %Explorer.Chain.Address.CurrentTokenBalance{ + address_hash: ^address_hash, + block_number: ^block_number, + token_contract_address_hash: ^token_erc_404_contract_address_hash, + value: ^value_6, + token_id: ^token_id_6 } ], address_current_token_balances_update_token_holder_counts: [ @@ -135,6 +156,10 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalancesTest do %{ contract_address_hash: ^token_erc_721_contract_address_hash, holder_count: 1 + }, + %{ + contract_address_hash: ^token_erc_404_contract_address_hash, + holder_count: 2 } ] }} = @@ -184,6 +209,24 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalancesTest do value_fetched_at: DateTime.utc_now(), token_id: token_id_5, token_type: "ERC-721" + }, + %{ + address_hash: address_hash, + block_number: block_number, + token_contract_address_hash: token_erc_404_contract_address_hash, + value: value_6, + value_fetched_at: DateTime.utc_now(), + token_id: token_id_6, + token_type: "ERC-404" + }, + %{ + address_hash: address_hash, + block_number: block_number, + token_contract_address_hash: token_erc_404_contract_address_hash, + value: value_7, + value_fetched_at: DateTime.utc_now(), + token_id: nil, + token_type: "ERC-404" } ], options @@ -197,7 +240,7 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalancesTest do current_token_balances |> Enum.count() - assert current_token_balances_count == 4 + assert current_token_balances_count == 6 end test "updates when the new block number is greater", %{ diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 59e3adbd7e4a..36d4647adb7d 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -148,8 +148,8 @@ defmodule Explorer.ChainTest do end end - describe "ERC721_or_ERC1155_token_instance_from_token_id_and_token_address/2" do - test "return ERC721 token instance" do + describe "nft_instance_from_token_id_and_token_address/2" do + test "return NFT instance" do token = insert(:token) token_id = 10 @@ -160,7 +160,7 @@ defmodule Explorer.ChainTest do ) assert {:ok, result} = - Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address( + Chain.nft_instance_from_token_id_and_token_address( token_id, token.contract_address_hash ) diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 0c9a98e43a47..9953bf1a59ba 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -106,6 +106,10 @@ defmodule Explorer.Factory do "ERC-721" => %{ "incoming" => random_bool(), "outcoming" => random_bool() + }, + "ERC-404" => %{ + "incoming" => random_bool(), + "outcoming" => random_bool() } }, "notification_methods" => %{ @@ -130,6 +134,8 @@ defmodule Explorer.Factory do watch_erc_721_output: random_bool(), watch_erc_1155_input: random_bool(), watch_erc_1155_output: random_bool(), + watch_erc_404_input: random_bool(), + watch_erc_404_output: random_bool(), notify_email: random_bool() } end @@ -205,6 +211,8 @@ defmodule Explorer.Factory do watch_erc_721_output: random_bool(), watch_erc_1155_input: random_bool(), watch_erc_1155_output: random_bool(), + watch_erc_404_input: random_bool(), + watch_erc_404_output: random_bool(), notify_email: random_bool() } end @@ -951,7 +959,14 @@ defmodule Explorer.Factory do end def address_current_token_balance_with_token_id_factory do - {token_type, token_id} = Enum.random([{"ERC-20", nil}, {"ERC-721", nil}, {"ERC-1155", Enum.random(1..100_000)}]) + {token_type, token_id} = + Enum.random([ + {"ERC-20", nil}, + {"ERC-721", nil}, + {"ERC-1155", Enum.random(1..100_000)}, + {"ERC-404", nil}, + {"ERC-404", Enum.random(1..100_000)} + ]) %CurrentTokenBalance{ address: build(:address), diff --git a/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex b/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex index 7020cfd9d74d..5ea891727eda 100644 --- a/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex +++ b/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex @@ -209,8 +209,18 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do balance_response = case token_type do - "ERC-1155" -> BalanceReader.get_balances_of_erc_1155([request]) - _ -> BalanceReader.get_balances_of([request]) + "ERC-404" -> + if token_id do + BalanceReader.get_balances_of_erc_1155([request]) + else + BalanceReader.get_balances_of([request]) + end + + "ERC-1155" -> + BalanceReader.get_balances_of_erc_1155([request]) + + _ -> + BalanceReader.get_balances_of([request]) end balance = balance_response[:ok] diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex b/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex index 1a3a2ed48e2a..2f4c5eedb7d2 100644 --- a/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex +++ b/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex @@ -1,6 +1,6 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do @moduledoc """ - Fetches ERC-721 & ERC-1155 token instance metadata. + Fetches ERC-721/ERC-1155/ERC-404 token instance metadata. """ require Logger diff --git a/apps/indexer/lib/indexer/token_balances.ex b/apps/indexer/lib/indexer/token_balances.ex index e53347f90ba9..066cddbac24b 100644 --- a/apps/indexer/lib/indexer/token_balances.ex +++ b/apps/indexer/lib/indexer/token_balances.ex @@ -13,7 +13,7 @@ defmodule Indexer.TokenBalances do alias Indexer.Fetcher.TokenBalance alias Indexer.Tracer - @erc1155_balance_function_abi [ + @nft_balance_function_abi [ %{ "constant" => true, "inputs" => [%{"name" => "_owner", "type" => "address"}, %{"name" => "_id", "type" => "uint256"}], @@ -39,7 +39,7 @@ defmodule Indexer.TokenBalances do * `address_hash` - The address_hash that we want to know the balance. * `block_number` - The block number that the address_hash has the balance. * `token_type` - type of the token that balance belongs to - * `token_id` - token id for ERC-1155 tokens + * `token_id` - token id for ERC-1155/ERC-404 tokens """ def fetch_token_balances_from_blockchain([]), do: {:ok, []} @@ -47,39 +47,39 @@ defmodule Indexer.TokenBalances do def fetch_token_balances_from_blockchain(token_balances) do Logger.debug("fetching token balances", count: Enum.count(token_balances)) - regular_token_balances = + ft_token_balances = token_balances - |> Enum.filter(fn request -> - if Map.has_key?(request, :token_type) do - request.token_type !== "ERC-1155" + |> Enum.filter(fn token_balance -> + if Map.has_key?(token_balance, :token_type) do + token_balance.token_type !== "ERC-1155" && !(token_balance.token_type == "ERC-404" && token_balance.token_id) else true end end) - erc1155_token_balances = + nft_token_balances = token_balances - |> Enum.filter(fn request -> - if Map.has_key?(request, :token_type) do - request.token_type == "ERC-1155" + |> Enum.filter(fn token_balance -> + if Map.has_key?(token_balance, :token_type) do + token_balance.token_type == "ERC-1155" || (token_balance.token_type == "ERC-404" && token_balance.token_id) else false end end) - requested_regular_token_balances = - regular_token_balances + requested_ft_token_balances = + ft_token_balances |> BalanceReader.get_balances_of() - |> Stream.zip(regular_token_balances) + |> Stream.zip(ft_token_balances) |> Enum.map(fn {result, token_balance} -> set_token_balance_value(result, token_balance) end) - requested_erc1155_token_balances = - erc1155_token_balances - |> BalanceReader.get_balances_of_with_abi(@erc1155_balance_function_abi) - |> Stream.zip(erc1155_token_balances) + requested_nft_token_balances = + nft_token_balances + |> BalanceReader.get_balances_of_with_abi(@nft_balance_function_abi) + |> Stream.zip(nft_token_balances) |> Enum.map(fn {result, token_balance} -> set_token_balance_value(result, token_balance) end) - requested_token_balances = requested_regular_token_balances ++ requested_erc1155_token_balances + requested_token_balances = requested_ft_token_balances ++ requested_nft_token_balances fetched_token_balances = Enum.filter(requested_token_balances, &ignore_request_with_errors/1) requested_token_balances diff --git a/apps/indexer/lib/indexer/transform/address_token_balances.ex b/apps/indexer/lib/indexer/transform/address_token_balances.ex index 291783f6a2ba..af0c37caa7ef 100644 --- a/apps/indexer/lib/indexer/transform/address_token_balances.ex +++ b/apps/indexer/lib/indexer/transform/address_token_balances.ex @@ -22,7 +22,10 @@ defmodule Indexer.Transform.AddressTokenBalances do acc when is_integer(block_number) and is_binary(from_address_hash) and is_binary(to_address_hash) and is_binary(token_contract_address_hash) -> - Enum.reduce(token_ids || [nil], acc, fn id, sub_acc -> + sanitized_token_ids = + if is_nil(token_ids) || (is_list(token_ids) && Enum.empty?(token_ids)), do: [nil], else: token_ids + + Enum.reduce(sanitized_token_ids, acc, fn id, sub_acc -> sub_acc |> add_token_balance_address(from_address_hash, token_contract_address_hash, id, token_type, block_number) |> add_token_balance_address(to_address_hash, token_contract_address_hash, id, token_type, block_number) diff --git a/apps/indexer/lib/indexer/transform/token_instances.ex b/apps/indexer/lib/indexer/transform/token_instances.ex index e6f46e1c0d02..1b9374318b7d 100644 --- a/apps/indexer/lib/indexer/transform/token_instances.ex +++ b/apps/indexer/lib/indexer/transform/token_instances.ex @@ -66,6 +66,23 @@ defmodule Indexer.Transform.TokenInstances do ) end + defp transfer_to_instances( + %{ + token_type: "ERC-404" = token_type, + token_ids: [_ | _] = token_ids, + token_contract_address_hash: token_contract_address_hash + }, + acc + ) do + Enum.reduce(token_ids, acc, fn id, sub_acc -> + Map.put(sub_acc, {token_contract_address_hash, id}, %{ + token_contract_address_hash: token_contract_address_hash, + token_id: id, + token_type: token_type + }) + end) + end + defp transfer_to_instances( %{ token_type: _token_type, diff --git a/apps/indexer/lib/indexer/transform/token_transfers.ex b/apps/indexer/lib/indexer/transform/token_transfers.ex index 8d7f7f66ba3b..2ffd90abd1f5 100644 --- a/apps/indexer/lib/indexer/transform/token_transfers.ex +++ b/apps/indexer/lib/indexer/transform/token_transfers.ex @@ -1,6 +1,6 @@ defmodule Indexer.Transform.TokenTransfers do @moduledoc """ - Helper functions for transforming data for ERC-20 and ERC-721 token transfers. + Helper functions for transforming data for known token standards (ERC-20, ERC-721, ERC-1155, ERC-404) transfers. """ require Logger @@ -8,7 +8,7 @@ defmodule Indexer.Transform.TokenTransfers do import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] alias Explorer.{Helper, Repo} - alias Explorer.Chain.{Token, TokenTransfer} + alias Explorer.Chain.{Hash, Token, TokenTransfer} alias Indexer.Fetcher.TokenTotalSupplyUpdater @doc """ @@ -38,12 +38,22 @@ defmodule Indexer.Transform.TokenTransfers do end) |> Enum.reduce(initial_acc, &do_parse(&1, &2, :erc1155)) + erc404_token_transfers = + logs + |> Enum.filter(fn log -> + log.first_topic == TokenTransfer.erc404_erc20_transfer_event() || + log.first_topic == TokenTransfer.erc404_erc721_transfer_event() + end) + |> Enum.reduce(initial_acc, &do_parse(&1, &2, :erc404)) + rough_tokens = - erc1155_token_transfers.tokens ++ + erc404_token_transfers.tokens ++ + erc1155_token_transfers.tokens ++ erc20_and_erc721_token_transfers.tokens ++ weth_transfers.tokens rough_token_transfers = - erc1155_token_transfers.token_transfers ++ + erc404_token_transfers.token_transfers ++ + erc1155_token_transfers.token_transfers ++ erc20_and_erc721_token_transfers.token_transfers ++ weth_transfers.token_transfers tokens = sanitize_token_types(rough_tokens, rough_token_transfers) @@ -141,17 +151,17 @@ defmodule Indexer.Transform.TokenTransfers do defp token_type_priority(nil), do: -1 - @token_types_priority_order ["ERC-20", "ERC-721", "ERC-1155"] + @token_types_priority_order ["ERC-20", "ERC-721", "ERC-1155", "ERC-404"] defp token_type_priority(token_type) do Enum.find_index(@token_types_priority_order, &(&1 == token_type)) end defp do_parse(log, %{tokens: tokens, token_transfers: token_transfers} = acc, type \\ :erc20_erc721) do parse_result = - if type != :erc1155 do - parse_params(log) - else - parse_erc1155_params(log) + case type do + :erc1155 -> parse_erc1155_params(log) + :erc404 -> parse_erc404_params(log) + _ -> parse_params(log) end case parse_result do @@ -295,14 +305,20 @@ defmodule Indexer.Transform.TokenTransfers do {token, token_transfer} end - def parse_erc1155_params( - %{ - first_topic: unquote(TokenTransfer.erc1155_batch_transfer_signature()), - third_topic: third_topic, - fourth_topic: fourth_topic, - data: data - } = log - ) do + @spec parse_erc1155_params(map()) :: + nil + | {%{ + contract_address_hash: Hash.Address.t(), + type: String.t() + }, map()} + defp parse_erc1155_params( + %{ + first_topic: unquote(TokenTransfer.erc1155_batch_transfer_signature()), + third_topic: third_topic, + fourth_topic: fourth_topic, + data: data + } = log + ) do [token_ids, values] = Helper.decode_data(data, [{:array, {:uint, 256}}, {:array, {:uint, 256}}]) if is_nil(token_ids) or token_ids == [] or is_nil(values) or values == [] do @@ -333,7 +349,7 @@ defmodule Indexer.Transform.TokenTransfers do end end - def parse_erc1155_params(%{third_topic: third_topic, fourth_topic: fourth_topic, data: data} = log) do + defp parse_erc1155_params(%{third_topic: third_topic, fourth_topic: fourth_topic, data: data} = log) do [token_id, value] = Helper.decode_data(data, [{:uint, 256}, {:uint, 256}]) from_address_hash = truncate_address_hash(third_topic) @@ -360,6 +376,84 @@ defmodule Indexer.Transform.TokenTransfers do {token, token_transfer} end + @spec parse_erc404_params(map()) :: + nil + | {%{ + contract_address_hash: Hash.Address.t(), + type: String.t() + }, map()} + defp parse_erc404_params( + %{ + first_topic: unquote(TokenTransfer.erc404_erc20_transfer_event()), + second_topic: second_topic, + third_topic: third_topic, + fourth_topic: nil, + data: data + } = log + ) do + [value] = Helper.decode_data(data, [{:uint, 256}]) + + if is_nil(value) or value == [] do + nil + else + token_transfer = %{ + block_number: log.block_number, + block_hash: log.block_hash, + log_index: log.index, + from_address_hash: truncate_address_hash(second_topic), + to_address_hash: truncate_address_hash(third_topic), + token_contract_address_hash: log.address_hash, + transaction_hash: log.transaction_hash, + token_type: "ERC-404", + token_ids: [], + amounts: [value] + } + + token = %{ + contract_address_hash: log.address_hash, + type: "ERC-404" + } + + {token, token_transfer} + end + end + + defp parse_erc404_params( + %{ + first_topic: unquote(TokenTransfer.erc404_erc721_transfer_event()), + second_topic: second_topic, + third_topic: third_topic, + fourth_topic: fourth_topic, + data: _data + } = log + ) do + [token_id] = Helper.decode_data(fourth_topic, [{:uint, 256}]) + + if is_nil(token_id) or token_id == [] do + nil + else + token_transfer = %{ + block_number: log.block_number, + block_hash: log.block_hash, + log_index: log.index, + from_address_hash: truncate_address_hash(second_topic), + to_address_hash: truncate_address_hash(third_topic), + token_contract_address_hash: log.address_hash, + transaction_hash: log.transaction_hash, + token_type: "ERC-404", + token_ids: [token_id], + amounts: [] + } + + token = %{ + contract_address_hash: log.address_hash, + type: "ERC-404" + } + + {token, token_transfer} + end + end + defp truncate_address_hash(nil), do: burn_address_hash_string() defp truncate_address_hash("0x000000000000000000000000" <> truncated_hash) do diff --git a/apps/indexer/test/indexer/fetcher/token_balance_test.exs b/apps/indexer/test/indexer/fetcher/token_balance_test.exs index 6c090973fd1c..3da5675d52aa 100644 --- a/apps/indexer/test/indexer/fetcher/token_balance_test.exs +++ b/apps/indexer/test/indexer/fetcher/token_balance_test.exs @@ -261,7 +261,8 @@ defmodule Indexer.Fetcher.TokenBalanceTest do address_hash: "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", block_number: 19999, token_contract_address_hash: to_string(contract.contract_address_hash), - token_id: 11, + token_id: nil, + value: 100_500, token_type: "ERC-20" }, %{ @@ -275,5 +276,30 @@ defmodule Indexer.Fetcher.TokenBalanceTest do assert TokenBalance.import_token_balances(token_balances_params) == :ok end + + test "import ERC-404 token balances and return :ok" do + contract = insert(:token) + insert(:block, number: 19999) + + token_balances_params = [ + %{ + address_hash: "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + block_number: 19999, + token_contract_address_hash: to_string(contract.contract_address_hash), + token_id: 11, + token_type: "ERC-404" + }, + %{ + address_hash: "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + block_number: 19999, + token_contract_address_hash: to_string(contract.contract_address_hash), + token_id: nil, + value: 100_500, + token_type: "ERC-404" + } + ] + + assert TokenBalance.import_token_balances(token_balances_params) == :ok + end end end diff --git a/apps/indexer/test/indexer/token_balances_test.exs b/apps/indexer/test/indexer/token_balances_test.exs index 2f5c89c1a6dc..92265544486c 100644 --- a/apps/indexer/test/indexer/token_balances_test.exs +++ b/apps/indexer/test/indexer/token_balances_test.exs @@ -88,6 +88,63 @@ defmodule Indexer.TokenBalancesTest do } = result end + test "fetches balances of ERC-404 tokens" do + address = insert(:address, hash: "0x609991ca0ae39bc4eaf2669976237296d40c2f31") + + address_hash_string = Hash.to_string(address.hash) + + token_contract_address_hash = "0xf7f79032fd395978acb7069c74d21e5a53206559" + + contract_address = insert(:address, hash: token_contract_address_hash) + + token = insert(:token, contract_address: contract_address) + + data = [ + %{ + token_contract_address_hash: Hash.to_string(token.contract_address_hash), + address_hash: address_hash_string, + block_number: 1_000, + token_id: nil, + value: 10, + token_type: "ERC-404" + }, + %{ + token_contract_address_hash: Hash.to_string(token.contract_address_hash), + address_hash: address_hash_string, + block_number: 1_000, + token_id: 5, + token_type: "ERC-404", + value: 2 + } + ] + + get_404_ft_balances_from_blockchain() + get_404_nft_balances_from_blockchain() + + {:ok, result} = TokenBalances.fetch_token_balances_from_blockchain(data) + + assert %{ + failed_token_balances: [], + fetched_token_balances: [ + %{ + value: 10, + token_contract_address_hash: ^token_contract_address_hash, + address_hash: ^address_hash_string, + block_number: 1_000, + value_fetched_at: _ + }, + %{ + token_id: 5, + value: 2, + token_contract_address_hash: ^token_contract_address_hash, + address_hash: ^address_hash_string, + block_number: 1_000, + value_fetched_at: _ + } + ] + } = result + end + test "fetches multiple balances of tokens" do address_1 = insert(:address, hash: "0xecba3c9ea993b0e0594e0b0a0d361a1f9596e310") address_2 = insert(:address, hash: "0x609991ca0ae39bc4eaf2669976237296d40c2f31") @@ -363,6 +420,67 @@ defmodule Indexer.TokenBalancesTest do ) end + defp get_404_ft_balances_from_blockchain() do + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [ + %{ + id: id, + method: "eth_call", + params: [ + %{ + data: "0x70a08231000000000000000000000000609991ca0ae39bc4eaf2669976237296d40c2f31", + to: "0xf7f79032fd395978acb7069c74d21e5a53206559" + }, + "0x3E8" + ] + } + ], + _options -> + {:ok, + [ + %{ + id: id, + jsonrpc: "2.0", + result: "0x000000000000000000000000000000000000000000000000000000000000000a" + } + ]} + end + ) + end + + defp get_404_nft_balances_from_blockchain() do + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [ + %{ + id: id, + method: "eth_call", + params: [ + %{ + data: + "0x00fdd58e000000000000000000000000609991ca0ae39bc4eaf2669976237296d40c2f310000000000000000000000000000000000000000000000000000000000000005", + to: "0xf7f79032fd395978acb7069c74d21e5a53206559" + }, + "0x3E8" + ] + } + ], + _options -> + {:ok, + [ + %{ + id: id, + jsonrpc: "2.0", + result: "0x0000000000000000000000000000000000000000000000000000000000000002" + } + ]} + end + ) + end + defp get_erc1155_balance_from_blockchain() do expect( EthereumJSONRPC.Mox, diff --git a/apps/indexer/test/indexer/transform/token_transfers_test.exs b/apps/indexer/test/indexer/transform/token_transfers_test.exs index 3ff6e5e52fc0..f8be8358ab9d 100644 --- a/apps/indexer/test/indexer/transform/token_transfers_test.exs +++ b/apps/indexer/test/indexer/transform/token_transfers_test.exs @@ -187,7 +187,7 @@ defmodule Indexer.Transform.TokenTransfersTest do data: "0x1000000000000c520000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", first_topic: "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62", - secon_topic: "0x0000000000000000000000009c978f4cfa1fe13406bcc05baf26a35716f881dd", + second_topic: "0x0000000000000000000000009c978f4cfa1fe13406bcc05baf26a35716f881dd", third_topic: "0x0000000000000000000000009c978f4cfa1fe13406bcc05baf26a35716f881dd", fourth_topic: "0x0000000000000000000000009c978f4cfa1fe13406bcc05baf26a35716f881dd", index: 2, @@ -228,7 +228,7 @@ defmodule Indexer.Transform.TokenTransfersTest do data: "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000001388", first_topic: "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb", - secon_topic: "0x0000000000000000000000006c943470780461b00783ad530a53913bd2c104d3", + second_topic: "0x0000000000000000000000006c943470780461b00783ad530a53913bd2c104d3", third_topic: "0x0000000000000000000000006c943470780461b00783ad530a53913bd2c104d3", fourth_topic: "0x0000000000000000000000006c943470780461b00783ad530a53913bd2c104d3", index: 2, @@ -262,7 +262,7 @@ defmodule Indexer.Transform.TokenTransfersTest do data: "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", first_topic: "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb", - secon_topic: "0x81D0caF80E9bFfD9bF9c641ab964feB9ef69069e", + second_topic: "0x81D0caF80E9bFfD9bF9c641ab964feB9ef69069e", third_topic: "0x598AF04C88122FA4D1e08C5da3244C39F10D4F14", fourth_topic: "0x0000000000000000000000000000000000000000", index: 6, @@ -340,7 +340,7 @@ defmodule Indexer.Transform.TokenTransfersTest do data: "0x1000000000000c520000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", first_topic: "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62", - secon_topic: "0x0000000000000000000000009c978f4cfa1fe13406bcc05baf26a35716f881dd", + second_topic: "0x0000000000000000000000009c978f4cfa1fe13406bcc05baf26a35716f881dd", third_topic: "0x0000000000000000000000009c978f4cfa1fe13406bcc05baf26a35716f881dd", fourth_topic: "0x0000000000000000000000009c978f4cfa1fe13406bcc05baf26a35716f881dd", index: 2, @@ -357,6 +357,84 @@ defmodule Indexer.Transform.TokenTransfersTest do tokens: [%{contract_address_hash: ^contract_address_hash, type: "ERC-1155"}] } = TokenTransfers.parse(logs) end + + test "parses erc404 token transfer from ERC20Transfer" do + log = %{ + address_hash: "0x03F6CCfCE60273eFbEB9535675C8EFA69D863f37", + block_number: 10_561_358, + data: "0x00000000000000000000000000000000000000000000003635c9adc5de9ffc48", + first_topic: "0xe59fdd36d0d223c0c7d996db7ad796880f45e1936cb0bb7ac102e7082e031487", + second_topic: "0x000000000000000000000000c36442b4a4522e871399cd717abdd847ab11fe88", + third_topic: "0x00000000000000000000000018336808ed2f2c80795861041f711b299ecd38ca", + fourth_topic: nil, + index: 34, + transaction_hash: "0x6be468f465911ec70103aa83e38c84697848feaf760eee3a181ebcdcab82dc4a", + block_hash: "0x7cffabfd975bded1ec397f44b4af3a97618b96ca0e2f92d70a3025ba233815ca" + } + + assert TokenTransfers.parse([log]) == %{ + token_transfers: [ + %{ + block_hash: "0x7cffabfd975bded1ec397f44b4af3a97618b96ca0e2f92d70a3025ba233815ca", + block_number: 10_561_358, + from_address_hash: "0xc36442b4a4522e871399cd717abdd847ab11fe88", + log_index: 34, + to_address_hash: "0x18336808ed2f2c80795861041f711b299ecd38ca", + token_contract_address_hash: "0x03F6CCfCE60273eFbEB9535675C8EFA69D863f37", + amounts: [ + 999_999_999_999_999_999_048 + ], + token_ids: [], + token_type: "ERC-404", + transaction_hash: "0x6be468f465911ec70103aa83e38c84697848feaf760eee3a181ebcdcab82dc4a" + } + ], + tokens: [ + %{ + contract_address_hash: "0x03F6CCfCE60273eFbEB9535675C8EFA69D863f37", + type: "ERC-404" + } + ] + } + end + + test "parses erc404 token transfer from ERC721Transfer" do + log = %{ + address_hash: "0x68995c84aFb019913942E53F27E7ceA47D86Cd9d", + block_number: 10_514_498, + data: "0x", + first_topic: "0xe5f815dc84b8cecdfd4beedfc3f91ab5be7af100eca4e8fb11552b867995394f", + second_topic: "0x000000000000000000000000fd7ec4d8b6ba1a72f3895b6ce3846b00d6b83aab", + third_topic: "0x0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d", + fourth_topic: "0x000000000000000000000000000000000000000000000000000000000000000a", + index: 41, + transaction_hash: "0xe201aed9c948f46395c6acc54de5e9c3ebe0c41a5c34cc6a507b67ec46057c55", + block_hash: "0xea065ff2fc04177bbef27317209a25f2633199aa453b86ee405b619c495b2e77" + } + + assert TokenTransfers.parse([log]) == %{ + token_transfers: [ + %{ + block_hash: "0xea065ff2fc04177bbef27317209a25f2633199aa453b86ee405b619c495b2e77", + block_number: 10_514_498, + from_address_hash: "0xfd7ec4d8b6ba1a72f3895b6ce3846b00d6b83aab", + log_index: 41, + to_address_hash: "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", + token_contract_address_hash: "0x68995c84aFb019913942E53F27E7ceA47D86Cd9d", + amounts: [], + token_ids: [10], + token_type: "ERC-404", + transaction_hash: "0xe201aed9c948f46395c6acc54de5e9c3ebe0c41a5c34cc6a507b67ec46057c55" + } + ], + tokens: [ + %{ + contract_address_hash: "0x68995c84aFb019913942E53F27E7ceA47D86Cd9d", + type: "ERC-404" + } + ] + } + end end defp truncated_hash("0x000000000000000000000000" <> rest) do From a5130b38f1e634d999b0f7c8d6fcdd77bb99b721 Mon Sep 17 00:00:00 2001 From: varasev <33550681+varasev@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:41:33 +0300 Subject: [PATCH 606/607] Hotfix for Optimism Ecotone batch blobs indexing (#9646) * Hotfix for Optimism Ecotone batch blobs indexing * Update changelog --------- Co-authored-by: POA <33550681+poa@users.noreply.github.com> --- CHANGELOG.md | 1 + .../lib/explorer/chain/optimism/txn_batch.ex | 8 ++-- .../lib/indexer/fetcher/optimism/txn_batch.ex | 45 +++++++++++-------- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4458b99401d1..f77db8bf5be5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ ### Fixes +- [#9646](https://github.com/blockscout/blockscout/pull/9646) - Hotfix for Optimism Ecotone batch blobs indexing - [#9640](https://github.com/blockscout/blockscout/pull/9640) - Fix no function clause matching in `BENS.item_to_address_hash_strings/1` - [#9638](https://github.com/blockscout/blockscout/pull/9638) - Do not broadcast coin balance changes with empty value/delta - [#9635](https://github.com/blockscout/blockscout/pull/9635) - Reset missing ranges collector to max number after the cycle is done diff --git a/apps/explorer/lib/explorer/chain/optimism/txn_batch.ex b/apps/explorer/lib/explorer/chain/optimism/txn_batch.ex index 7ba61b2f2ee1..83b70e3d24fa 100644 --- a/apps/explorer/lib/explorer/chain/optimism/txn_batch.ex +++ b/apps/explorer/lib/explorer/chain/optimism/txn_batch.ex @@ -102,13 +102,13 @@ defmodule Explorer.Chain.Optimism.TxnBatch do end end) - Enum.each(Range.new(output_len, byte_size(output) - 1), fn i -> + Enum.each(Range.new(output_len, byte_size(output) - 1, 1), fn i -> <<0>> = binary_part(output, i, 1) end) output = binary_part(output, 0, output_len) - Enum.each(Range.new(ipos, @blob_size - 1), fn i -> + Enum.each(Range.new(ipos, @blob_size - 1, 1), fn i -> <<0>> = binary_part(b, i, 1) end) @@ -118,10 +118,10 @@ defmodule Explorer.Chain.Optimism.TxnBatch do end defp decode_eip4844_field_element(b, opos, ipos, output) do - <<_::binary-size(ipos), ipos_byte::size(8), insert::binary-size(32), _::binary>> = b + <<_::binary-size(ipos), ipos_byte::size(8), insert::binary-size(31), _::binary>> = b if Bitwise.band(ipos_byte, 0b11000000) == 0 do - <> = output + <> = output {ipos_byte, opos + 32, ipos + 32, output_before_opos <> insert <> rest} end diff --git a/apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex b/apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex index 8010861c2d97..fa0dfcd371d4 100644 --- a/apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex +++ b/apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex @@ -409,8 +409,9 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do end end - defp blobs_to_input(transaction_hash, blob_versioned_hashes, block_timestamp, blobs_api_url) do - Enum.reduce(blob_versioned_hashes, <<>>, fn blob_hash, acc -> + defp blobs_to_inputs(transaction_hash, blob_versioned_hashes, block_timestamp, blobs_api_url) do + blob_versioned_hashes + |> Enum.reduce([], fn blob_hash, acc -> with {:ok, response} <- http_get_request(blobs_api_url <> "/" <> blob_hash), blob_data = Map.get(response, "blob_data"), false <- is_nil(blob_data) do @@ -425,8 +426,11 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do Logger.warning("Cannot decode the blob #{blob_hash} taken from the Blockscout Blobs API.") acc else - Logger.info("The input for transaction #{transaction_hash} is taken from the Blockscout Blobs API.") - acc <> decoded + Logger.info( + "The input for transaction #{transaction_hash} is taken from the Blockscout Blobs API. Blob hash: #{blob_hash}" + ) + + [decoded | acc] end else _ -> @@ -470,8 +474,11 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do if is_nil(decoded_blob_data) do raise "Invalid blob" else - Logger.info("The input for transaction #{transaction_hash} is taken from the Beacon Node.") - acc <> decoded_blob_data + Logger.info( + "The input for transaction #{transaction_hash} is taken from the Beacon Node. Blob hash: #{blob_hash}" + ) + + [decoded_blob_data | acc] end rescue reason -> @@ -483,6 +490,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do end end end) + |> Enum.reverse() end defp get_txn_batches_inner( @@ -496,30 +504,31 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do transactions_filtered |> Enum.reduce({:ok, incomplete_channels, [], []}, fn tx, {_, incomplete_channels_acc, batches_acc, sequences_acc} -> - input = + inputs = if tx.type == 3 do - # this is EIP-4844 transaction, so we get the input from the blobs + # this is EIP-4844 transaction, so we get the inputs from the blobs block_timestamp = get_block_timestamp_by_number(tx.block_number, blocks_params) - blobs_to_input(tx.hash, tx.blob_versioned_hashes, block_timestamp, blobs_api_url) + blobs_to_inputs(tx.hash, tx.blob_versioned_hashes, block_timestamp, blobs_api_url) else - tx.input + [tx.input] end - if tx.type == 3 and input == <<>> do - # skip this transaction as we cannot find or read its blobs - {:ok, incomplete_channels_acc, batches_acc, sequences_acc} - else + Enum.reduce(inputs, {:ok, incomplete_channels_acc, batches_acc, sequences_acc}, fn input, + {_, + new_incomplete_channels_acc, + new_batches_acc, + new_sequences_acc} -> handle_input( input, tx, blocks_params, - incomplete_channels_acc, - batches_acc, - sequences_acc, + new_incomplete_channels_acc, + new_batches_acc, + new_sequences_acc, genesis_block_l2, json_rpc_named_arguments_l2 ) - end + end) end) end From 510bf9d79194065119b85258d0bcf427ae626984 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Fri, 15 Mar 2024 18:00:14 +0300 Subject: [PATCH 607/607] 6.3.0 --- CHANGELOG.md | 36 +++++++++++++++++++++++++++++++ apps/block_scout_web/mix.exs | 2 +- apps/ethereum_jsonrpc/mix.exs | 2 +- apps/explorer/mix.exs | 2 +- apps/indexer/mix.exs | 2 +- docker-compose/docker-compose.yml | 2 +- docker/Makefile | 2 +- mix.exs | 2 +- rel/config.exs | 2 +- 9 files changed, 44 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f77db8bf5be5..e2073100db10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ ### Features +### Fixes + +### Chore + +
+ Dependencies version bumps + +
+ +## 6.3.0 + +### Features + - [#9631](https://github.com/blockscout/blockscout/pull/9631) - Initial support of zksync chain type - [#9532](https://github.com/blockscout/blockscout/pull/9532) - Add last output root size counter - [#9511](https://github.com/blockscout/blockscout/pull/9511) - Separate errors by type in EndpointAvailabilityObserver @@ -64,6 +77,29 @@ - [#9260](https://github.com/blockscout/blockscout/pull/9260) - Optimism Delta upgrade support by Indexer.Fetcher.OptimismTxnBatch module - [#8740](https://github.com/blockscout/blockscout/pull/8740) - Add delay to Indexer.Fetcher.OptimismTxnBatch module initialization +
+ Dependencies version bumps + +- [#9544](https://github.com/blockscout/blockscout/pull/9544) - Bump @babel/core from 7.23.9 to 7.24.0 in /apps/block_scout_web/assets +- [#9537](https://github.com/blockscout/blockscout/pull/9537) - Bump logger_json from 5.1.3 to 5.1.4 +- [#9550](https://github.com/blockscout/blockscout/pull/9550) - Bump xss from 1.0.14 to 1.0.15 in /apps/block_scout_web/assets +- [#9539](https://github.com/blockscout/blockscout/pull/9539) - Bump floki from 0.35.4 to 0.36.0 +- [#9551](https://github.com/blockscout/blockscout/pull/9551) - Bump @amplitude/analytics-browser from 2.5.1 to 2.5.2 in /apps/block_scout_web/assets +- [#9547](https://github.com/blockscout/blockscout/pull/9547) - Bump @babel/preset-env from 7.23.9 to 7.24.0 in /apps/block_scout_web/assets +- [#9549](https://github.com/blockscout/blockscout/pull/9549) - Bump postcss-loader from 8.1.0 to 8.1.1 in /apps/block_scout_web/assets +- [#9542](https://github.com/blockscout/blockscout/pull/9542) - Bump phoenix_ecto from 4.4.3 to 4.5.0 +- [#9546](https://github.com/blockscout/blockscout/pull/9546) - https://github.com/blockscout/blockscout/pull/9546 +- [#9545](https://github.com/blockscout/blockscout/pull/9545) - Bump chart.js from 4.4.1 to 4.4.2 in /apps/block_scout_web/assets +- [#9540](https://github.com/blockscout/blockscout/pull/9540) - Bump postgrex from 0.17.4 to 0.17.5 +- [#9543](https://github.com/blockscout/blockscout/pull/9543) - Bump ueberauth from 0.10.7 to 0.10.8 +- [#9538](https://github.com/blockscout/blockscout/pull/9538) - Bump credo from 1.7.4 to 1.7.5 +- [#9607](https://github.com/blockscout/blockscout/pull/9607) - Bump redix from 1.3.0 to 1.4.1 +- [#9606](https://github.com/blockscout/blockscout/pull/9606) - Bump ecto from 3.11.1 to 3.11.2 +- [#9605](https://github.com/blockscout/blockscout/pull/9605) - Bump ex_doc from 0.31.1 to 0.31.2 +- [#9604](https://github.com/blockscout/blockscout/pull/9604) - Bump phoenix_ecto from 4.5.0 to 4.5.1 + +
+ ## 6.2.2 ### Features diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index 78757a211351..271d7adc4dd0 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -23,7 +23,7 @@ defmodule BlockScoutWeb.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.2.2", + version: "6.3.0", xref: [ exclude: [ Explorer.Chain.PolygonZkevm.Reader, diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index 48847ad2d33c..63d06afe77c5 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -23,7 +23,7 @@ defmodule EthereumJsonrpc.MixProject do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.2.2" + version: "6.3.0" ] end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 68edc0796dca..d858c9508025 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -24,7 +24,7 @@ defmodule Explorer.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.2.2", + version: "6.3.0", xref: [exclude: [BlockScoutWeb.WebRouter.Helpers, Indexer.Helper]] ] end diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs index 58bdf7c312cf..6c37e44898b1 100644 --- a/apps/indexer/mix.exs +++ b/apps/indexer/mix.exs @@ -14,7 +14,7 @@ defmodule Indexer.MixProject do elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", start_permanent: Mix.env() == :prod, - version: "6.2.2", + version: "6.3.0", xref: [ exclude: [ Explorer.Chain.Optimism.Deposit, diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 32098e476758..f306bb7036fa 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -37,7 +37,7 @@ services: CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED: "" CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL: "" ADMIN_PANEL_ENABLED: "" - RELEASE_VERSION: 6.2.2 + RELEASE_VERSION: 6.3.0 links: - db:database environment: diff --git a/docker/Makefile b/docker/Makefile index da8770a93451..02496904fd38 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -10,7 +10,7 @@ STATS_CONTAINER_NAME := stats STATS_DB_CONTAINER_NAME := stats-db PROXY_CONTAINER_NAME := proxy PG_CONTAINER_NAME := postgres -RELEASE_VERSION ?= '6.2.2' +RELEASE_VERSION ?= '6.3.0' TAG := $(RELEASE_VERSION)-commit-$(shell git log -1 --pretty=format:"%h") STABLE_TAG := $(RELEASE_VERSION) diff --git a/mix.exs b/mix.exs index 70de7c9eab60..70e3fced9a9a 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule BlockScout.Mixfile do [ # app: :block_scout, # aliases: aliases(config_env()), - version: "6.2.2", + version: "6.3.0", apps_path: "apps", deps: deps(), dialyzer: dialyzer(), diff --git a/rel/config.exs b/rel/config.exs index 866dbde4644b..6b33ba9bf76f 100644 --- a/rel/config.exs +++ b/rel/config.exs @@ -71,7 +71,7 @@ end # will be used by default release :blockscout do - set version: "6.2.2-beta" + set version: "6.3.0-beta" set applications: [ :runtime_tools, block_scout_web: :permanent,